Passed
Push — master ( ee6bc9...8b4b12 )
by Jonas
02:07
created

hasOneOrManyDeepFromBelongsToMany()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 6
nc 1
nop 4
dl 0
loc 11
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Staudenmeir\EloquentHasManyDeep;
4
5
use Closure;
6
use Illuminate\Database\Eloquent\Builder;
7
use Illuminate\Database\Eloquent\Model;
8
use Illuminate\Database\Eloquent\Relations\BelongsTo;
9
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
10
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
11
use Illuminate\Database\Eloquent\Relations\HasOneOrMany;
12
use Illuminate\Database\Eloquent\Relations\MorphOneOrMany;
13
use Illuminate\Database\Eloquent\Relations\MorphToMany;
14
use Illuminate\Database\Eloquent\Relations\Pivot;
15
use Illuminate\Database\Eloquent\Relations\Relation;
16
use Illuminate\Support\Str;
17
use RuntimeException;
18
19
trait HasRelationships
20
{
21
    /**
22
     * Define a has-many-deep relationship.
23
     *
24
     * @param  string  $related
25
     * @param  array  $through
26
     * @param  array  $foreignKeys
27
     * @param  array  $localKeys
28
     * @return \Staudenmeir\EloquentHasManyDeep\HasManyDeep
29
     */
30
    public function hasManyDeep($related, array $through, array $foreignKeys = [], array $localKeys = [])
31
    {
32
        return $this->newHasManyDeep(...$this->hasOneOrManyDeep($related, $through, $foreignKeys, $localKeys));
33
    }
34
35
    /**
36
     * Define a has-many-deep relationship from existing relationships.
37
     *
38
     * @param  \Illuminate\Database\Eloquent\Relations\Relation ...$relations
39
     * @return \Staudenmeir\EloquentHasManyDeep\HasManyDeep
40
     */
41
    public function hasManyDeepFromRelations(...$relations)
42
    {
43
        return $this->hasManyDeep(...$this->hasOneOrManyDeepFromRelations($relations));
44
    }
45
46
    /**
47
     * Define a has-one-deep relationship.
48
     *
49
     * @param  string  $related
50
     * @param  array  $through
51
     * @param  array  $foreignKeys
52
     * @param  array  $localKeys
53
     * @return \Staudenmeir\EloquentHasManyDeep\HasOneDeep
54
     */
55
    public function hasOneDeep($related, array $through, array $foreignKeys = [], array $localKeys = [])
56
    {
57
        return $this->newHasOneDeep(...$this->hasOneOrManyDeep($related, $through, $foreignKeys, $localKeys));
58
    }
59
60
    /**
61
     * Define a has-one-deep relationship from existing relationships.
62
     *
63
     * @param  \Illuminate\Database\Eloquent\Relations\Relation ...$relations
64
     * @return \Staudenmeir\EloquentHasManyDeep\HasOneDeep
65
     */
66
    public function hasOneDeepFromRelations(...$relations)
67
    {
68
        return $this->hasOneDeep(...$this->hasOneOrManyDeepFromRelations($relations));
69
    }
70
71
    /**
72
     * Prepare a has-one-deep or has-many-deep relationship.
73
     *
74
     * @param  string  $related
75
     * @param  array  $through
76
     * @param  array  $foreignKeys
77
     * @param  array  $localKeys
78
     * @return array
79
     */
80
    protected function hasOneOrManyDeep($related, array $through, array $foreignKeys, array $localKeys)
81
    {
82
        $relatedInstance = $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

82
        /** @scrutinizer ignore-call */ 
83
        $relatedInstance = $this->newRelatedInstance($related);
Loading history...
83
84
        $throughParents = $this->hasOneOrManyDeepThroughParents($through);
85
86
        $foreignKeys = $this->hasOneOrManyDeepForeignKeys($relatedInstance, $throughParents, $foreignKeys);
87
88
        $localKeys = $this->hasOneOrManyDeepLocalKeys($relatedInstance, $throughParents, $localKeys);
89
90
        return [$relatedInstance->newQuery(), $this, $throughParents, $foreignKeys, $localKeys];
91
    }
92
93
    /**
94
     * Prepare the through parents for a has-one-deep or has-many-deep relationship.
95
     *
96
     * @param  array  $through
97
     * @return array
98
     */
99
    protected function hasOneOrManyDeepThroughParents(array $through)
100
    {
101
        return array_map(function ($class) {
102
            $segments = preg_split('/\s+as\s+/i', $class);
103
104
            $instance = Str::contains($segments[0], '\\')
105
                ? new $segments[0]
106
                : (new Pivot)->setTable($segments[0]);
107
108
            if (isset($segments[1])) {
109
                $instance->setTable($instance->getTable().' as '.$segments[1]);
110
            }
111
112
            return $instance;
113
        }, $through);
114
    }
115
116
    /**
117
     * Prepare the foreign keys for a has-one-deep or has-many-deep relationship.
118
     *
119
     * @param  \Illuminate\Database\Eloquent\Model $related
120
     * @param  \Illuminate\Database\Eloquent\Model[]  $throughParents
121
     * @param  array  $foreignKeys
122
     * @return array
123
     */
124
    protected function hasOneOrManyDeepForeignKeys(Model $related, array $throughParents, array $foreignKeys)
125
    {
126
        foreach (array_merge([$this], $throughParents) as $i => $instance) {
127
            if (! isset($foreignKeys[$i])) {
128
                if ($instance instanceof Pivot) {
129
                    $foreignKeys[$i] = ($throughParents[$i] ?? $related)->getKeyName();
130
                } else {
131
                    $foreignKeys[$i] = $instance->getForeignKey();
132
                }
133
            }
134
        }
135
136
        return $foreignKeys;
137
    }
138
139
    /**
140
     * Prepare the local keys for a has-one-deep or has-many-deep relationship.
141
     *
142
     * @param  \Illuminate\Database\Eloquent\Model $related
143
     * @param  \Illuminate\Database\Eloquent\Model[]  $throughParents
144
     * @param  array  $localKeys
145
     * @return array
146
     */
147
    protected function hasOneOrManyDeepLocalKeys(Model $related, array $throughParents, array $localKeys)
148
    {
149
        foreach (array_merge([$this], $throughParents) as $i => $instance) {
150
            if (! isset($localKeys[$i])) {
151
                if ($instance instanceof Pivot) {
152
                    $localKeys[$i] = ($throughParents[$i] ?? $related)->getForeignKey();
153
                } else {
154
                    $localKeys[$i] = $instance->getKeyName();
155
                }
156
            }
157
        }
158
159
        return $localKeys;
160
    }
161
162
    /**
163
     * Prepare a has-one-deep or has-many-deep relationship from existing relationships.
164
     *
165
     * @param  \Illuminate\Database\Eloquent\Relations\Relation[]  $relations
166
     * @return array
167
     */
168
    protected function hasOneOrManyDeepFromRelations(array $relations)
169
    {
170
        if (is_array($relations[0])) {
0 ignored issues
show
introduced by
The condition is_array($relations[0]) is always false.
Loading history...
171
            $relations = $relations[0];
172
        }
173
174
        $related = null;
175
        $through = [];
176
        $foreignKeys = [];
177
        $localKeys = [];
178
179
        foreach ($relations as $i => $relation) {
180
            $method = $this->hasOneOrManyDeepRelationMethod($relation);
181
182
            list($through, $foreignKeys, $localKeys) = $this->$method($relation, $through, $foreignKeys, $localKeys);
183
184
            if ($i === count($relations) - 1) {
185
                $related = get_class($relation->getRelated());
186
            } else {
187
                $through[] = get_class($relation->getRelated());
188
            }
189
        }
190
191
        return [$related, $through, $foreignKeys, $localKeys];
192
    }
193
194
    /**
195
     * Prepare a has-one-deep or has-many-deep relationship from an existing belongs-to relationship.
196
     *
197
     * @param  \Illuminate\Database\Eloquent\Relations\BelongsTo  $relation
198
     * @param  \Illuminate\Database\Eloquent\Model[]  $through
199
     * @param  array  $foreignKeys
200
     * @param  array  $localKeys
201
     * @return array
202
     */
203
    protected function hasOneOrManyDeepFromBelongsTo(BelongsTo $relation, array $through, array $foreignKeys, array $localKeys)
204
    {
205
        $foreignKeys[] = $relation->getOwnerKey();
206
207
        $localKeys[] = $relation->getForeignKey();
208
209
        return [$through, $foreignKeys, $localKeys];
210
    }
211
212
    /**
213
     * Prepare a has-one-deep or has-many-deep relationship from an existing belongs-to-many relationship.
214
     *
215
     * @param  \Illuminate\Database\Eloquent\Relations\BelongsToMany  $relation
216
     * @param  \Illuminate\Database\Eloquent\Model[]  $through
217
     * @param  array  $foreignKeys
218
     * @param  array  $localKeys
219
     * @return array
220
     */
221
    protected function hasOneOrManyDeepFromBelongsToMany(BelongsToMany $relation, array $through, array $foreignKeys, array $localKeys)
222
    {
223
        $through[] = $relation->getTable();
224
225
        $foreignKeys[] = $relation->getForeignPivotKeyName();
226
        $foreignKeys[] = $this->getProtectedProperty($relation, 'relatedKey');
227
228
        $localKeys[] = $this->getProtectedProperty($relation, 'parentKey');
229
        $localKeys[] = $relation->getRelatedPivotKeyName();
230
231
        return [$through, $foreignKeys, $localKeys];
232
    }
233
234
    /**
235
     * Prepare a has-one-deep or has-many-deep relationship from an existing has-one or has-many relationship.
236
     *
237
     * @param  \Illuminate\Database\Eloquent\Relations\HasOneOrMany  $relation
238
     * @param  \Illuminate\Database\Eloquent\Model[]  $through
239
     * @param  array  $foreignKeys
240
     * @param  array  $localKeys
241
     * @return array
242
     */
243
    protected function hasOneOrManyDeepFromHasOneOrMany(HasOneOrMany $relation, array $through, array $foreignKeys, array $localKeys)
244
    {
245
        $foreignKeys[] = $relation->getQualifiedForeignKeyName();
246
247
        $localKeys[] = $this->getProtectedProperty($relation, 'localKey');
248
249
        return [$through, $foreignKeys, $localKeys];
250
    }
251
252
    /**
253
     * Prepare a has-one-deep or has-many-deep relationship from an existing has-many-through relationship.
254
     *
255
     * @param  \Illuminate\Database\Eloquent\Relations\HasManyThrough  $relation
256
     * @param  \Illuminate\Database\Eloquent\Model[]  $through
257
     * @param  array  $foreignKeys
258
     * @param  array  $localKeys
259
     * @return array
260
     */
261
    protected function hasOneOrManyDeepFromHasManyThrough(HasManyThrough $relation, array $through, array $foreignKeys, array $localKeys)
262
    {
263
        $through[] = get_class($relation->getParent());
264
265
        $foreignKeys[] = $this->getProtectedProperty($relation, 'firstKey');
266
        $foreignKeys[] = $this->getProtectedProperty($relation, 'secondKey');
267
268
        $localKeys[] = $this->getProtectedProperty($relation, 'localKey');
269
        $localKeys[] = $this->getProtectedProperty($relation, 'secondLocalKey');
270
271
        return [$through, $foreignKeys, $localKeys];
272
    }
273
274
    /**
275
     * Prepare a has-one-deep or has-many-deep relationship from an existing morph-one or morph-many relationship.
276
     *
277
     * @param  \Illuminate\Database\Eloquent\Relations\MorphOneOrMany  $relation
278
     * @param  \Illuminate\Database\Eloquent\Model[]  $through
279
     * @param  array  $foreignKeys
280
     * @param  array  $localKeys
281
     * @return array
282
     */
283
    protected function hasOneOrManyDeepFromMorphOneOrMany(MorphOneOrMany $relation, array $through, array $foreignKeys, array $localKeys)
284
    {
285
        $foreignKeys[] = [$relation->getQualifiedMorphType(), $relation->getQualifiedForeignKeyName()];
286
287
        $localKeys[] = $this->getProtectedProperty($relation, 'localKey');
288
289
        return [$through, $foreignKeys, $localKeys];
290
    }
291
292
    /**
293
     * Prepare a has-one-deep or has-many-deep relationship from an existing morph-to-many relationship.
294
     *
295
     * @param  \Illuminate\Database\Eloquent\Relations\MorphToMany  $relation
296
     * @param  \Illuminate\Database\Eloquent\Model[]  $through
297
     * @param  array  $foreignKeys
298
     * @param  array  $localKeys
299
     * @return array
300
     */
301
    protected function hasOneOrManyDeepFromMorphToMany(MorphToMany $relation, array $through, array $foreignKeys, array $localKeys)
302
    {
303
        $through[] = $relation->getTable();
304
305
        if ($this->getProtectedProperty($relation, 'inverse')) {
306
            $foreignKeys[] = $relation->getForeignPivotKeyName();
307
            $foreignKeys[] = $this->getProtectedProperty($relation, 'relatedKey');
308
309
            $localKeys[] = $this->getProtectedProperty($relation, 'parentKey');
310
            $localKeys[] = [$relation->getMorphType(), $relation->getRelatedPivotKeyName()];
311
        } else {
312
            $foreignKeys[] = [$relation->getMorphType(), $relation->getForeignPivotKeyName()];
313
            $foreignKeys[] = $this->getProtectedProperty($relation, 'relatedKey');
314
315
            $localKeys[] = $this->getProtectedProperty($relation, 'parentKey');
316
            $localKeys[] = $relation->getRelatedPivotKeyName();
317
        }
318
319
        return [$through, $foreignKeys, $localKeys];
320
    }
321
322
    /**
323
     * Get the relationship method name.
324
     *
325
     * @param  \Illuminate\Database\Eloquent\Relations\Relation  $relation
326
     * @return string
327
     */
328
    protected function hasOneOrManyDeepRelationMethod(Relation $relation)
329
    {
330
        $classes = [
331
            BelongsTo::class,
332
            HasManyThrough::class,
333
            MorphOneOrMany::class,
334
            HasOneOrMany::class,
335
            MorphToMany::class,
336
            BelongsToMany::class
337
        ];
338
339
        foreach ($classes as $class) {
340
            if ($relation instanceof $class) {
341
                return 'hasOneOrManyDeepFrom'.class_basename($class);
342
            }
343
        }
344
345
        throw new RuntimeException('This relationship is not supported.');
346
    }
347
348
    /**
349
     * Instantiate a new HasManyDeep relationship.
350
     *
351
     * @param  \Illuminate\Database\Eloquent\Builder  $query
352
     * @param  \Illuminate\Database\Eloquent\Model  $farParent
353
     * @param  \Illuminate\Database\Eloquent\Model[]  $throughParents
354
     * @param  array  $foreignKeys
355
     * @param  array  $localKeys
356
     * @return \Staudenmeir\EloquentHasManyDeep\HasManyDeep
357
     */
358
    protected function newHasManyDeep(Builder $query, Model $farParent, array $throughParents, array $foreignKeys, array $localKeys)
359
    {
360
        return new HasManyDeep($query, $farParent, $throughParents, $foreignKeys, $localKeys);
361
    }
362
363
    /**
364
     * Instantiate a new HasOneDeep relationship.
365
     *
366
     * @param  \Illuminate\Database\Eloquent\Builder  $query
367
     * @param  \Illuminate\Database\Eloquent\Model  $farParent
368
     * @param  \Illuminate\Database\Eloquent\Model[]  $throughParents
369
     * @param  array  $foreignKeys
370
     * @param  array  $localKeys
371
     * @return \Staudenmeir\EloquentHasManyDeep\HasOneDeep
372
     */
373
    protected function newHasOneDeep(Builder $query, Model $farParent, array $throughParents, array $foreignKeys, array $localKeys)
374
    {
375
        return new HasOneDeep($query, $farParent, $throughParents, $foreignKeys, $localKeys);
376
    }
377
378
    /**
379
     * Get a protected property from a relationship instance.
380
     *
381
     * @param  \Illuminate\Database\Eloquent\Relations\Relation  $relation
382
     * @param  string  $property
383
     * @return \Staudenmeir\EloquentHasManyDeep\HasOneDeep
384
     */
385
    protected function getProtectedProperty(Relation $relation, $property)
386
    {
387
        $closure = Closure::bind(function (Relation $relation) use ($property) {
388
            return $relation->$property;
389
        }, null, $relation);
390
391
        return $closure($relation);
392
    }
393
}
394