BelongsToJson::addEagerConstraints()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 1
dl 0
loc 9
rs 10
c 0
b 0
f 0
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
The trait Staudenmeir\EloquentJson...leBelongsToJsonRelation requires some properties which are not provided by Staudenmeir\EloquentJson...Relations\BelongsToJson: $laravel_through_key, $wheres, $connection
Loading history...
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
introduced by
The condition is_array($foreignKey) is always false.
Loading history...
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
Bug introduced by
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 ignore-type  annotation

224
        [$sql, $bindings] = $this->relationExistenceQueryOwnerKey($query, /** @scrutinizer ignore-type */ $ownerKey);
Loading history...
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
Bug Best Practice introduced by
The expression return $query->select($columns) also could return the type Illuminate\Database\Query\Builder which is incompatible with the documented return type Illuminate\Database\Eloquent\Builder.
Loading history...
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
Bug introduced by
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 ignore-type  annotation

255
        [$sql, $bindings] = $this->relationExistenceQueryOwnerKey($query, $hash.'.'./** @scrutinizer ignore-type */ $this->ownerKey);
Loading history...
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
Bug Best Practice introduced by
The expression return $query->select($columns) also could return the type Illuminate\Database\Query\Builder which is incompatible with the documented return type Illuminate\Database\Eloquent\Builder.
Loading history...
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
Unused Code introduced by
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 ignore-unused  annotation

311
    public function pivotAttributes(Model $model, /** @scrutinizer ignore-unused */ Model $parent, array $records)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
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
Bug introduced by
$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 ignore-type  annotation

317
        $record = (new BaseCollection(/** @scrutinizer ignore-type */ $records))
Loading history...
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
Bug Best Practice introduced by
The expression return $this->ownerKey also could return the type array which is incompatible with the documented return type string.
Loading history...
348
    }
349
}
350