Passed
Push — master ( 3263b5...c3fb84 )
by Jonas
15:03 queued 18s
created

BelongsToJson::addEagerConstraints()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 4
c 0
b 0
f 0
nc 2
nop 1
dl 0
loc 9
rs 10
ccs 1
cts 1
cp 1
crap 2
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 72
     * @var string|array
27
     */
28 72
    protected $foreignKey;
29 68
30 72
    /**
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 96
     *
40
     * @param \Illuminate\Database\Eloquent\Builder $query
41 96
     * @param \Illuminate\Database\Eloquent\Model $child
42
     * @param string $foreignKey
43 96
     * @param string $ownerKey
44 56
     * @param string $relationName
45 56
     * @return void
46 56
     */
47 56
    public function __construct(Builder $query, Model $child, $foreignKey, $ownerKey, $relationName)
48 56
    {
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 96
            : 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 284
    /**
60
     * Get the results of the relationship.
61 284
     *
62 170
     * @return mixed
63
     */
64 170
    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 68
     * @param array $columns
75
     * @return \Illuminate\Database\Eloquent\Collection
76 68
     */
77
    public function get($columns = ['*'])
78 68
    {
79 68
        $models = parent::get($columns);
80
81
        if ($this->key && !empty($this->parent->{$this->path})) {
82 68
            $this->hydratePivotRelation(
83
                $models,
84 68
                $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 48
     * @return void
96
     */
97 48
    public function addConstraints()
98
    {
99 48
        if (static::$constraints) {
100 48
            $table = $this->related->getTable();
101
102 48
            $ownerKey = $this->hasCompositeKey() ? $this->ownerKey[0] : $this->ownerKey;
103 48
104 48
            $this->query->whereIn("$table.$ownerKey", $this->getForeignKeys());
105
106
            if ($this->hasCompositeKey()) {
107
                $this->addConstraintsWithCompositeKey();
108 48
            }
109
        }
110 48
    }
111 32
112 32
    /**
113 32
     * Set the constraints for an eager load of the relation.
114 32
     *
115 32
     * @param array $models
116
     * @return void
117
     */
118
    public function addEagerConstraints(array $models)
119 48
    {
120
        if ($this->hasCompositeKey()) {
121
            $this->addEagerConstraintsWithCompositeKey($models);
122
123
            return;
124
        }
125
126
        parent::addEagerConstraints($models);
127
    }
128 48
129
    /**
130 48
     * Gather the keys from an array of related models.
131
     *
132 48
     * @param array $models
133 48
     * @return array
134
     */
135
    protected function getEagerModelKeys(array $models)
136 48
    {
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 28
148
    /**
149 28
     * Match the eagerly loaded results to their parents.
150 7
     *
151
     * @param array  $models
152
     * @param \Illuminate\Database\Eloquent\Collection $results
153 21
     * @param string $relation
154
     * @return array
155 21
     */
156
    public function match(array $models, Collection $results, $relation)
157 21
    {
158 21
        if ($this->hasCompositeKey()) {
159 21
            $this->matchWithCompositeKey($models, $results, $relation);
160 21
        } else {
161 21
            $dictionary = $this->buildDictionary($results);
162
163 21
            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 7
                $model->setRelation($relation, $collection);
175
            }
176 7
        }
177
178 7
        foreach ($models as $model) {
179
            if ($this->key) {
180 7
                $this->hydratePivotRelation(
181
                    $model->getRelation($relation),
182 7
                    $model,
183
                    fn (Model $model, Model $parent) => $parent->{$this->path}
184 7
                );
185 7
            }
186 7
        }
187 7
188 7
        return $models;
189
    }
190 7
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 95
201
        foreach ($results as $result) {
202 95
            $dictionary[$result->{$this->ownerKey}] = $result;
203
        }
204 95
205 95
        return $dictionary;
206
    }
207 95
208 27
    /**
209
     * Add the constraints for a relationship query.
210 27
     *
211
     * @param \Illuminate\Database\Eloquent\Builder $query
212 68
     * @param \Illuminate\Database\Eloquent\Builder $parentQuery
213 26
     * @param array|mixed $columns
214
     * @return \Illuminate\Database\Eloquent\Builder
215 26
     */
216
    public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*'])
217 26
    {
218
        if ($parentQuery->getQuery()->from == $query->getQuery()->from) {
219 42
            return $this->getRelationExistenceQueryForSelfRelation($query, $parentQuery, $columns);
220
        }
221 42
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 95
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 78
        }
237
238 78
        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 78
241 78
    /**
242 78
     * Add the constraints for a relationship query on the same table.
243 78
     *
244
     * @param \Illuminate\Database\Eloquent\Builder $query
245 78
     * @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 214
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 214
257
        $query->addBinding($bindings);
258 214
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 4
    }
267
268 4
    /**
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