1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Huntie\JsonApi\Serializers; |
4
|
|
|
|
5
|
|
|
use Illuminate\Database\Eloquent\Model; |
6
|
|
|
use Illuminate\Support\Collection; |
7
|
|
|
|
8
|
|
|
class ModelSerializer |
9
|
|
|
{ |
10
|
|
|
/** |
11
|
|
|
* The model instance to transform. |
12
|
|
|
* |
13
|
|
|
* @var \Illuminate\Database\Eloquent\Model |
14
|
|
|
*/ |
15
|
|
|
protected $record; |
16
|
|
|
|
17
|
|
|
/** |
18
|
|
|
* The relationships to return. |
19
|
|
|
* |
20
|
|
|
* @var array |
21
|
|
|
*/ |
22
|
|
|
protected $relationships; |
23
|
|
|
|
24
|
|
|
/** |
25
|
|
|
* The subset of record attributes to return. |
26
|
|
|
* |
27
|
|
|
* @var array |
28
|
|
|
*/ |
29
|
|
|
protected $fields; |
30
|
|
|
|
31
|
|
|
/** |
32
|
|
|
* The relationships to load and include. |
33
|
|
|
* |
34
|
|
|
* @var array |
35
|
|
|
*/ |
36
|
|
|
protected $include; |
37
|
|
|
|
38
|
|
|
/** |
39
|
|
|
* Meta information to include. |
40
|
|
|
* |
41
|
|
|
* @var \Illuminate\Support\Collection |
42
|
|
|
*/ |
43
|
|
|
protected $meta; |
44
|
|
|
|
45
|
|
|
/** |
46
|
|
|
* Resource links to include. |
47
|
|
|
* |
48
|
|
|
* @var \Illuminate\Support\Collection |
49
|
|
|
*/ |
50
|
|
|
protected $links; |
51
|
|
|
|
52
|
|
|
/** |
53
|
|
|
* Create a new JSON API resource serializer. |
54
|
|
|
* |
55
|
|
|
* @param Model $record The model instance to serialise |
56
|
|
|
* @param array|null $fields Subset of fields to return |
57
|
|
|
* @param array|null $include Relations to include |
58
|
|
|
*/ |
59
|
|
|
public function __construct($record, array $fields = [], array $include = []) |
60
|
|
|
{ |
61
|
|
|
$this->record = $record; |
62
|
|
|
$this->relationships = array_merge($record->getRelations(), $include); |
63
|
|
|
$this->fields = array_unique($fields); |
64
|
|
|
$this->include = array_unique($include); |
65
|
|
|
$this->meta = collect([]); |
66
|
|
|
$this->links = collect([]); |
67
|
|
|
} |
68
|
|
|
|
69
|
|
|
/** |
70
|
|
|
* Limit which relations can be included. |
71
|
|
|
* |
72
|
|
|
* @param array $include |
73
|
|
|
*/ |
74
|
|
|
public function scopeIncludes($include) |
75
|
|
|
{ |
76
|
|
|
$this->include = array_intersect($this->include, $include); |
77
|
|
|
} |
78
|
|
|
|
79
|
|
|
/** |
80
|
|
|
* Add meta information to the returned object. |
81
|
|
|
* |
82
|
|
|
* @param string|array $key |
83
|
|
|
* @param string|int|null $value |
84
|
|
|
*/ |
85
|
|
|
public function addMeta($key, $value = null) |
86
|
|
|
{ |
87
|
|
|
$this->meta = $this->meta->merge(is_array($key) ? $key : [$key => $value]); |
88
|
|
|
} |
89
|
|
|
|
90
|
|
|
/** |
91
|
|
|
* Add one or more links to the returned object. |
92
|
|
|
* |
93
|
|
|
* @param string|array $key |
94
|
|
|
* @param string|int|null $value |
95
|
|
|
*/ |
96
|
|
|
public function addLinks($key, $value = null) |
97
|
|
|
{ |
98
|
|
|
$this->links = $this->links->merge(is_array($key) ? $key : [$key => $value]); |
99
|
|
|
} |
100
|
|
|
|
101
|
|
|
/** |
102
|
|
|
* Return a JSON API resource identifier object for the primary record. |
103
|
|
|
* |
104
|
|
|
* @return array |
105
|
|
|
*/ |
106
|
|
|
public function toResourceIdentifier() |
107
|
|
|
{ |
108
|
|
|
return [ |
109
|
|
|
'type' => $this->getRecordType(), |
110
|
|
|
'id' => $this->record->id, |
111
|
|
|
]; |
112
|
|
|
} |
113
|
|
|
|
114
|
|
|
/** |
115
|
|
|
* Return a base JSON API resource object for the primary record containing |
116
|
|
|
* only immediate attributes. |
117
|
|
|
* |
118
|
|
|
* @return array |
119
|
|
|
*/ |
120
|
|
|
public function toBaseResourceObject() |
121
|
|
|
{ |
122
|
|
|
return array_merge($this->toResourceIdentifier(), [ |
123
|
|
|
'attributes' => $this->transformRecordAttributes(), |
124
|
|
|
]); |
125
|
|
|
} |
126
|
|
|
|
127
|
|
|
/** |
128
|
|
|
* Return a full JSON API resource object for the primary record. |
129
|
|
|
* |
130
|
|
|
* @return array |
131
|
|
|
*/ |
132
|
|
|
public function toResourceObject() |
133
|
|
|
{ |
134
|
|
|
$this->record->load($this->relationships); |
135
|
|
|
|
136
|
|
|
return array_merge($this->toBaseResourceObject(), [ |
137
|
|
|
'relationships' => $this->transformRecordRelations()->toArray(), |
138
|
|
|
]); |
139
|
|
|
} |
140
|
|
|
|
141
|
|
|
/** |
142
|
|
|
* Serialise complete JSON API document to an array. |
143
|
|
|
* |
144
|
|
|
* @return array |
145
|
|
|
*/ |
146
|
|
|
public function serialiseToObject() |
147
|
|
|
{ |
148
|
|
|
return array_filter([ |
149
|
|
|
'data' => $this->toResourceObject(), |
150
|
|
|
'included' => $this->transformIncludedRelations()->toArray(), |
151
|
|
|
'links' => $this->links->toArray(), |
152
|
|
|
'meta' => $this->meta->toArray(), |
153
|
|
|
]); |
154
|
|
|
} |
155
|
|
|
|
156
|
|
|
/** |
157
|
|
|
* Serialise complete JSON API document to a JSON string. |
158
|
|
|
* |
159
|
|
|
* @return array |
160
|
|
|
*/ |
161
|
|
|
public function serializeToJson() |
162
|
|
|
{ |
163
|
|
|
return json_encode($this->serialiseToObject()); |
164
|
|
|
} |
165
|
|
|
|
166
|
|
|
/** |
167
|
|
|
* Return the primary record type name. |
168
|
|
|
* |
169
|
|
|
* @return string |
170
|
|
|
*/ |
171
|
|
|
protected function getRecordType() |
172
|
|
|
{ |
173
|
|
|
$modelName = collect(explode('\\', get_class($this->record)))->last(); |
174
|
|
|
|
175
|
|
|
return snake_case(str_plural($modelName), '-'); |
176
|
|
|
} |
177
|
|
|
|
178
|
|
|
/** |
179
|
|
|
* Return the attribute object data for the primary record. |
180
|
|
|
* |
181
|
|
|
* @return array |
182
|
|
|
*/ |
183
|
|
|
protected function transformRecordAttributes() |
184
|
|
|
{ |
185
|
|
|
$attributes = array_diff_key($this->record->toArray(), $this->record->getRelations()); |
186
|
|
|
$attributes = array_except($attributes, ['id']); |
187
|
|
|
|
188
|
|
|
if (!empty($this->fields)) { |
189
|
|
|
$attributes = array_only($attributes, $this->fields); |
190
|
|
|
} |
191
|
|
|
|
192
|
|
|
return $attributes; |
193
|
|
|
} |
194
|
|
|
|
195
|
|
|
/** |
196
|
|
|
* Return a collection of JSON API resource identifier objects by each |
197
|
|
|
* relation on the primary record. |
198
|
|
|
* |
199
|
|
|
* @return \Illuminate\Support\Collection |
200
|
|
|
*/ |
201
|
|
View Code Duplication |
protected function transformRecordRelations() |
|
|
|
|
202
|
|
|
{ |
203
|
|
|
$relationships = collect([]); |
204
|
|
|
|
205
|
|
|
foreach ($this->relationships as $relation) { |
206
|
|
|
$data = $this->mapRelation($relation, function ($record) { |
207
|
|
|
return (new static($record))->toResourceIdentifier(); |
208
|
|
|
}); |
209
|
|
|
|
210
|
|
|
$relationships = $relationships->merge([$relation => compact('data')]); |
211
|
|
|
} |
212
|
|
|
|
213
|
|
|
return $relationships; |
214
|
|
|
} |
215
|
|
|
|
216
|
|
|
/** |
217
|
|
|
* Return a collection of JSON API resource objects for each included |
218
|
|
|
* relationship. |
219
|
|
|
* |
220
|
|
|
* @return \Illuminate\Support\Collection |
221
|
|
|
*/ |
222
|
|
View Code Duplication |
protected function transformIncludedRelations() |
|
|
|
|
223
|
|
|
{ |
224
|
|
|
$included = collect([]); |
225
|
|
|
|
226
|
|
|
foreach ($this->include as $relation) { |
227
|
|
|
$records = $this->mapRelation($relation, function ($record) { |
228
|
|
|
return (new static($record))->toBaseResourceObject(); |
229
|
|
|
}); |
230
|
|
|
|
231
|
|
|
$included = $included->merge(collect($records)); |
232
|
|
|
} |
233
|
|
|
|
234
|
|
|
return $included; |
235
|
|
|
} |
236
|
|
|
|
237
|
|
|
/** |
238
|
|
|
* Run a map over each item in a loaded relation on the primary record. |
239
|
|
|
* |
240
|
|
|
* @param string $relation |
241
|
|
|
* @param \Closure $callback |
242
|
|
|
* |
243
|
|
|
* @return Collection|Model|null |
244
|
|
|
*/ |
245
|
|
|
protected function mapRelation($relation, $callback) |
246
|
|
|
{ |
247
|
|
|
$loadedRelation = $this->record->{$relation}; |
248
|
|
|
|
249
|
|
|
if ($loadedRelation instanceof Collection) { |
250
|
|
|
return $loadedRelation->map($callback); |
251
|
|
|
} else if ($loadedRelation instanceof Model) { |
252
|
|
|
return $callback($loadedRelation); |
253
|
|
|
} |
254
|
|
|
|
255
|
|
|
return null; |
256
|
|
|
} |
257
|
|
|
} |
258
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.