HasManyJson::addConstraints()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 14
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 3
eloc 9
nc 3
nop 0
dl 0
loc 14
rs 9.9666
c 2
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\HasMany;
9
use Illuminate\Support\Arr;
10
use Illuminate\Support\Collection as BaseCollection;
11
use Staudenmeir\EloquentHasManyDeepContracts\Interfaces\ConcatenableRelation;
12
use Staudenmeir\EloquentJsonRelations\Relations\Traits\Concatenation\IsConcatenableHasManyJsonRelation;
13
use Staudenmeir\EloquentJsonRelations\Relations\Traits\CompositeKeys\SupportsHasManyJsonCompositeKeys;
14
use Staudenmeir\EloquentJsonRelations\Relations\Traits\IsJsonRelation;
15
16
class HasManyJson extends HasMany implements ConcatenableRelation
17
{
18
    use IsConcatenableHasManyJsonRelation;
0 ignored issues
show
introduced by
The trait Staudenmeir\EloquentJson...ableHasManyJsonRelation requires some properties which are not provided by Staudenmeir\EloquentJson...s\Relations\HasManyJson: $grammar, $laravel_through_key, $wheres, $connection
Loading history...
19
    use IsJsonRelation;
20
    use SupportsHasManyJsonCompositeKeys;
21
22
    /**
23
     * The foreign key of the parent model.
24
     *
25
     * @var string|array
26
     */
27
    protected $foreignKey;
28
29
    /**
30
     * The local key of the parent model.
31
     *
32
     * @var string|array
33
     */
34
    protected $localKey;
35
36
    /**
37
     * Create a new has many JSON relationship instance.
38
     *
39
     * @param \Illuminate\Database\Eloquent\Builder $query
40
     * @param \Illuminate\Database\Eloquent\Model $parent
41
     * @param string $foreignKey
42
     * @param string $localKey
43
     * @return void
44
     */
45
    public function __construct(Builder $query, Model $parent, $foreignKey, $localKey)
46
    {
47
        $segments = is_array($foreignKey)
0 ignored issues
show
introduced by
The condition is_array($foreignKey) is always false.
Loading history...
48
            ? explode('[]->', $foreignKey[0])
49
            : explode('[]->', $foreignKey);
50
51
        $this->path = $segments[0];
52
        $this->key = $segments[1] ?? null;
53
54
        parent::__construct($query, $parent, $foreignKey, $localKey);
55
    }
56
57
    /**
58
     * Get the results of the relationship.
59
     *
60
     * @return mixed
61
     */
62
    public function getResults()
63
    {
64
        return !is_null($this->getParentKey())
65
            ? $this->get()
66
            : $this->related->newCollection();
67
    }
68
69
    /**
70
     * Execute the query as a "select" statement.
71
     *
72
     * @param array $columns
73
     * @return \Illuminate\Database\Eloquent\Collection
74
     */
75
    public function get($columns = ['*'])
76
    {
77
        $models = parent::get($columns);
78
79
        $localKey = $this->hasCompositeKey() ? $this->localKey[0] : $this->localKey;
80
81
        if ($this->key && !is_null($this->parent->$localKey)) {
82
            $this->hydratePivotRelation(
83
                $models,
84
                $this->parent,
85
                fn (Model $model) => $model->{$this->getPathName()}
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
            $parentKey = $this->getParentKey();
101
102
            $this->whereJsonContainsOrMemberOf(
103
                $this->query,
104
                $this->path,
105
                $parentKey,
106
                fn ($parentKey) => $this->parentKeyToArray($parentKey)
107
            );
108
109
            if ($this->hasCompositeKey()) {
110
                $this->addConstraintsWithCompositeKey();
111
            }
112
        }
113
    }
114
115
    /**
116
     * Set the constraints for an eager load of the relation.
117
     *
118
     * @param array $models
119
     * @return void
120
     */
121
    public function addEagerConstraints(array $models)
122
    {
123
        if ($this->hasCompositeKey()) {
124
            $this->addEagerConstraintsWithCompositeKey($models);
125
126
            return;
127
        }
128
129
        $parentKeys = $this->getKeys($models, $this->localKey);
0 ignored issues
show
Bug introduced by
It seems like $this->localKey can also be of type array; however, parameter $key of Illuminate\Database\Eloq...ons\Relation::getKeys() does only seem to accept null|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

129
        $parentKeys = $this->getKeys($models, /** @scrutinizer ignore-type */ $this->localKey);
Loading history...
130
131
        $this->query->where(function (Builder $query) use ($parentKeys) {
132
            foreach ($parentKeys as $parentKey) {
133
                $this->whereJsonContainsOrMemberOf(
134
                    $query,
135
                    $this->path,
136
                    $parentKey,
137
                    fn ($parentKey) => $this->parentKeyToArray($parentKey),
138
                    'or'
139
                );
140
            }
141
        });
142
    }
143
144
    /**
145
     * Embed a parent key in a nested array.
146
     *
147
     * @param mixed $parentKey
148
     * @return array
149
     */
150
    protected function parentKeyToArray($parentKey)
151
    {
152
        $keys = explode('->', $this->key);
153
154
        foreach (array_reverse($keys) as $key) {
155
            $parentKey = [$key => $parentKey];
156
        }
157
158
        return [$parentKey];
159
    }
160
161
    /**
162
     * Match the eagerly loaded results to their many parents.
163
     *
164
     * @param array $models
165
     * @param \Illuminate\Database\Eloquent\Collection $results
166
     * @param string $relation
167
     * @param string $type
168
     * @return array
169
     */
170
    protected function matchOneOrMany(array $models, Collection $results, $relation, $type)
171
    {
172
        if ($this->hasCompositeKey()) {
173
            $this->matchWithCompositeKey($models, $results, $relation, 'many');
174
        } else {
175
            parent::matchOneOrMany($models, $results, $relation, $type);
176
        }
177
178
        if ($this->key) {
179
            foreach ($models as $model) {
180
                $this->hydratePivotRelation(
181
                    $model->$relation,
182
                    $model,
183
                    fn (Model $model) => $model->{$this->getPathName()}
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
        $foreign = $this->getForeignKeyName();
200
201
        $dictionary = [];
202
203
        foreach ($results as $result) {
204
            foreach ($result->{$foreign} as $value) {
205
                $dictionary[$value][] = $result;
206
            }
207
        }
208
209
        return $dictionary;
210
    }
211
212
    /**
213
     * Set the foreign ID for creating a related model.
214
     *
215
     * @param \Illuminate\Database\Eloquent\Model $model
216
     * @return void
217
     */
218
    protected function setForeignAttributesForCreate(Model $model)
219
    {
220
        $foreignKey = explode('.', $this->foreignKey)[1];
0 ignored issues
show
Bug introduced by
It seems like $this->foreignKey can also be of type array; however, parameter $string of explode() 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

220
        $foreignKey = explode('.', /** @scrutinizer ignore-type */ $this->foreignKey)[1];
Loading history...
221
222
        /** @var \Staudenmeir\EloquentJsonRelations\Relations\BelongsToJson $relation */
223
        $relation = $model->belongsToJson(get_class($this->parent), $foreignKey, $this->localKey);
224
225
        $relation->attach($this->getParentKey());
226
    }
227
228
    /**
229
     * Add the constraints for a relationship query.
230
     *
231
     * @param \Illuminate\Database\Eloquent\Builder $query
232
     * @param \Illuminate\Database\Eloquent\Builder $parentQuery
233
     * @param array|mixed $columns
234
     * @return \Illuminate\Database\Eloquent\Builder
235
     */
236
    public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*'])
237
    {
238
        if ($query->getQuery()->from == $parentQuery->getQuery()->from) {
239
            return $this->getRelationExistenceQueryForSelfRelation($query, $parentQuery, $columns);
240
        }
241
242
        [$sql, $bindings] = $this->relationExistenceQueryParentKey($query);
243
244
        $query->addBinding($bindings);
245
246
        $this->whereJsonContainsOrMemberOf(
247
            $query,
248
            $this->getQualifiedPath(),
249
            $query->getQuery()->connection->raw($sql)
250
        );
251
252
        if ($this->hasCompositeKey()) {
253
            $this->getRelationExistenceQueryWithCompositeKey($query);
254
        }
255
256
        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...
257
    }
258
259
    /**
260
     * Add the constraints for a relationship query on the same table.
261
     *
262
     * @param \Illuminate\Database\Eloquent\Builder $query
263
     * @param \Illuminate\Database\Eloquent\Builder $parentQuery
264
     * @param array|mixed $columns
265
     * @return \Illuminate\Database\Eloquent\Builder
266
     */
267
    public function getRelationExistenceQueryForSelfRelation(Builder $query, Builder $parentQuery, $columns = ['*'])
268
    {
269
        $query->from($query->getModel()->getTable() . ' as ' . $hash = $this->getRelationCountHash());
270
271
        $query->getModel()->setTable($hash);
272
273
        [$sql, $bindings] = $this->relationExistenceQueryParentKey($query);
274
275
        $query->addBinding($bindings);
276
277
        $this->whereJsonContainsOrMemberOf(
278
            $query,
279
            $hash . '.' . $this->getPathName(),
280
            $query->getQuery()->connection->raw($sql)
281
        );
282
283
        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...
284
    }
285
286
    /**
287
     * Get the parent key for the relationship query.
288
     *
289
     * @param \Illuminate\Database\Eloquent\Builder $query
290
     * @return array
291
     */
292
    protected function relationExistenceQueryParentKey(Builder $query): array
293
    {
294
        $parentKey = $this->getQualifiedParentKeyName();
295
296
        $grammar = $this->getJsonGrammar($query);
297
        $connection = $query->getConnection();
298
299
        if ($grammar->supportsMemberOf($connection)) {
300
            $sql = $grammar->wrap($parentKey);
301
302
            $bindings = [];
303
        } else {
304
            if ($this->key) {
305
                $keys = explode('->', $this->key);
306
307
                $sql = $this->getJsonGrammar($query)->compileJsonObject($parentKey, count($keys));
308
309
                $bindings = $keys;
310
            } else {
311
                $sql = $this->getJsonGrammar($query)->compileJsonArray($parentKey);
312
313
                $bindings = [];
314
            }
315
        }
316
317
        return [$sql, $bindings];
318
    }
319
320
    /**
321
     * Get the pivot attributes from a model.
322
     *
323
     * @param \Illuminate\Database\Eloquent\Model $model
324
     * @param \Illuminate\Database\Eloquent\Model $parent
325
     * @param array $records
326
     * @return array
327
     */
328
    public function pivotAttributes(Model $model, Model $parent, array $records)
0 ignored issues
show
Unused Code introduced by
The parameter $model 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

328
    public function pivotAttributes(/** @scrutinizer ignore-unused */ Model $model, 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...
329
    {
330
        $key = str_replace('->', '.', $this->key);
331
332
        $localKey = $this->hasCompositeKey() ? $this->localKey[0] : $this->localKey;
333
334
        $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

334
        $record = (new BaseCollection(/** @scrutinizer ignore-type */ $records))
Loading history...
335
            ->filter(function ($value) use ($key, $localKey, $parent) {
336
                return Arr::get($value, $key) == $parent->$localKey;
337
            })->first();
338
339
        return Arr::except($record, $key);
340
    }
341
342
    /**
343
     * Get the plain path name.
344
     *
345
     * @return string
346
     */
347
    public function getPathName()
348
    {
349
        return last(explode('.', $this->path));
350
    }
351
352
    /**
353
     * Get the key value of the parent's local key.
354
     *
355
     * @return mixed
356
     */
357
    public function getParentKey()
358
    {
359
        $localKey = $this->hasCompositeKey() ? $this->localKey[0] : $this->localKey;
360
361
        return $this->parent->getAttribute($localKey);
362
    }
363
364
    /**
365
     * Get the fully qualified parent key name.
366
     *
367
     * @return string
368
     */
369
    public function getQualifiedParentKeyName()
370
    {
371
        $localKey = $this->hasCompositeKey() ? $this->localKey[0] : $this->localKey;
372
373
        return $this->parent->qualifyColumn($localKey);
374
    }
375
376
    /**
377
     * Get the foreign key for the relationship.
378
     *
379
     * @return string
380
     */
381
    public function getQualifiedForeignKeyName()
382
    {
383
        return $this->hasCompositeKey() ? $this->foreignKey[0] : $this->foreignKey;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->hasComposi...[0] : $this->foreignKey also could return the type array which is incompatible with the documented return type string.
Loading history...
384
    }
385
}
386