Maintain local ⭤ remote in sync with automatic AT Protocol parity for Laravel (alpha & unstable)
1<?php
2
3namespace SocialDept\AtpParity;
4
5use Illuminate\Database\Eloquent\Model;
6use SocialDept\AtpParity\Contracts\RecordMapper as RecordMapperContract;
7use SocialDept\AtpSchema\Data\Data;
8
9/**
10 * Abstract base class for bidirectional Record <-> Model mapping.
11 *
12 * @template TRecord of Data
13 * @template TModel of Model
14 *
15 * @implements RecordMapperContract<TRecord, TModel>
16 */
17abstract class RecordMapper implements RecordMapperContract
18{
19 /**
20 * Get the Record class this mapper handles.
21 *
22 * @return class-string<TRecord>
23 */
24 abstract public function recordClass(): string;
25
26 /**
27 * Get the Model class this mapper handles.
28 *
29 * @return class-string<TModel>
30 */
31 abstract public function modelClass(): string;
32
33 /**
34 * Map record properties to model attributes.
35 *
36 * @param TRecord $record
37 * @return array<string, mixed>
38 */
39 abstract protected function recordToAttributes(Data $record): array;
40
41 /**
42 * Map model attributes to record properties.
43 *
44 * @param TModel $model
45 * @return array<string, mixed>
46 */
47 abstract protected function modelToRecordData(Model $model): array;
48
49 /**
50 * Get the lexicon NSID this mapper handles.
51 */
52 public function lexicon(): string
53 {
54 $recordClass = $this->recordClass();
55
56 return $recordClass::getLexicon();
57 }
58
59 /**
60 * Get the column name for storing the AT Protocol URI.
61 */
62 protected function uriColumn(): string
63 {
64 return config('parity.columns.uri', 'atp_uri');
65 }
66
67 /**
68 * Get the column name for storing the AT Protocol CID.
69 */
70 protected function cidColumn(): string
71 {
72 return config('parity.columns.cid', 'atp_cid');
73 }
74
75 public function toModel(Data $record, array $meta = []): Model
76 {
77 $modelClass = $this->modelClass();
78 $attributes = $this->recordToAttributes($record);
79 $attributes = $this->applyMeta($attributes, $meta);
80
81 return new $modelClass($attributes);
82 }
83
84 public function toRecord(Model $model): Data
85 {
86 $recordClass = $this->recordClass();
87
88 return $recordClass::fromArray($this->modelToRecordData($model));
89 }
90
91 public function updateModel(Model $model, Data $record, array $meta = []): Model
92 {
93 $attributes = $this->recordToAttributes($record);
94 $attributes = $this->applyMeta($attributes, $meta);
95 $model->fill($attributes);
96
97 return $model;
98 }
99
100 public function findByUri(string $uri): ?Model
101 {
102 $modelClass = $this->modelClass();
103
104 return $modelClass::where($this->uriColumn(), $uri)->first();
105 }
106
107 public function upsert(Data $record, array $meta = []): Model
108 {
109 $uri = $meta['uri'] ?? null;
110
111 if ($uri) {
112 $existing = $this->findByUri($uri);
113
114 if ($existing) {
115 $this->updateModel($existing, $record, $meta);
116 $existing->save();
117
118 return $existing;
119 }
120 }
121
122 $model = $this->toModel($record, $meta);
123 $model->save();
124
125 return $model;
126 }
127
128 public function deleteByUri(string $uri): bool
129 {
130 $model = $this->findByUri($uri);
131
132 if ($model) {
133 return (bool) $model->delete();
134 }
135
136 return false;
137 }
138
139 /**
140 * Apply AT Protocol metadata to attributes.
141 */
142 protected function applyMeta(array $attributes, array $meta): array
143 {
144 if (isset($meta['uri'])) {
145 $attributes[$this->uriColumn()] = $meta['uri'];
146 }
147
148 if (isset($meta['cid'])) {
149 $attributes[$this->cidColumn()] = $meta['cid'];
150 }
151
152 return $attributes;
153 }
154}