1 | <?php |
||||
2 | |||||
3 | namespace Staudenmeir\EloquentJsonRelations\Relations; |
||||
4 | |||||
5 | use Illuminate\Database\Eloquent\Builder; |
||||
6 | use Illuminate\Database\Eloquent\Collection; |
||||
7 | use Illuminate\Database\Eloquent\Model; |
||||
8 | use Illuminate\Database\Eloquent\Relations\BelongsTo; |
||||
9 | use Illuminate\Support\Arr; |
||||
10 | use Illuminate\Support\Collection as BaseCollection; |
||||
11 | use Staudenmeir\EloquentHasManyDeepContracts\Interfaces\ConcatenableRelation; |
||||
12 | use Staudenmeir\EloquentJsonRelations\Relations\Traits\CompositeKeys\SupportsBelongsToJsonCompositeKeys; |
||||
13 | use Staudenmeir\EloquentJsonRelations\Relations\Traits\Concatenation\IsConcatenableBelongsToJsonRelation; |
||||
14 | use Staudenmeir\EloquentJsonRelations\Relations\Traits\IsJsonRelation; |
||||
15 | |||||
16 | class BelongsToJson extends BelongsTo implements ConcatenableRelation |
||||
17 | { |
||||
18 | use InteractsWithPivotRecords; |
||||
19 | use IsConcatenableBelongsToJsonRelation; |
||||
0 ignored issues
–
show
introduced
by
![]() |
|||||
20 | use IsJsonRelation; |
||||
21 | use SupportsBelongsToJsonCompositeKeys; |
||||
22 | |||||
23 | /** |
||||
24 | * The foreign key of the parent model. |
||||
25 | * |
||||
26 | * @var string|array |
||||
27 | */ |
||||
28 | protected $foreignKey; |
||||
29 | |||||
30 | /** |
||||
31 | * The associated key on the parent model. |
||||
32 | * |
||||
33 | * @var string|array |
||||
34 | */ |
||||
35 | protected $ownerKey; |
||||
36 | |||||
37 | /** |
||||
38 | * Create a new belongs to JSON relationship instance. |
||||
39 | * |
||||
40 | * @param \Illuminate\Database\Eloquent\Builder $query |
||||
41 | * @param \Illuminate\Database\Eloquent\Model $child |
||||
42 | * @param string $foreignKey |
||||
43 | * @param string $ownerKey |
||||
44 | * @param string $relationName |
||||
45 | * @return void |
||||
46 | */ |
||||
47 | public function __construct(Builder $query, Model $child, $foreignKey, $ownerKey, $relationName) |
||||
48 | { |
||||
49 | $segments = is_array($foreignKey) |
||||
0 ignored issues
–
show
|
|||||
50 | ? explode('[]->', $foreignKey[0]) |
||||
51 | : explode('[]->', $foreignKey); |
||||
52 | |||||
53 | $this->path = $segments[0]; |
||||
54 | $this->key = $segments[1] ?? null; |
||||
55 | |||||
56 | parent::__construct($query, $child, $foreignKey, $ownerKey, $relationName); |
||||
57 | } |
||||
58 | |||||
59 | /** |
||||
60 | * Get the results of the relationship. |
||||
61 | * |
||||
62 | * @return mixed |
||||
63 | */ |
||||
64 | public function getResults() |
||||
65 | { |
||||
66 | return !empty($this->getForeignKeys()) |
||||
67 | ? $this->get() |
||||
68 | : $this->related->newCollection(); |
||||
69 | } |
||||
70 | |||||
71 | /** |
||||
72 | * Execute the query as a "select" statement. |
||||
73 | * |
||||
74 | * @param array $columns |
||||
75 | * @return \Illuminate\Database\Eloquent\Collection |
||||
76 | */ |
||||
77 | public function get($columns = ['*']) |
||||
78 | { |
||||
79 | $models = parent::get($columns); |
||||
80 | |||||
81 | if ($this->key && !empty($this->parent->{$this->path})) { |
||||
82 | $this->hydratePivotRelation( |
||||
83 | $models, |
||||
84 | $this->parent, |
||||
85 | fn (Model $model, Model $parent) => $parent->{$this->path} |
||||
86 | ); |
||||
87 | } |
||||
88 | |||||
89 | return $models; |
||||
90 | } |
||||
91 | |||||
92 | /** |
||||
93 | * Set the base constraints on the relation query. |
||||
94 | * |
||||
95 | * @return void |
||||
96 | */ |
||||
97 | public function addConstraints() |
||||
98 | { |
||||
99 | if (static::$constraints) { |
||||
100 | $table = $this->related->getTable(); |
||||
101 | |||||
102 | $ownerKey = $this->hasCompositeKey() ? $this->ownerKey[0] : $this->ownerKey; |
||||
103 | |||||
104 | $this->query->whereIn("$table.$ownerKey", $this->getForeignKeys()); |
||||
105 | |||||
106 | if ($this->hasCompositeKey()) { |
||||
107 | $this->addConstraintsWithCompositeKey(); |
||||
108 | } |
||||
109 | } |
||||
110 | } |
||||
111 | |||||
112 | /** |
||||
113 | * Set the constraints for an eager load of the relation. |
||||
114 | * |
||||
115 | * @param array $models |
||||
116 | * @return void |
||||
117 | */ |
||||
118 | public function addEagerConstraints(array $models) |
||||
119 | { |
||||
120 | if ($this->hasCompositeKey()) { |
||||
121 | $this->addEagerConstraintsWithCompositeKey($models); |
||||
122 | |||||
123 | return; |
||||
124 | } |
||||
125 | |||||
126 | parent::addEagerConstraints($models); |
||||
127 | } |
||||
128 | |||||
129 | /** |
||||
130 | * Gather the keys from an array of related models. |
||||
131 | * |
||||
132 | * @param array $models |
||||
133 | * @return array |
||||
134 | */ |
||||
135 | protected function getEagerModelKeys(array $models) |
||||
136 | { |
||||
137 | $keys = []; |
||||
138 | |||||
139 | foreach ($models as $model) { |
||||
140 | $keys = array_merge($keys, $this->getForeignKeys($model)); |
||||
141 | } |
||||
142 | |||||
143 | sort($keys); |
||||
144 | |||||
145 | return array_values(array_unique($keys)); |
||||
146 | } |
||||
147 | |||||
148 | /** |
||||
149 | * Match the eagerly loaded results to their parents. |
||||
150 | * |
||||
151 | * @param array $models |
||||
152 | * @param \Illuminate\Database\Eloquent\Collection $results |
||||
153 | * @param string $relation |
||||
154 | * @return array |
||||
155 | */ |
||||
156 | public function match(array $models, Collection $results, $relation) |
||||
157 | { |
||||
158 | if ($this->hasCompositeKey()) { |
||||
159 | $this->matchWithCompositeKey($models, $results, $relation); |
||||
160 | } else { |
||||
161 | $dictionary = $this->buildDictionary($results); |
||||
162 | |||||
163 | foreach ($models as $model) { |
||||
164 | $matches = []; |
||||
165 | |||||
166 | foreach ($this->getForeignKeys($model) as $id) { |
||||
167 | if (isset($dictionary[$id])) { |
||||
168 | $matches[] = $dictionary[$id]; |
||||
169 | } |
||||
170 | } |
||||
171 | |||||
172 | $collection = $this->related->newCollection($matches); |
||||
173 | |||||
174 | $model->setRelation($relation, $collection); |
||||
175 | } |
||||
176 | } |
||||
177 | |||||
178 | foreach ($models as $model) { |
||||
179 | if ($this->key) { |
||||
180 | $this->hydratePivotRelation( |
||||
181 | $model->getRelation($relation), |
||||
182 | $model, |
||||
183 | fn (Model $model, Model $parent) => $parent->{$this->path} |
||||
184 | ); |
||||
185 | } |
||||
186 | } |
||||
187 | |||||
188 | return $models; |
||||
189 | } |
||||
190 | |||||
191 | /** |
||||
192 | * Build model dictionary keyed by the relation's foreign key. |
||||
193 | * |
||||
194 | * @param \Illuminate\Database\Eloquent\Collection $results |
||||
195 | * @return array |
||||
196 | */ |
||||
197 | protected function buildDictionary(Collection $results) |
||||
198 | { |
||||
199 | $dictionary = []; |
||||
200 | |||||
201 | foreach ($results as $result) { |
||||
202 | $dictionary[$result->{$this->ownerKey}] = $result; |
||||
203 | } |
||||
204 | |||||
205 | return $dictionary; |
||||
206 | } |
||||
207 | |||||
208 | /** |
||||
209 | * Add the constraints for a relationship query. |
||||
210 | * |
||||
211 | * @param \Illuminate\Database\Eloquent\Builder $query |
||||
212 | * @param \Illuminate\Database\Eloquent\Builder $parentQuery |
||||
213 | * @param array|mixed $columns |
||||
214 | * @return \Illuminate\Database\Eloquent\Builder |
||||
215 | */ |
||||
216 | public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*']) |
||||
217 | { |
||||
218 | if ($parentQuery->getQuery()->from == $query->getQuery()->from) { |
||||
219 | return $this->getRelationExistenceQueryForSelfRelation($query, $parentQuery, $columns); |
||||
220 | } |
||||
221 | |||||
222 | $ownerKey = $this->hasCompositeKey() ? $this->ownerKey[0] : $this->ownerKey; |
||||
223 | |||||
224 | [$sql, $bindings] = $this->relationExistenceQueryOwnerKey($query, $ownerKey); |
||||
0 ignored issues
–
show
It seems like
$ownerKey can also be of type array ; however, parameter $ownerKey of Staudenmeir\EloquentJson...xistenceQueryOwnerKey() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
225 | |||||
226 | $query->addBinding($bindings); |
||||
227 | |||||
228 | $this->whereJsonContainsOrMemberOf( |
||||
229 | $query, |
||||
230 | $this->getQualifiedPath(), |
||||
231 | $query->getQuery()->connection->raw($sql) |
||||
232 | ); |
||||
233 | |||||
234 | if ($this->hasCompositeKey()) { |
||||
235 | $this->getRelationExistenceQueryWithCompositeKey($query); |
||||
236 | } |
||||
237 | |||||
238 | return $query->select($columns); |
||||
0 ignored issues
–
show
|
|||||
239 | } |
||||
240 | |||||
241 | /** |
||||
242 | * Add the constraints for a relationship query on the same table. |
||||
243 | * |
||||
244 | * @param \Illuminate\Database\Eloquent\Builder $query |
||||
245 | * @param \Illuminate\Database\Eloquent\Builder $parentQuery |
||||
246 | * @param array|mixed $columns |
||||
247 | * @return \Illuminate\Database\Eloquent\Builder |
||||
248 | */ |
||||
249 | public function getRelationExistenceQueryForSelfRelation(Builder $query, Builder $parentQuery, $columns = ['*']) |
||||
250 | { |
||||
251 | $query->from($query->getModel()->getTable().' as '.$hash = $this->getRelationCountHash()); |
||||
252 | |||||
253 | $query->getModel()->setTable($hash); |
||||
254 | |||||
255 | [$sql, $bindings] = $this->relationExistenceQueryOwnerKey($query, $hash.'.'.$this->ownerKey); |
||||
0 ignored issues
–
show
Are you sure
$this->ownerKey of type array|string can be used in concatenation ?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
256 | |||||
257 | $query->addBinding($bindings); |
||||
258 | |||||
259 | $this->whereJsonContainsOrMemberOf( |
||||
260 | $query, |
||||
261 | $this->getQualifiedPath(), |
||||
262 | $query->getQuery()->connection->raw($sql) |
||||
263 | ); |
||||
264 | |||||
265 | return $query->select($columns); |
||||
0 ignored issues
–
show
|
|||||
266 | } |
||||
267 | |||||
268 | /** |
||||
269 | * Get the owner key for the relationship query. |
||||
270 | * |
||||
271 | * @param \Illuminate\Database\Eloquent\Builder $query |
||||
272 | * @param string $ownerKey |
||||
273 | * @return array |
||||
274 | */ |
||||
275 | protected function relationExistenceQueryOwnerKey(Builder $query, string $ownerKey): array |
||||
276 | { |
||||
277 | $ownerKey = $query->qualifyColumn($ownerKey); |
||||
278 | |||||
279 | $grammar = $this->getJsonGrammar($query); |
||||
280 | $connection = $query->getConnection(); |
||||
281 | |||||
282 | if ($grammar->supportsMemberOf($connection)) { |
||||
283 | $sql = $grammar->wrap($ownerKey); |
||||
284 | |||||
285 | $bindings = []; |
||||
286 | } else { |
||||
287 | if ($this->key) { |
||||
288 | $keys = explode('->', $this->key); |
||||
289 | |||||
290 | $sql = $this->getJsonGrammar($query)->compileJsonObject($ownerKey, count($keys)); |
||||
291 | |||||
292 | $bindings = $keys; |
||||
293 | } else { |
||||
294 | $sql = $this->getJsonGrammar($query)->compileJsonArray($ownerKey); |
||||
295 | |||||
296 | $bindings = []; |
||||
297 | } |
||||
298 | } |
||||
299 | |||||
300 | return [$sql, $bindings]; |
||||
301 | } |
||||
302 | |||||
303 | /** |
||||
304 | * Get the pivot attributes from a model. |
||||
305 | * |
||||
306 | * @param \Illuminate\Database\Eloquent\Model $model |
||||
307 | * @param \Illuminate\Database\Eloquent\Model $parent |
||||
308 | * @param array $records |
||||
309 | * @return array |
||||
310 | */ |
||||
311 | public function pivotAttributes(Model $model, Model $parent, array $records) |
||||
0 ignored issues
–
show
The parameter
$parent is not used and could be removed.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for parameters that have been defined for a function or method, but which are not used in the method body. ![]() |
|||||
312 | { |
||||
313 | $key = str_replace('->', '.', $this->key); |
||||
314 | |||||
315 | $ownerKey = $this->hasCompositeKey() ? $this->ownerKey[0] : $this->ownerKey; |
||||
316 | |||||
317 | $record = (new BaseCollection($records)) |
||||
0 ignored issues
–
show
$records of type array is incompatible with the type Illuminate\Contracts\Support\Arrayable expected by parameter $items of Illuminate\Support\Collection::__construct() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
318 | ->filter(function ($value) use ($key, $model, $ownerKey) { |
||||
319 | return Arr::get($value, $key) == $model->$ownerKey; |
||||
320 | })->first(); |
||||
321 | |||||
322 | return Arr::except($record, $key); |
||||
323 | } |
||||
324 | |||||
325 | /** |
||||
326 | * Get the foreign key values. |
||||
327 | * |
||||
328 | * @param \Illuminate\Database\Eloquent\Model|null $model |
||||
329 | * @return array |
||||
330 | */ |
||||
331 | public function getForeignKeys(?Model $model = null) |
||||
332 | { |
||||
333 | $model = $model ?: $this->child; |
||||
334 | |||||
335 | $foreignKey = $this->hasCompositeKey() ? $this->foreignKey[0] : $this->foreignKey; |
||||
336 | |||||
337 | return (new BaseCollection($model->$foreignKey))->filter(fn ($key) => $key !== null)->all(); |
||||
338 | } |
||||
339 | |||||
340 | /** |
||||
341 | * Get the related key for the relationship. |
||||
342 | * |
||||
343 | * @return string |
||||
344 | */ |
||||
345 | public function getRelatedKeyName() |
||||
346 | { |
||||
347 | return $this->ownerKey; |
||||
0 ignored issues
–
show
|
|||||
348 | } |
||||
349 | } |
||||
350 |