Passed
Push — master ( 2566cc...6adfa0 )
by Jonas
12:49 queued 12s
created

normalizeVariadicRelations()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 1
nc 4
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Staudenmeir\EloquentHasManyDeep\Eloquent\Traits;
4
5
use Illuminate\Database\Eloquent\Relations\Relation;
6
use Illuminate\Database\Eloquent\SoftDeletingScope;
7
use Staudenmeir\EloquentHasManyDeep\Eloquent\Relations\ThirdParty\LaravelHasManyMerged\HasManyMerged;
8
use Staudenmeir\EloquentHasManyDeep\HasManyDeep;
9
use Staudenmeir\EloquentHasManyDeep\HasOneDeep;
10
use Staudenmeir\EloquentHasManyDeepContracts\Interfaces\ConcatenableRelation;
11
12
trait ConcatenatesRelationships
13
{
14
    use ConcatenatesNativeRelationships;
15
16
    /**
17
     * Define a has-many-deep relationship from existing relationships.
18
     *
19
     * @param \Illuminate\Database\Eloquent\Relations\Relation|callable ...$relations
20
     * @return \Staudenmeir\EloquentHasManyDeep\HasManyDeep
21
     */
22 112
    public function hasManyDeepFromRelations(...$relations)
23
    {
24 112
        [
25 112
            $related,
26 112
            $through,
27 112
            $foreignKeys,
28 112
            $localKeys,
29 112
            $postGetCallbacks,
30 112
            $customThroughKeyCallback,
31 112
            $customEagerConstraintsCallback,
32 112
            $customEagerMatchingCallback
33 112
        ] =
34 112
            $this->hasOneOrManyDeepFromRelations($relations);
35
36 112
        $relation = $this->hasManyDeep($related, $through, $foreignKeys, $localKeys);
0 ignored issues
show
Bug introduced by
The method hasManyDeep() does not exist on Staudenmeir\EloquentHasM...ncatenatesRelationships. Did you maybe mean hasManyDeepFromRelationsWithConstraints()? ( Ignorable by Annotation )

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

36
        /** @scrutinizer ignore-call */ 
37
        $relation = $this->hasManyDeep($related, $through, $foreignKeys, $localKeys);

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...
37
38 112
        return $this->customizeHasOneOrManyDeepRelationship(
39 112
            $relation,
40 112
            $postGetCallbacks,
41 112
            $customThroughKeyCallback,
42 112
            $customEagerConstraintsCallback,
43 112
            $customEagerMatchingCallback
44 112
        );
45
    }
46
47
    /**
48
     * Define a has-one-deep relationship from existing relationships.
49
     *
50
     * @param \Illuminate\Database\Eloquent\Relations\Relation|callable ...$relations
51
     * @return \Staudenmeir\EloquentHasManyDeep\HasOneDeep
52
     */
53 4
    public function hasOneDeepFromRelations(...$relations)
54
    {
55 4
        [
56 4
            $related,
57 4
            $through,
58 4
            $foreignKeys,
59 4
            $localKeys,
60 4
            $postGetCallbacks,
61 4
            $customThroughKeyCallback,
62 4
            $customEagerConstraintsCallback,
63 4
            $customEagerMatchingCallback
64 4
        ] = $this->hasOneOrManyDeepFromRelations($relations);
65
66 4
        $relation = $this->hasOneDeep($related, $through, $foreignKeys, $localKeys);
0 ignored issues
show
Bug introduced by
The method hasOneDeep() does not exist on Staudenmeir\EloquentHasM...ncatenatesRelationships. Did you maybe mean hasOneDeepFromRelationsWithConstraints()? ( Ignorable by Annotation )

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

66
        /** @scrutinizer ignore-call */ 
67
        $relation = $this->hasOneDeep($related, $through, $foreignKeys, $localKeys);

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...
67
68 4
        return $this->customizeHasOneOrManyDeepRelationship(
69 4
            $relation,
70 4
            $postGetCallbacks,
71 4
            $customThroughKeyCallback,
72 4
            $customEagerConstraintsCallback,
73 4
            $customEagerMatchingCallback
74 4
        );
75
    }
76
77
    /**
78
     * Prepare a has-one-deep or has-many-deep relationship from existing relationships.
79
     *
80
     * @param \Illuminate\Database\Eloquent\Relations\Relation[]|callable[] $relations
81
     * @return array
82
     */
83 116
    protected function hasOneOrManyDeepFromRelations(array $relations)
84
    {
85 116
        $relations = $this->normalizeVariadicRelations($relations);
86
87 116
        foreach ($relations as $i => $relation) {
88 116
            if (is_callable($relation)) {
89 12
                $relations[$i] = $relation();
90
            }
91
        }
92
93 116
        $related = null;
94 116
        $through = [];
95 116
        $foreignKeys = [];
96 116
        $localKeys = [];
97 116
        $postGetCallbacks = [];
98 116
        $customThroughKeyCallback = null;
99 116
        $customEagerConstraintsCallback = null;
100 116
        $customEagerMatchingCallback = null;
101
102 116
        foreach ($relations as $i => $relation) {
103
            // https://github.com/korridor/laravel-has-many-merged
104 116
            if (is_a($relation, 'Korridor\LaravelHasManyMerged\HasManyMerged', true)) {
105 30
                $relation = HasManyMerged::fromBaseRelation($relation);
106
            }
107
108 116
            if ($relation instanceof ConcatenableRelation) {
109 80
                [$through, $foreignKeys, $localKeys] = $relation->appendToDeepRelationship(
110 80
                    $through,
111 80
                    $foreignKeys,
112 80
                    $localKeys,
113 80
                    $i
114 80
                );
115
116 80
                if (method_exists($relation, 'postGetCallback')) {
117 28
                    $postGetCallbacks[] = [$relation, 'postGetCallback'];
118
                }
119
120 80
                if ($i === 0) {
121 57
                    if (method_exists($relation, 'getThroughKeyForDeepRelationships')) {
122 49
                        $customThroughKeyCallback = [$relation, 'getThroughKeyForDeepRelationships'];
123
                    }
124
125 57
                    if (method_exists($relation, 'addEagerConstraintsToDeepRelationship')) {
126 51
                        $customEagerConstraintsCallback = [$relation, 'addEagerConstraintsToDeepRelationship'];
127
                    }
128
129 57
                    if (method_exists($relation, 'matchResultsForDeepRelationship')) {
130 80
                        $customEagerMatchingCallback = [$relation, 'matchResultsForDeepRelationship'];
131
                    }
132
                }
133
            } else {
134 110
                $method = $this->hasOneOrManyDeepRelationMethod($relation);
135
136 110
                [$through, $foreignKeys, $localKeys] = $this->$method($relation, $through, $foreignKeys, $localKeys);
137
            }
138
139 116
            if ($i === count($relations) - 1) {
140 116
                $related = get_class($relation->getRelated());
141
142 116
                if ((new $related())->getTable() !== $relation->getRelated()->getTable()) {
143 116
                    $related .= ' from ' . $relation->getRelated()->getTable();
144
                }
145
            } else {
146 110
                $through[] = $this->hasOneOrManyThroughParent($relation, $relations[$i + 1]);
147
            }
148
        }
149
150 116
        return [
151 116
            $related,
152 116
            $through,
153 116
            $foreignKeys,
154 116
            $localKeys,
155 116
            $postGetCallbacks,
156 116
            $customThroughKeyCallback,
157 116
            $customEagerConstraintsCallback,
158 116
            $customEagerMatchingCallback
159 116
        ];
160
    }
161
162
    /**
163
     * Prepare the through parent class from an existing relationship and its successor.
164
     *
165
     * @param \Illuminate\Database\Eloquent\Relations\Relation $relation
166
     * @param \Illuminate\Database\Eloquent\Relations\Relation $successor
167
     * @return string
168
     */
169 110
    protected function hasOneOrManyThroughParent(Relation $relation, Relation $successor)
170
    {
171 110
        $through = get_class($relation->getRelated());
172
173 110
        if ($relation instanceof ConcatenableRelation && method_exists($relation, 'getTableForDeepRelationship')) {
174 28
            return $through . ' from ' . $relation->getTableForDeepRelationship();
175
        }
176
177 82
        if ((new $through())->getTable() !== $relation->getRelated()->getTable()) {
178 4
            $through .= ' from ' . $relation->getRelated()->getTable();
179
        }
180
181 82
        if (get_class($relation->getRelated()) === get_class($successor->getParent())) {
182 74
            $table = $successor->getParent()->getTable();
183
184 74
            $segments = explode(' as ', $table);
185
186 74
            if (isset($segments[1])) {
187 2
                $through .= ' as ' . $segments[1];
188
            }
189
        }
190
191 82
        return $through;
192
    }
193
194
    /**
195
     * Customize a has-one-deep or has-many-deep relationship.
196
     *
197
     * @param \Staudenmeir\EloquentHasManyDeep\HasManyDeep $relation
198
     * @param callable[] $postGetCallbacks
199
     * @param callable|null $customThroughKeyCallback
200
     * @param callable|null $customEagerConstraintsCallback
201
     * @param callable|null $customEagerMatchingCallback
202
     * @return \Staudenmeir\EloquentHasManyDeep\HasManyDeep|\Staudenmeir\EloquentHasManyDeep\HasOneDeep
203
     */
204 116
    protected function customizeHasOneOrManyDeepRelationship(
205
        HasManyDeep $relation,
206
        array $postGetCallbacks,
207
        ?callable $customThroughKeyCallback,
208
        ?callable $customEagerConstraintsCallback,
209
        ?callable $customEagerMatchingCallback
210
    ): HasManyDeep|HasOneDeep {
211 116
        $relation->withPostGetCallbacks($postGetCallbacks);
212
213 116
        if ($customThroughKeyCallback) {
214 49
            $relation->withCustomThroughKeyCallback($customThroughKeyCallback);
215
        }
216
217 116
        if ($customEagerConstraintsCallback) {
218 51
            $relation->withCustomEagerConstraintsCallback($customEagerConstraintsCallback);
219
        }
220
221 116
        if ($customEagerMatchingCallback) {
222 51
            $relation->withCustomEagerMatchingCallback($customEagerMatchingCallback);
223
        }
224
225 116
        return $relation;
226
    }
227
228
    /**
229
     * Define a has-many-deep relationship with constraints from existing relationships.
230
     *
231
     * @param callable ...$relations
232
     * @return \Staudenmeir\EloquentHasManyDeep\HasManyDeep
233
     */
234 10
    public function hasManyDeepFromRelationsWithConstraints(...$relations): HasManyDeep
235
    {
236 10
        $hasManyDeep = $this->hasManyDeepFromRelations(...$relations);
237
238 10
        return $this->addConstraintsToHasOneOrManyDeepRelationship($hasManyDeep, $relations);
239
    }
240
241
    /**
242
     * Define a has-one-deep relationship with constraints from existing relationships.
243
     *
244
     * @param callable ...$relations
245
     * @return \Staudenmeir\EloquentHasManyDeep\HasOneDeep
246
     */
247 2
    public function hasOneDeepFromRelationsWithConstraints(...$relations): HasOneDeep
248
    {
249 2
        $hasOneDeep = $this->hasOneDeepFromRelations(...$relations);
250
251 2
        return $this->addConstraintsToHasOneOrManyDeepRelationship($hasOneDeep, $relations);
252
    }
253
254
    /**
255
     * Add the constraints from existing relationships to a has-one-deep or has-many-deep relationship.
256
     *
257
     * @param \Staudenmeir\EloquentHasManyDeep\HasManyDeep $deepRelation
258
     * @param callable[] $relations
259
     * @return \Staudenmeir\EloquentHasManyDeep\HasManyDeep|\Staudenmeir\EloquentHasManyDeep\HasOneDeep
260
     */
261 12
    protected function addConstraintsToHasOneOrManyDeepRelationship(
262
        HasManyDeep $deepRelation,
263
        array $relations
264
    ): HasManyDeep|HasOneDeep {
265 12
        $relations = $this->normalizeVariadicRelations($relations);
266
267 12
        foreach ($relations as $i => $relation) {
268 12
            $relationWithoutConstraints = Relation::noConstraints(function () use ($relation) {
269 12
                return $relation();
270 12
            });
271
272 12
            $deepRelation->getQuery()->mergeWheres(
273 12
                $relationWithoutConstraints->getQuery()->getQuery()->wheres,
274 12
                $relationWithoutConstraints->getQuery()->getQuery()->getRawBindings()['where'] ?? []
275 12
            );
276
277 12
            $isLast = $i === count($relations) - 1;
278
279 12
            $this->addRemovedScopesToHasOneOrManyDeepRelationship($deepRelation, $relationWithoutConstraints, $isLast);
280
        }
281
282 12
        return $deepRelation;
283
    }
284
285
    /**
286
     * Add the removed scopes from an existing relationship to a has-one-deep or has-many-deep relationship.
287
     *
288
     * @param \Staudenmeir\EloquentHasManyDeep\HasManyDeep $deepRelation
289
     * @param \Illuminate\Database\Eloquent\Relations\Relation $relation
290
     * @param bool $isLastRelation
291
     * @return void
292
     */
293 12
    protected function addRemovedScopesToHasOneOrManyDeepRelationship(
294
        HasManyDeep $deepRelation,
295
        Relation $relation,
296
        bool $isLastRelation
297
    ): void {
298 12
        $removedScopes = $relation->getQuery()->removedScopes();
299
300 12
        foreach ($removedScopes as $scope) {
301 8
            if ($scope === SoftDeletingScope::class) {
302 4
                if ($isLastRelation) {
303 2
                    $deepRelation->withTrashed();
304
                } else {
305 2
                    $deletedAtColumn = $relation->getRelated()->getQualifiedDeletedAtColumn();
306
307 2
                    $deepRelation->withTrashed($deletedAtColumn);
308
                }
309
            }
310
311 8
            if ($scope === 'SoftDeletableHasManyThrough') {
312 2
                $deletedAtColumn = $relation->getParent()->getQualifiedDeletedAtColumn();
313
314 2
                $deepRelation->withTrashed($deletedAtColumn);
315
            }
316
317 8
            if (str_starts_with($scope, HasManyDeep::class . ':')) {
318 2
                $deletedAtColumn = explode(':', $scope)[1];
319
320 2
                $deepRelation->withTrashed($deletedAtColumn);
321
            }
322
        }
323
    }
324
325
    /**
326
     * Normalize the relations from a variadic parameter.
327
     *
328
     * @param array $relations
329
     * @return array
330
     */
331 116
    protected function normalizeVariadicRelations(array $relations): array
332
    {
333 116
        return is_array($relations[0]) && !is_callable($relations[0]) ? $relations[0] : $relations;
334
    }
335
}
336