Passed
Push — master ( ca857b...090ca3 )
by Jonas
08:51 queued 06:33
created

HasManyDeep::getThroughParents()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Staudenmeir\EloquentHasManyDeep;
4
5
use Illuminate\Contracts\Pagination\Paginator;
6
use Illuminate\Database\Eloquent\Builder;
7
use Illuminate\Database\Eloquent\Model;
8
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
9
use Illuminate\Database\Eloquent\SoftDeletes;
10
use Illuminate\Pagination\CursorPaginator;
11
use Illuminate\Support\Collection;
12
13
class HasManyDeep extends HasManyThrough
14
{
15
    use HasEagerLimit;
0 ignored issues
show
Bug introduced by
The trait Staudenmeir\EloquentHasManyDeep\HasEagerLimit requires the property $exists which is not provided by Staudenmeir\EloquentHasManyDeep\HasManyDeep.
Loading history...
16
    use RetrievesIntermediateTables;
17
18
    /**
19
     * The "through" parent model instances.
20
     *
21
     * @var \Illuminate\Database\Eloquent\Model[]
22
     */
23
    protected $throughParents;
24
25
    /**
26
     * The foreign keys on the relationship.
27
     *
28
     * @var array
29
     */
30
    protected $foreignKeys;
31
32
    /**
33
     * The local keys on the relationship.
34
     *
35
     * @var array
36
     */
37
    protected $localKeys;
38
39
    /**
40
     * Create a new has many deep relationship instance.
41
     *
42
     * @param \Illuminate\Database\Eloquent\Builder $query
43
     * @param \Illuminate\Database\Eloquent\Model $farParent
44
     * @param \Illuminate\Database\Eloquent\Model[] $throughParents
45
     * @param array $foreignKeys
46
     * @param array $localKeys
47
     * @return void
48
     */
49 43
    public function __construct(Builder $query, Model $farParent, array $throughParents, array $foreignKeys, array $localKeys)
50
    {
51 43
        $this->throughParents = $throughParents;
52 43
        $this->foreignKeys = $foreignKeys;
53 43
        $this->localKeys = $localKeys;
54
55 43
        $firstKey = is_array($foreignKeys[0]) ? $foreignKeys[0][1] : $foreignKeys[0];
56
57 43
        parent::__construct($query, $farParent, $throughParents[0], $firstKey, $foreignKeys[1], $localKeys[0], $localKeys[1]);
58 43
    }
59
60
    /**
61
     * Set the base constraints on the relation query.
62
     *
63
     * @return void
64
     */
65 43
    public function addConstraints()
66
    {
67 43
        parent::addConstraints();
68
69 43
        if (static::$constraints) {
70 28
            if (is_array($this->foreignKeys[0])) {
71 2
                $column = $this->throughParent->qualifyColumn($this->foreignKeys[0][0]);
72
73 2
                $this->query->where($column, '=', $this->farParent->getMorphClass());
74
            }
75
        }
76 43
    }
77
78
    /**
79
     * Set the join clauses on the query.
80
     *
81
     * @param \Illuminate\Database\Eloquent\Builder|null $query
82
     * @return void
83
     */
84 43
    protected function performJoin(Builder $query = null)
85
    {
86 43
        $query = $query ?: $this->query;
87
88 43
        $throughParents = array_reverse($this->throughParents);
89 43
        $foreignKeys = array_reverse($this->foreignKeys);
90 43
        $localKeys = array_reverse($this->localKeys);
91
92 43
        $segments = explode(' as ', $query->getQuery()->from);
93
94 43
        $alias = $segments[1] ?? null;
95
96 43
        foreach ($throughParents as $i => $throughParent) {
97 43
            $predecessor = $throughParents[$i - 1] ?? $this->related;
98
99 43
            $prefix = $i === 0 && $alias ? $alias.'.' : '';
100
101 43
            $this->joinThroughParent($query, $throughParent, $predecessor, $foreignKeys[$i], $localKeys[$i], $prefix);
102
        }
103 43
    }
104
105
    /**
106
     * Join a through parent table.
107
     *
108
     * @param \Illuminate\Database\Eloquent\Builder $query
109
     * @param \Illuminate\Database\Eloquent\Model $throughParent
110
     * @param \Illuminate\Database\Eloquent\Model $predecessor
111
     * @param array|string $foreignKey
112
     * @param array|string $localKey
113
     * @param string $prefix
114
     * @return void
115
     */
116 43
    protected function joinThroughParent(Builder $query, Model $throughParent, Model $predecessor, $foreignKey, $localKey, $prefix)
117
    {
118 43
        if (is_array($localKey)) {
119 4
            $query->where($throughParent->qualifyColumn($localKey[0]), '=', $predecessor->getMorphClass());
120
121 4
            $localKey = $localKey[1];
122
        }
123
124 43
        $first = $throughParent->qualifyColumn($localKey);
125
126 43
        if (is_array($foreignKey)) {
127 4
            $query->where($predecessor->qualifyColumn($foreignKey[0]), '=', $throughParent->getMorphClass());
128
129 4
            $foreignKey = $foreignKey[1];
130
        }
131
132 43
        $second = $predecessor->qualifyColumn($prefix.$foreignKey);
133
134 43
        $query->join($throughParent->getTable(), $first, '=', $second);
135
136 43
        if ($this->throughParentInstanceSoftDeletes($throughParent)) {
137 23
            $query->whereNull($throughParent->getQualifiedDeletedAtColumn());
138
        }
139 43
    }
140
141
    /**
142
     * Determine whether a "through" parent instance of the relation uses SoftDeletes.
143
     *
144
     * @param \Illuminate\Database\Eloquent\Model $instance
145
     * @return bool
146
     */
147 43
    public function throughParentInstanceSoftDeletes(Model $instance)
148
    {
149 43
        return in_array(SoftDeletes::class, class_uses_recursive($instance));
150
    }
151
152
    /**
153
     * Set the constraints for an eager load of the relation.
154
     *
155
     * @param array $models
156
     * @return void
157
     */
158 9
    public function addEagerConstraints(array $models)
159
    {
160 9
        parent::addEagerConstraints($models);
161
162 9
        if (is_array($this->foreignKeys[0])) {
163 1
            $column = $this->throughParent->qualifyColumn($this->foreignKeys[0][0]);
164
165 1
            $this->query->where($column, '=', $this->farParent->getMorphClass());
166
        }
167 9
    }
168
169
    /**
170
     * Execute the query as a "select" statement.
171
     *
172
     * @param array $columns
173
     * @return \Illuminate\Database\Eloquent\Collection
174
     */
175 34
    public function get($columns = ['*'])
176
    {
177 34
        $models = parent::get($columns);
178
179 34
        $this->hydrateIntermediateRelations($models->all());
180
181 34
        return $models;
182
    }
183
184
    /**
185
     * Get a paginator for the "select" statement.
186
     *
187
     * @param int $perPage
188
     * @param array $columns
189
     * @param string $pageName
190
     * @param int $page
191
     * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
192
     */
193 1
    public function paginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null)
