Passed
Push — master ( 8684f9...1eb973 )
by Jonas
02:02
created

ConcatenatesRelationships   A

Complexity

Total Complexity 37

Size/Duplication

Total Lines 317
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
eloc 128
c 0
b 0
f 0
dl 0
loc 317
ccs 97
cts 97
cp 1
rs 9.44
wmc 37

10 Methods

Rating   Name   Duplication   Size   Complexity  
A normalizeVariadicRelations() 0 3 3
A hasOneDeepFromRelations() 0 21 1
A hasOneOrManyThroughParent() 0 23 6
A hasManyDeepFromRelationsWithConstraints() 0 5 1
A hasManyDeepFromRelations() 0 22 1
A customizeHasOneOrManyDeepRelationship() 0 22 4
C hasOneOrManyDeepFromRelations() 0 71 12
A hasOneDeepFromRelationsWithConstraints() 0 5 1
A addRemovedScopesToHasOneOrManyDeepRelationship() 0 28 6
A addConstraintsToHasOneOrManyDeepRelationship() 0 22 2
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\HasManyDeep;
8
use Staudenmeir\EloquentHasManyDeep\HasOneDeep;
9
use Staudenmeir\EloquentHasManyDeepContracts\Interfaces\ConcatenableRelation;
10
11
trait ConcatenatesRelationships
12
{
13
    use ConcatenatesNativeRelationships;
14
15
    /**
16
     * Define a has-many-deep relationship from existing relationships.
17
     *
18
     * @param \Illuminate\Database\Eloquent\Relations\Relation|callable ...$relations
19
     * @return \Staudenmeir\EloquentHasManyDeep\HasManyDeep
20
     */
21 80
    public function hasManyDeepFromRelations(...$relations)
22
    {
23
        [
24 80
            $related,
25
            $through,
26
            $foreignKeys,
27
            $localKeys,
28
            $postGetCallbacks,
29
            $customThroughKeyCallback,
30
            $customEagerConstraintsCallback,
31
            $customEagerMatchingCallback
32
        ] =
33 80
            $this->hasOneOrManyDeepFromRelations($relations);
34
35 80
        $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

35
        /** @scrutinizer ignore-call */ 
36
        $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...
36
37 80
        return $this->customizeHasOneOrManyDeepRelationship(
38
            $relation,
39
            $postGetCallbacks,
40
            $customThroughKeyCallback,
41
            $customEagerConstraintsCallback,
42
            $customEagerMatchingCallback
43
        );
44
    }
45
46
    /**
47
     * Define a has-one-deep relationship from existing relationships.
48
     *
49
     * @param \Illuminate\Database\Eloquent\Relations\Relation|callable ...$relations
50
     * @return \Staudenmeir\EloquentHasManyDeep\HasOneDeep
51
     */
52 4
    public function hasOneDeepFromRelations(...$relations)
53
    {
54
        [
55 4
            $related,
56
            $through,
57
            $foreignKeys,
58
            $localKeys,
59
            $postGetCallbacks,
60
            $customThroughKeyCallback,
61
            $customEagerConstraintsCallback,
62
            $customEagerMatchingCallback
63 4
        ] = $this->hasOneOrManyDeepFromRelations($relations);
64
65 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

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