Passed
Push — master ( af341e...2b2ec9 )
by Jonas
07:18 queued 05:16
created

addHasManyThroughJsonPivotRelationship()   A

Complexity

Conditions 5
Paths 3

Size

Total Lines 55
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 24
CRAP Score 5

Importance

Changes 0
Metric Value
cc 5
eloc 30
c 0
b 0
f 0
nc 3
nop 3
dl 0
loc 55
ccs 24
cts 24
cp 1
crap 5
rs 9.1288

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Staudenmeir\EloquentJsonRelations;
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\Database\Eloquent\Relations\HasMany;
10
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
11
use Illuminate\Database\Eloquent\Relations\HasOne;
12
use Illuminate\Database\Eloquent\Relations\HasOneThrough;
13
use Illuminate\Database\Eloquent\Relations\MorphMany;
14
use Illuminate\Database\Eloquent\Relations\MorphOne;
15
use RuntimeException;
16
use Staudenmeir\EloquentJsonRelations\Relations\BelongsToJson;
17
use Staudenmeir\EloquentJsonRelations\Relations\HasManyJson;
18
use Staudenmeir\EloquentJsonRelations\Relations\Postgres\BelongsTo as BelongsToPostgres;
19
use Staudenmeir\EloquentJsonRelations\Relations\Postgres\HasMany as HasManyPostgres;
20
use Staudenmeir\EloquentJsonRelations\Relations\Postgres\HasManyThrough as HasManyThroughPostgres;
21
use Staudenmeir\EloquentJsonRelations\Relations\Postgres\HasOne as HasOnePostgres;
22
use Staudenmeir\EloquentJsonRelations\Relations\Postgres\HasOneThrough as HasOneThroughPostgres;
23
use Staudenmeir\EloquentJsonRelations\Relations\Postgres\MorphMany as MorphManyPostgres;
24
use Staudenmeir\EloquentJsonRelations\Relations\Postgres\MorphOne as MorphOnePostgres;
25
26
trait HasJsonRelationships
27
{
28
    /**
29
     * Get an attribute from the model.
30
     *
31
     * @param string $key
32
     * @return mixed
33
     */
34 419
    public function getAttribute($key)
35
    {
36 419
        $attribute = preg_split('/(->|\[\])/', $key)[0];
37
38 419
        if (array_key_exists($attribute, $this->attributes)) {
39 407
            return $this->getAttributeValue($key);
40
        }
41
42 320
        return parent::getAttribute($key);
43
    }
44
45
    /**
46
     * Get an attribute from the $attributes array.
47
     *
48
     * @param string $key
49
     * @return mixed
50
     */
51 407
    public function getAttributeFromArray($key)
52
    {
53 407
        if (str_contains($key, '->')) {
54 28
            return $this->getAttributeValue($key);
55
        }
56
57 407
        return parent::getAttributeFromArray($key);
58
    }
59
60
    /**
61
     * Get a plain attribute (not a relationship).
62
     *
63
     * @param string $key
64
     * @return mixed
65
     */
66 407
    public function getAttributeValue($key)
67
    {
68 407
        if (str_contains($key, '->')) {
69 183
            [$key, $path] = explode('->', $key, 2);
70
71 183
            if (substr($key, -2) === '[]') {
72 3
                $key = substr($key, 0, -2);
73
74 3
                $path = '*.'.$path;
75
            }
76
77 183
            $path = str_replace(['->', '[]'], ['.', '.*'], $path);
78
79 183
            return data_get($this->getAttributeValue($key), $path);
80
        }
81
82 407
        return parent::getAttributeValue($key);
83
    }
84
85
    /**
86
     * Instantiate a new HasOne relationship.
87
     *
88
     * @param \Illuminate\Database\Eloquent\Builder $query
89
     * @param \Illuminate\Database\Eloquent\Model $parent
90
     * @param string $foreignKey
91
     * @param string $localKey
92
     * @return \Illuminate\Database\Eloquent\Relations\HasOne
93
     */
94 24
    protected function newHasOne(Builder $query, Model $parent, $foreignKey, $localKey)
95
    {
96 24
        if ($query->getConnection()->getDriverName() === 'pgsql') {
97 6
            return new HasOnePostgres($query, $parent, $foreignKey, $localKey);
98
        }
99
100 18
        return new HasOne($query, $parent, $foreignKey, $localKey);
101
    }
102
103
    /**
104
     * Instantiate a new HasOneThrough relationship.
105
     *
106
     * @param \Illuminate\Database\Eloquent\Builder $query
107
     * @param \Illuminate\Database\Eloquent\Model $farParent
108
     * @param \Illuminate\Database\Eloquent\Model $throughParent
109
     * @param string $firstKey
110
     * @param string $secondKey
111
     * @param string $localKey
112
     * @param string $secondLocalKey
113
     * @return \Illuminate\Database\Eloquent\Relations\HasOneThrough
114
     */
115 24
    protected function newHasOneThrough(Builder $query, Model $farParent, Model $throughParent, $firstKey, $secondKey, $localKey, $secondLocalKey)
116
    {
117 24
        if ($query->getConnection()->getDriverName() === 'pgsql') {
118 6
            return new HasOneThroughPostgres($query, $farParent, $throughParent, $firstKey, $secondKey, $localKey, $secondLocalKey);
119
        }
120
121 18
        return new HasOneThrough($query, $farParent, $throughParent, $firstKey, $secondKey, $localKey, $secondLocalKey);
122
    }
123
124
    /**
125
     * Instantiate a new MorphOne relationship.
126
     *
127
     * @param \Illuminate\Database\Eloquent\Builder $query
128
     * @param \Illuminate\Database\Eloquent\Model $parent
129
     * @param string $type
130
     * @param string $id
131
     * @param string $localKey
132
     * @return \Illuminate\Database\Eloquent\Relations\MorphOne
133
     */
134 20
    protected function newMorphOne(Builder $query, Model $parent, $type, $id, $localKey)
135
    {
136 20
        if ($query->getConnection()->getDriverName() === 'pgsql') {
137 5
            return new MorphOnePostgres($query, $parent, $type, $id, $localKey);
138
        }
139
140 15
        return new MorphOne($query, $parent, $type, $id, $localKey);
141
    }
142
143
    /**
144
     * Instantiate a new BelongsTo relationship.
145
     *
146
     * @param \Illuminate\Database\Eloquent\Builder $query
147
     * @param \Illuminate\Database\Eloquent\Model $child
148
     * @param string $foreignKey
149
     * @param string $ownerKey
150
     * @param string $relation
151
     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
152
     */
153 86
    protected function newBelongsTo(Builder $query, Model $child, $foreignKey, $ownerKey, $relation)
154
    {
155 86
        if ($query->getConnection()->getDriverName() === 'pgsql') {
156 29
            return new BelongsToPostgres($query, $child, $foreignKey, $ownerKey, $relation);
157
        }
158
159 57
        return new BelongsTo($query, $child, $foreignKey, $ownerKey, $relation);
160
    }
161
162
    /**
163
     * Instantiate a new HasMany relationship.
164
     *
165
     * @param \Illuminate\Database\Eloquent\Builder $query
166
     * @param \Illuminate\Database\Eloquent\Model $parent
167
     * @param string $foreignKey
168
     * @param string $localKey
169
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
170
     */
171 98
    protected function newHasMany(Builder $query, Model $parent, $foreignKey, $localKey)
172
    {
173 98
        if ($query->getConnection()->getDriverName() === 'pgsql') {
174 34
            return new HasManyPostgres($query, $parent, $foreignKey, $localKey);
175
        }
176
177 64
        return new HasMany($query, $parent, $foreignKey, $localKey);
178
    }
179
180
    /**
181
     * Instantiate a new HasManyThrough relationship.
182
     *
183
     * @param \Illuminate\Database\Eloquent\Builder $query
184
     * @param \Illuminate\Database\Eloquent\Model $farParent
185
     * @param \Illuminate\Database\Eloquent\Model $throughParent
186
     * @param string $firstKey
187
     * @param string $secondKey
188
     * @param string $localKey
189
     * @param string $secondLocalKey
190
     * @return \Illuminate\Database\Eloquent\Relations\HasManyThrough
191
     */
192 24
    protected function newHasManyThrough(Builder $query, Model $farParent, Model $throughParent, $firstKey, $secondKey, $localKey, $secondLocalKey)
193
    {
194 24
        if ($query->getConnection()->getDriverName() === 'pgsql') {
195 6
            return new HasManyThroughPostgres($query, $farParent, $throughParent, $firstKey, $secondKey, $localKey, $secondLocalKey);
196
        }
197
198 18
        return new HasManyThrough($query, $farParent, $throughParent, $firstKey, $secondKey, $localKey, $secondLocalKey);
199
    }
200
201
    /**
202
     * Instantiate a new MorphMany relationship.
203
     *
204
     * @param \Illuminate\Database\Eloquent\Builder $query
205
     * @param \Illuminate\Database\Eloquent\Model $parent
206
     * @param string $type
207
     * @param string $id
208
     * @param string $localKey
209
     * @return \Illuminate\Database\Eloquent\Relations\MorphMany
210
     */
211 20
    protected function newMorphMany(Builder $query, Model $parent, $type, $id, $localKey)
212
    {
213 20
        if ($query->getConnection()->getDriverName() === 'pgsql') {
214 5
            return new MorphManyPostgres($query, $parent, $type, $id, $localKey);
215
        }
216
217 15
        return new MorphMany($query, $parent, $type, $id, $localKey);
218
    }
219
220
    /**
221
     * Define an inverse one-to-one or many JSON relationship.
222
     *
223
     * @param string $related
224
     * @param string $foreignKey
225
     * @param string $ownerKey
226
     * @param string $relation
227
     * @return \Staudenmeir\EloquentJsonRelations\Relations\BelongsToJson
228
     */
229 147
    public function belongsToJson($related, $foreignKey, $ownerKey = null, $relation = null)
230
    {
231 147
        if (is_null($relation)) {
232 147
            $relation = $this->guessBelongsToRelation();
0 ignored issues
show
Bug introduced by
It seems like guessBelongsToRelation() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

232
            /** @scrutinizer ignore-call */ 
233
            $relation = $this->guessBelongsToRelation();
Loading history...
233
        }
234
235
        /** @var \Illuminate\Database\Eloquent\Model $instance */
236 147
        $instance = $this->newRelatedInstance($related);
0 ignored issues
show
Bug introduced by
It seems like newRelatedInstance() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

236
        /** @scrutinizer ignore-call */ 
237
        $instance = $this->newRelatedInstance($related);
Loading history...
237
238 147
        $ownerKey = $ownerKey ?: $instance->getKeyName();
239
240 147
        return $this->newBelongsToJson(
241 147
            $instance->newQuery(),
242
            $this,
0 ignored issues
show
Bug introduced by
$this of type Staudenmeir\EloquentJson...ns\HasJsonRelationships is incompatible with the type Illuminate\Database\Eloquent\Model expected by parameter $child of Staudenmeir\EloquentJson...ips::newBelongsToJson(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

242
            /** @scrutinizer ignore-type */ $this,
Loading history...
243
            $foreignKey,
244
            $ownerKey,
245
            $relation
246
        );
247
    }
248
249
    /**
250
     * Instantiate a new BelongsToJson relationship.
251
     *
252
     * @param \Illuminate\Database\Eloquent\Builder $query
253
     * @param \Illuminate\Database\Eloquent\Model $child
254
     * @param string $foreignKey
255
     * @param string $ownerKey
256
     * @param string $relation
257
     * @return \Staudenmeir\EloquentJsonRelations\Relations\BelongsToJson
258
     */
259 147
    protected function newBelongsToJson(Builder $query, Model $child, $foreignKey, $ownerKey, $relation)
260
    {
261 147
        return new BelongsToJson($query, $child, $foreignKey, $ownerKey, $relation);
262
    }
263
264
    /**
265
     * Define a one-to-many JSON relationship.
266
     *
267
     * @param string $related
268
     * @param string $foreignKey
269
     * @param string $localKey
270
     * @return \Staudenmeir\EloquentJsonRelations\Relations\HasManyJson
271
     */
272 104
    public function hasManyJson($related, $foreignKey, $localKey = null)
273
    {
274
        /** @var \Illuminate\Database\Eloquent\Model $instance */
275 104
        $instance = $this->newRelatedInstance($related);
276
277 104
        $localKey = $localKey ?: $this->getKeyName();
0 ignored issues
show
Bug introduced by
It seems like getKeyName() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

277
        $localKey = $localKey ?: $this->/** @scrutinizer ignore-call */ getKeyName();
Loading history...
278
279 104
        return $this->newHasManyJson(
280 104
            $instance->newQuery(),
281
            $this,
0 ignored issues
show
Bug introduced by
$this of type Staudenmeir\EloquentJson...ns\HasJsonRelationships is incompatible with the type Illuminate\Database\Eloquent\Model expected by parameter $parent of Staudenmeir\EloquentJson...ships::newHasManyJson(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

281
            /** @scrutinizer ignore-type */ $this,
Loading history...
282 104
            $instance->getTable().'.'.$foreignKey,
283
            $localKey
284
        );
285
    }
286
287
    /**
288
     * Instantiate a new HasManyJson relationship.
289
     *
290
     * @param \Illuminate\Database\Eloquent\Builder $query
291
     * @param \Illuminate\Database\Eloquent\Model $parent
292
     * @param string $foreignKey
293
     * @param string $localKey
294
     * @return \Staudenmeir\EloquentJsonRelations\Relations\HasManyJson
295
     */
296 104
    protected function newHasManyJson(Builder $query, Model $parent, $foreignKey, $localKey)
297
    {
298 104
        return new HasManyJson($query, $parent, $foreignKey, $localKey);
299
    }
300
301
    /**
302
     * Define has-many-through JSON relationship.
303
     *
304
     * @param string $related
305
     * @param string $through
306
     * @param string|\Staudenmeir\EloquentJsonRelations\JsonKey $firstKey
307
     * @param string|null $secondKey
308
     * @param string|null $localKey
309
     * @param string|\Staudenmeir\EloquentJsonRelations\JsonKey|null $secondLocalKey
310
     * @return \Staudenmeir\EloquentHasManyDeep\HasManyDeep
311
     */
312 36
    public function hasManyThroughJson(
313
        string $related,
314
        string $through,
315
        string|JsonKey $firstKey,
316
        string $secondKey = null,
317
        string $localKey = null,
318
        string|JsonKey $secondLocalKey = null
319
    ) {
320 36
        $relationships = [];
321
322 36
        $through = new $through();
323
324 36
        if ($firstKey instanceof JsonKey) {
325 18
            $relationships[] = $this->hasManyJson($through, $firstKey, $localKey);
0 ignored issues
show
Bug introduced by
$through of type object is incompatible with the type string expected by parameter $related of Staudenmeir\EloquentJson...ionships::hasManyJson(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

325
            $relationships[] = $this->hasManyJson(/** @scrutinizer ignore-type */ $through, $firstKey, $localKey);
Loading history...
326
327 18
            $relationships[] = $through->hasMany($related, $secondKey, $secondLocalKey);
328
        } else {
329 18
            if (!method_exists($through, 'belongsToJson')) {
330
                //@codeCoverageIgnoreStart
331
                $message = 'Please add the HasJsonRelationships trait to the ' . $through::class . ' model.';
332
333
                throw new RuntimeException($message);
334
                // @codeCoverageIgnoreEnd
335
            }
336
337 18
            $relationships[] = $this->hasMany($through, $firstKey, $localKey);
0 ignored issues
show
Bug introduced by
The method hasMany() does not exist on Staudenmeir\EloquentJson...ns\HasJsonRelationships. Did you maybe mean hasManyThroughJson()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

337
            /** @scrutinizer ignore-call */ 
338
            $relationships[] = $this->hasMany($through, $firstKey, $localKey);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
338
339 18
            $relationships[] = $through->belongsToJson($related, $secondLocalKey, $secondKey);
340
        }
341
342 36
        $hasManyThroughJson = $this->newHasManyThroughJson($relationships);
343
344 36
        $jsonKey = $firstKey instanceof JsonKey ? $firstKey : $secondLocalKey;
345
346 36
        if (str_contains($jsonKey, '[]->')) {
0 ignored issues
show
Bug introduced by
It seems like $jsonKey can also be of type null; however, parameter $haystack of str_contains() 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

346
        if (str_contains(/** @scrutinizer ignore-type */ $jsonKey, '[]->')) {
Loading history...
347 12
            $this->addHasManyThroughJsonPivotRelationship($hasManyThroughJson, $relationships, $through);
348
        }
349
350 36
        return $hasManyThroughJson;
351
    }
352
353
    /**
354
     * Add the pivot relationship to the has-many-through JSON relationship.
355
     *
356
     * @param \Staudenmeir\EloquentHasManyDeep\HasManyDeep $hasManyThroughJson
357
     * @param \Illuminate\Database\Eloquent\Relations\Relation[] $relationships
358
     * @param \Illuminate\Database\Eloquent\Model $through
359
     * @return void
360
     */
361 12
    protected function addHasManyThroughJsonPivotRelationship(
362
        $hasManyThroughJson,
363
        array $relationships,
364
        Model $through
365
    ): void {
366 12
        if ($relationships[0] instanceof HasManyJson) {
367
            /** @var \Staudenmeir\EloquentJsonRelations\Relations\HasManyJson $hasManyJson */
368 6
            $hasManyJson = $relationships[0];
369
370 6
            $postGetCallback = function (Collection $models) use ($hasManyJson, $relationships) {
0 ignored issues
show
Unused Code introduced by
The import $relationships is not used and could be removed.

This check looks for imports that have been defined, but are not used in the scope.

Loading history...
371 2
                if (isset($models[0]->laravel_through_key)) {
372 2
                    $hasManyJson->hydratePivotRelation(
373
                        $models,
374
                        $this,
0 ignored issues
show
Bug introduced by
$this of type Staudenmeir\EloquentJson...ns\HasJsonRelationships is incompatible with the type Illuminate\Database\Eloquent\Model expected by parameter $parent of Staudenmeir\EloquentJson...:hydratePivotRelation(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

374
                        /** @scrutinizer ignore-type */ $this,
Loading history...
375 2
                        fn (Model $model) => json_decode($model->laravel_through_key, true)
376
                    );
377
                }
378
            };
379
380 6
            $localKey = $this->{$hasManyJson->getLocalKeyName()};
381
382 6
            if (!is_null($localKey)) {
383 2
                $hasManyThroughJson->withPostGetCallbacks([$postGetCallback]);
384
            }
385
386 6
            $hasManyThroughJson->withCustomEagerMatchingCallback(
387 6
                function (array $models, Collection $results, string $relation) use ($hasManyJson, $hasManyThroughJson) {
0 ignored issues
show
Unused Code introduced by
The import $hasManyThroughJson is not used and could be removed.

This check looks for imports that have been defined, but are not used in the scope.

Loading history...
388 2
                    foreach ($models as $model) {
389 2
                        $hasManyJson->hydratePivotRelation(
390 2
                            $model->$relation,
391
                            $model,
392 2
                            fn (Model $model) => json_decode($model->laravel_through_key, true)
393
                        );
394
                    }
395
396 2
                    return $models;
397
                }
398
            );
399
        } else {
400
            /** @var \Staudenmeir\EloquentJsonRelations\Relations\BelongsToJson $belongsToJson */
401 6
            $belongsToJson = $relationships[1];
402
403 6
            $path = $belongsToJson->getForeignKeyPath();
404
405 6
            $postProcessor = function (Model $model, array $attributes) use ($belongsToJson, $path) {
406 4
                $records = json_decode($attributes[$path], true);
407
408 4
                return $belongsToJson->pivotAttributes($model, $model, $records);
409
            };
410
411 6
            $hasManyThroughJson->withPivot(
412 6
                $through->getTable(),
413
                [$path],
414
                accessor: 'pivot',
415
                postProcessor: $postProcessor
416
            );
417
        }
418
    }
419
420
    /**
421
     * Instantiate a new HasManyThroughJson relationship.
422
     *
423
     * @param \Illuminate\Database\Eloquent\Relations\Relation[] $relationships
424
     * @return \Staudenmeir\EloquentHasManyDeep\HasManyDeep
425
     */
426 36
    protected function newHasManyThroughJson(array $relationships)
427
    {
428 36
        if (!method_exists($this, 'hasManyDeepFromRelations')) {
429
            //@codeCoverageIgnoreStart
430
            $message = 'Please install staudenmeir/eloquent-has-many-deep and add the HasRelationships trait to this model.';
431
432
            throw new RuntimeException($message);
433
            // @codeCoverageIgnoreEnd
434
        }
435
436 36
        return $this->hasManyDeepFromRelations($relationships);
437
    }
438
}
439