194
    {
195 1
        $columns = $this->shouldSelect($columns);
196
197 1
        $columns = array_diff($columns, [$this->getQualifiedFirstKeyName().' as laravel_through_key']);
198
199 1
        $this->query->addSelect($columns);
200
201 1
        return tap($this->query->paginate($perPage, $columns, $pageName, $page), function (Paginator $paginator) {
202 1
            $this->hydrateIntermediateRelations($paginator->items());
203 1
        });
204
    }
205
206
    /**
207
     * Paginate the given query into a simple paginator.
208
     *
209
     * @param int $perPage
210
     * @param array $columns
211
     * @param string $pageName
212
     * @param int|null $page
213
     * @return \Illuminate\Contracts\Pagination\Paginator
214
     */
215 1
    public function simplePaginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null)
216
    {
217 1
        $columns = $this->shouldSelect($columns);
218
219 1
        $columns = array_diff($columns, [$this->getQualifiedFirstKeyName().' as laravel_through_key']);
220
221 1
        $this->query->addSelect($columns);
222
223 1
        return tap($this->query->simplePaginate($perPage, $columns, $pageName, $page), function (Paginator $paginator) {
224 1
            $this->hydrateIntermediateRelations($paginator->items());
225 1
        });
226
    }
227
228
    /**
229
     * Paginate the given query into a cursor paginator.
230
     *
231
     * @param  int|null  $perPage
232
     * @param  array  $columns
233
     * @param  string  $cursorName
234
     * @param  string|null  $cursor
235
     * @return \Illuminate\Contracts\Pagination\CursorPaginator
236
     */
237
    public function cursorPaginate($perPage = null, $columns = ['*'], $cursorName = 'cursor', $cursor = null)
