1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Flugg\Responder\Transformers\Concerns; |
4
|
|
|
|
5
|
|
|
use Illuminate\Database\Eloquent\Model; |
6
|
|
|
|
7
|
|
|
/** |
8
|
|
|
* A trait to be used by a transformer to handle relations |
9
|
|
|
* |
10
|
|
|
* @package flugger/laravel-responder |
11
|
|
|
* @author Alexander Tømmerås <[email protected]> |
12
|
|
|
* @license The MIT License |
13
|
|
|
*/ |
14
|
|
|
trait HasRelationships |
15
|
|
|
{ |
16
|
|
|
/** |
17
|
|
|
* List of available relations. |
18
|
|
|
* |
19
|
|
|
* @var string[] |
20
|
|
|
*/ |
21
|
|
|
protected $relations = []; |
22
|
|
|
|
23
|
|
|
/** |
24
|
|
|
* A list of autoloaded default relations. |
25
|
|
|
* |
26
|
|
|
* @var array |
27
|
|
|
*/ |
28
|
|
|
protected $load = []; |
29
|
|
|
|
30
|
|
|
/** |
31
|
|
|
* Get a list of whitelisted relations that are requested, including nested relations. |
32
|
|
|
* |
33
|
|
|
* @param array $requested |
34
|
|
|
* @return array |
35
|
|
|
*/ |
36
|
41 |
View Code Duplication |
public function relations(array $requested = []): array |
|
|
|
|
37
|
|
|
{ |
38
|
41 |
|
$requested = $this->normalizeRelations($requested); |
39
|
41 |
|
$relations = $this->applyQueryConstraints($this->extractRelations($requested)); |
40
|
41 |
|
$nestedRelations = $this->nestedRelations($requested, $relations, 'relations'); |
41
|
|
|
|
42
|
41 |
|
return array_merge($relations, $nestedRelations); |
43
|
|
|
} |
44
|
|
|
|
45
|
|
|
/** |
46
|
|
|
* Get a list of default relations including nested relations. |
47
|
|
|
* |
48
|
|
|
* @param array $requested |
49
|
|
|
* @return array |
50
|
|
|
*/ |
51
|
41 |
View Code Duplication |
public function defaultRelations(array $requested = []): array |
|
|
|
|
52
|
|
|
{ |
53
|
41 |
|
$requested = $this->normalizeRelations($requested); |
54
|
41 |
|
$relations = $this->applyQueryConstraints($this->normalizeRelations($this->load)); |
55
|
41 |
|
$nestedRelations = $this->nestedRelations($relations, array_merge($relations, $requested), 'defaultRelations'); |
56
|
|
|
|
57
|
41 |
|
return array_merge($relations, $nestedRelations); |
58
|
|
|
} |
59
|
|
|
|
60
|
|
|
/** |
61
|
|
|
* Get a list of available relations from the transformer with a normalized structure. |
62
|
|
|
* |
63
|
|
|
* @return array |
64
|
|
|
*/ |
65
|
44 |
|
protected function availableRelations(): array |
66
|
|
|
{ |
67
|
44 |
|
return $this->normalizeRelations(array_merge($this->relations, $this->load)); |
68
|
|
|
} |
69
|
|
|
|
70
|
|
|
/** |
71
|
|
|
* Get nested relations from transformers resolved from the $available parameter that |
72
|
|
|
* also occur in the $requested parameter. |
73
|
|
|
* |
74
|
|
|
* @param array $requested |
75
|
|
|
* @param array $available |
76
|
|
|
* @param string $method |
77
|
|
|
* @return array |
78
|
|
|
*/ |
79
|
41 |
|
protected function nestedRelations(array $requested, array $available, string $method): array |
80
|
|
|
{ |
81
|
41 |
|
$transformers = $this->mappedTransformers($available); |
82
|
|
|
|
83
|
41 |
|
return collect(array_keys($transformers))->reduce(function ($nestedRelations, $relation) use ($requested, $method, $transformers) { |
84
|
12 |
|
$transformer = $transformers[$relation]; |
85
|
12 |
|
$children = $this->extractChildRelations($requested, $relation); |
86
|
12 |
|
$childRelations = $this->wrapChildRelations($transformer->$method($children), $relation); |
87
|
|
|
|
88
|
12 |
|
return array_merge($nestedRelations, $childRelations); |
89
|
41 |
|
}, []); |
90
|
|
|
} |
91
|
|
|
|
92
|
|
|
/** |
93
|
|
|
* Extract available root relations from the given list of relations. |
94
|
|
|
* |
95
|
|
|
* @param array $relations |
96
|
|
|
* @return array |
97
|
|
|
*/ |
98
|
41 |
|
protected function extractRelations(array $relations): array |
99
|
|
|
{ |
100
|
41 |
|
$available = $this->availableRelations(); |
101
|
|
|
|
102
|
18 |
|
return array_filter($this->mapRelations($relations, function ($relation, $constraint) { |
103
|
18 |
|
$identifier = explode('.', $relation)[0]; |
104
|
18 |
|
$constraint = $identifier === $relation ? $constraint : null; |
105
|
|
|
|
106
|
18 |
|
return [$identifier => $constraint ?: $this->resolveQueryConstraint($identifier)]; |
107
|
|
|
}), function ($relation) use ($available) { |
108
|
18 |
|
return array_has($available, explode(':', $relation)[0]); |
109
|
41 |
|
}, ARRAY_FILTER_USE_KEY); |
110
|
|
|
} |
111
|
|
|
|
112
|
|
|
/** |
113
|
|
|
* Extract all nested relations under a given identifier. |
114
|
|
|
* |
115
|
|
|
* @param array $relations |
116
|
|
|
* @param string $identifier |
117
|
|
|
* @return array |
118
|
|
|
*/ |
119
|
|
|
protected function extractChildRelations(array $relations, string $identifier): array |
120
|
|
|
{ |
121
|
12 |
|
return array_reduce(array_keys($relations), function ($nested, $relation) use ($relations, $identifier) { |
122
|
12 |
|
if (! starts_with($relation, "$identifier.")) { |
123
|
12 |
|
return $nested; |
124
|
|
|
} |
125
|
|
|
|
126
|
6 |
|
$nestedIdentifier = explode('.', $relation); |
127
|
6 |
|
array_shift($nestedIdentifier); |
128
|
|
|
|
129
|
6 |
|
return array_merge($nested, [implode('.', $nestedIdentifier) => $relations[$relation]]); |
130
|
12 |
|
}, []); |
131
|
|
|
} |
132
|
|
|
|
133
|
|
|
/** |
134
|
|
|
* Wrap the identifier of each relation of the given list of nested relations with |
135
|
|
|
* the parent relation identifier using dot notation. |
136
|
|
|
* |
137
|
|
|
* @param array $nestedRelations |
138
|
|
|
* @param string $relation |
139
|
|
|
* @return array |
140
|
|
|
*/ |
141
|
|
|
protected function wrapChildRelations(array $nestedRelations, string $relation): array |
142
|
|
|
{ |
143
|
12 |
|
return $this->mapRelations($nestedRelations, function ($nestedRelation, $constraint) use ($relation) { |
144
|
9 |
|
return ["$relation.$nestedRelation" => $constraint]; |
145
|
12 |
|
}); |
146
|
|
|
} |
147
|
|
|
|
148
|
|
|
/** |
149
|
|
|
* Normalize relations to force an [identifier => constraint/transformer] structure. |
150
|
|
|
* |
151
|
|
|
* @param array $relations |
152
|
|
|
* @return array |
153
|
|
|
*/ |
154
|
|
|
protected function normalizeRelations(array $relations): array |
155
|
|
|
{ |
156
|
47 |
|
return array_reduce(array_keys($relations), function ($normalized, $relation) use ($relations) { |
157
|
26 |
|
if (is_numeric($relation)) { |
158
|
16 |
|
return array_merge($normalized, [$relations[$relation] => null]); |
159
|
|
|
} |
160
|
|
|
|
161
|
22 |
|
return array_merge($normalized, [$relation => $relations[$relation]]); |
162
|
47 |
|
}, []); |
163
|
|
|
} |
164
|
|
|
|
165
|
|
|
/** |
166
|
|
|
* Map over a list of relations with the [identifier => constraint/transformer] structure. |
167
|
|
|
* |
168
|
|
|
* @param array $relations |
169
|
|
|
* @param callable $callback |
170
|
|
|
* @return array |
171
|
|
|
*/ |
172
|
41 |
|
protected function mapRelations(array $relations, callable $callback): array |
173
|
|
|
{ |
174
|
41 |
|
$mapped = []; |
175
|
|
|
|
176
|
41 |
|
foreach ($relations as $identifier => $value) { |
177
|
20 |
|
$mapped = array_merge($mapped, $callback($identifier, $value)); |
178
|
|
|
} |
179
|
|
|
|
180
|
41 |
|
return $mapped; |
181
|
|
|
} |
182
|
|
|
|
183
|
|
|
/** |
184
|
|
|
* Applies any query constraints defined in the transformer to the list of relaations. |
185
|
|
|
* |
186
|
|
|
* @param array $relations |
187
|
|
|
* @return array |
188
|
|
|
*/ |
189
|
|
|
protected function applyQueryConstraints(array $relations): array |
190
|
|
|
{ |
191
|
41 |
|
return $this->mapRelations($relations, function ($relation, $constraint) { |
192
|
19 |
|
return [$relation => is_callable($constraint) ? $constraint : $this->resolveQueryConstraint($relation)]; |
193
|
41 |
|
}); |
194
|
|
|
} |
195
|
|
|
|
196
|
|
|
/** |
197
|
|
|
* Resolve a query constraint for a given relation identifier. |
198
|
|
|
* |
199
|
|
|
* @param string $identifier |
200
|
|
|
* @return \Closure|null |
201
|
|
|
*/ |
202
|
19 |
|
protected function resolveQueryConstraint(string $identifier) |
203
|
|
|
{ |
204
|
19 |
|
if (! method_exists($this, $method = 'load' . ucfirst(camel_case($identifier)))) { |
205
|
18 |
|
return null; |
206
|
|
|
} |
207
|
|
|
|
208
|
1 |
|
return function ($query) use ($method) { |
209
|
1 |
|
return $this->$method($query); |
210
|
1 |
|
}; |
211
|
|
|
} |
212
|
|
|
|
213
|
|
|
/** |
214
|
|
|
* Resolve a relation from a model instance and an identifier. |
215
|
|
|
* |
216
|
|
|
* @param \Illuminate\Database\Eloquent\Model $model |
217
|
|
|
* @param string $identifier |
218
|
|
|
* @return mixed |
219
|
|
|
*/ |
220
|
18 |
|
protected function resolveRelation(Model $model, string $identifier) |
221
|
|
|
{ |
222
|
18 |
|
$identifier = camel_case($identifier); |
223
|
18 |
|
$relation = $model->$identifier; |
224
|
|
|
|
225
|
18 |
|
if (method_exists($this, $method = 'filter' . ucfirst($identifier))) { |
226
|
1 |
|
return $this->$method($relation); |
227
|
|
|
} |
228
|
|
|
|
229
|
17 |
|
return $relation; |
230
|
|
|
} |
231
|
|
|
|
232
|
|
|
/** |
233
|
|
|
* Resolve a list of transformers from a list of relations mapped to transformers. |
234
|
|
|
* |
235
|
|
|
* @param array $relations |
236
|
|
|
* @return array |
237
|
|
|
*/ |
238
|
|
|
protected function mappedTransformers(array $relations): array |
239
|
|
|
{ |
240
|
19 |
|
return collect($this->availableRelations())->filter(function ($transformer) { |
241
|
19 |
|
return ! is_null($transformer); |
242
|
|
|
})->map(function ($transformer) { |
243
|
12 |
|
return $this->resolveTransformer($transformer); |
244
|
41 |
|
})->intersectKey($relations)->all(); |
245
|
|
|
} |
246
|
|
|
|
247
|
|
|
/** |
248
|
|
|
* Get a related transformer class mapped to a relation identifier. |
249
|
|
|
* |
250
|
|
|
* @param string $identifier |
251
|
|
|
* @return string|null |
252
|
|
|
*/ |
253
|
22 |
|
protected function mappedTransformerClass(string $identifier) |
254
|
|
|
{ |
255
|
22 |
|
return $this->availableRelations()[$identifier] ?? null; |
256
|
|
|
} |
257
|
|
|
|
258
|
|
|
/** |
259
|
|
|
* Resolve a transformer from a class name string. |
260
|
|
|
* |
261
|
|
|
* @param string $transformer |
262
|
|
|
* @return mixed |
263
|
|
|
*/ |
264
|
|
|
protected abstract function resolveTransformer(string $transformer); |
265
|
|
|
} |
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.