238
    {
239
        $columns = $this->shouldSelect($columns);
240
241
        $columns = array_diff($columns, [$this->getQualifiedFirstKeyName().' as laravel_through_key']);
242
243
        $this->query->addSelect($columns);
244
245
        return tap($this->query->cursorPaginate($perPage, $columns, $cursorName, $cursor), function (CursorPaginator $paginator) {
246
            $this->hydrateIntermediateRelations($paginator->items());
247
        });
248
    }
249
250
    /**
251
     * Set the select clause for the relation query.
252
     *
253
     * @param array $columns
254
     * @return array
255
     */
256 37
    protected function shouldSelect(array $columns = ['*'])
257
    {
258 37
        return array_merge(parent::shouldSelect($columns), $this->intermediateColumns());
259
    }
260
261
    /**
262
     * Chunk the results of the query.
263
     *
264
     * @param int $count
265
     * @param callable $callback
266
     * @return bool
267
     */
268 1
    public function chunk($count, callable $callback)
269
    {
270 1
        return $this->prepareQueryBuilder()->chunk($count, function (Collection $results) use ($callback) {
271 1
            $this->hydrateIntermediateRelations($results->all());
272
273 1
            return $callback($results);
274 1
        });
275
    }
276
277
    /**
278
     * Add the constraints for a relationship query.
279
     *
280
     * @param \Illuminate\Database\Eloquent\Builder $query
281
     * @param \Illuminate\Database\Eloquent\Builder $parentQuery
282
     * @param array|mixed $columns
283
     * @return \Illuminate\Database\Eloquent\Builder
284
     */
285 6
    public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*'])
286
    {
287 6
        $query = parent::getRelationExistenceQuery($query, $parentQuery, $columns);
288
289 6
        if (is_array($this->foreignKeys[0])) {
290 2
            $column = $this->throughParent->qualifyColumn($this->foreignKeys[0][0]);
291
292 2
            $query->where($column, '=', $this->farParent->getMorphClass());
293
        }
294
295 6
        return $query;
296
    }
297
298
    /**
299
     * Add the constraints for a relationship query on the same table.
300
     *
301
     * @param \Illuminate\Database\Eloquent\Builder $query
302
     * @param \Illuminate\Database\Eloquent\Builder $parentQuery
303
     * @param array|mixed $columns
304
     * @return \Illuminate\Database\Eloquent\Builder
305
     */
306 2
    public function getRelationExistenceQueryForSelfRelation(Builder $query, Builder $parentQuery, $columns = ['*'])
307
    {
308 2
        $query->from($query->getModel()->getTable().' as '.$hash = $this->getRelationCountHash());
309
310 2
        $this->performJoin($query);
311
312 2
        $query->getModel()->setTable($hash);
313
314 2
        return $query->select($columns)->whereColumn(
0 ignored issues
show
Bug Best Practice introduced by
The expression return $query->select($c...ualifiedFirstKeyName()) also could return the type Illuminate\Database\Query\Builder which is incompatible with the documented return type Illuminate\Database\Eloquent\Builder.
Loading history...
315 2
            $parentQuery->getQuery()->from.'.'.$query->getModel()->getKeyName(),
316 2
            '=',
317 2
            $this->getQualifiedFirstKeyName()
318
        );
319
    }
320
321
    /**
322
     * Restore soft-deleted models.
323
     *
324
     * @param array|string ...$columns
325
     * @return $this
326
     */
327 2
    public function withTrashed(...$columns)
328
    {
329 2
        if (empty($columns)) {
330 1
            $this->query->withTrashed();
331
332 1
            return $this;
333
        }
334
335 1
        if (is_array($columns[0])) {
336 1
            $columns = $columns[0];
337
        }
338
339 1
        $this->query->getQuery()->wheres = collect($this->query->getQuery()->wheres)
340 1
            ->reject(function ($where) use ($columns) {
341 1
                return $where['type'] === 'Null' && in_array($where['column'], $columns);
342 1
            })->values()->all();
343
344 1
        return $this;
345
    }
346
347
    /**
348
     * Get the "through" parent model instances.
349
     *
350
     * @return \Illuminate\Database\Eloquent\Model[]
351
     */
352 2
    public function getThroughParents()
353
    {
354 2
        return $this->throughParents;
355
    }
356
357
    /**
358
     * Get the foreign keys on the relationship.
359
     *
360
     * @return array
361
     */
362 2
    public function getForeignKeys()
363
    {
364 2
        return $this->foreignKeys;
365
    }
366
367
    /**
368
     * Get the local keys on the relationship.
369
     *
370
     * @return array
371
     */
372 2
    public function getLocalKeys()
373
    {
374 2
        return $this->localKeys;
375
    }
376
}
377