Passed
Push — master ( db2f08...ca857b )
by Jonas
08:00
created

HasManyDeep::cursorPaginate()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 5
nc 1
nop 4
dl 0
loc 10
ccs 1
cts 1
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 43
     */
49
    public function __construct(Builder $query, Model $farParent, array $throughParents, array $foreignKeys, array $localKeys)
50 43
    {
51 43
        $this->throughParents = $throughParents;
52 43
        $this->foreignKeys = $foreignKeys;
53
        $this->localKeys = $localKeys;
54 43
55
        $firstKey = is_array($foreignKeys[0]) ? $foreignKeys[0][1] : $foreignKeys[0];
56 43
57 43
        parent::__construct($query, $farParent, $throughParents[0], $firstKey, $foreignKeys[1], $localKeys[0], $localKeys[1]);
58
    }
59
60
    /**
61
     * Set the base constraints on the relation query.
62
     *
63
     * @return void
64 43
     */
65
    public function addConstraints()
66 43
    {
67
        parent::addConstraints();
68 43
69 28
        if (static::$constraints) {
70 2
            if (is_array($this->foreignKeys[0])) {
71
                $column = $this->throughParent->qualifyColumn($this->foreignKeys[0][0]);
72 2
73
                $this->query->where($column, '=', $this->farParent->getMorphClass());
74
            }
75 43
        }
76
    }
77
78
    /**
79
     * Set the join clauses on the query.
80
     *
81
     * @param \Illuminate\Database\Eloquent\Builder|null $query
82
     * @return void
83 43
     */
84
    protected function performJoin(Builder $query = null)
85 43
    {
86
        $query = $query ?: $this->query;
87 43
88 43
        $throughParents = array_reverse($this->throughParents);
89 43
        $foreignKeys = array_reverse($this->foreignKeys);
90
        $localKeys = array_reverse($this->localKeys);
91 43
92
        $segments = explode(' as ', $query->getQuery()->from);
93 43
94
        $alias = $segments[1] ?? null;
95 43
96 43
        foreach ($throughParents as $i => $throughParent) {
97
            $predecessor = $throughParents[$i - 1] ?? $this->related;
98 43
99
            $prefix = $i === 0 && $alias ? $alias.'.' : '';
100 43
101
            $this->joinThroughParent($query, $throughParent, $predecessor, $foreignKeys[$i], $localKeys[$i], $prefix);
102 43
        }
103
    }
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 43
     */
116
    protected function joinThroughParent(Builder $query, Model $throughParent, Model $predecessor, $foreignKey, $localKey, $prefix)
117 43
    {
118 4
        if (is_array($localKey)) {
119
            $query->where($throughParent->qualifyColumn($localKey[0]), '=', $predecessor->getMorphClass());
120 4
121
            $localKey = $localKey[1];
122
        }
123 43
124
        $first = $throughParent->qualifyColumn($localKey);
125 43
126 4
        if (is_array($foreignKey)) {
127
            $query->where($predecessor->qualifyColumn($foreignKey[0]), '=', $throughParent->getMorphClass());
128 4
129
            $foreignKey = $foreignKey[1];
130
        }
131 43
132
        $second = $predecessor->qualifyColumn($prefix.$foreignKey);
133 43
134
        $query->join($throughParent->getTable(), $first, '=', $second);
135 43
136 23
        if ($this->throughParentInstanceSoftDeletes($throughParent)) {
137
            $query->whereNull($throughParent->getQualifiedDeletedAtColumn());
138 43
        }
139
    }
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 43
     */
147
    public function throughParentInstanceSoftDeletes(Model $instance)
148 43
    {
149
        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 9
     */
158
    public function addEagerConstraints(array $models)
159 9
    {
160
        parent::addEagerConstraints($models);
161 9
162 1
        if (is_array($this->foreignKeys[0])) {
163
            $column = $this->throughParent->qualifyColumn($this->foreignKeys[0][0]);
164 1
165
            $this->query->where($column, '=', $this->farParent->getMorphClass());
166 9
        }
167
    }
168
169
    /**
170
     * Execute the query as a "select" statement.
171
     *
172
     * @param array $columns
173
     * @return \Illuminate\Database\Eloquent\Collection
174 34
     */
175
    public function get($columns = ['*'])
176 34
    {
177
        $models = parent::get($columns);
178 34
179
        $this->hydrateIntermediateRelations($models->all());
180 34
181
        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 1
     */
193
    public function paginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null)
194 1
    {
195
        $columns = $this->shouldSelect($columns);
196 1
197
        $columns = array_diff($columns, [$this->getQualifiedFirstKeyName().' as laravel_through_key']);
198 1
199
        $this->query->addSelect($columns);
200 1
201 1
        return tap($this->query->paginate($perPage, $columns, $pageName, $page), function (Paginator $paginator) {
202 1
            $this->hydrateIntermediateRelations($paginator->items());
203
        });
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 1
     */
215
    public function simplePaginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null)
216 1
    {
217
        $columns = $this->shouldSelect($columns);
218 1
219
        $columns = array_diff($columns, [$this->getQualifiedFirstKeyName().' as laravel_through_key']);
220 1
221
        $this->query->addSelect($columns);
222 1
223 1
        return tap($this->query->simplePaginate($perPage, $columns, $pageName, $page), function (Paginator $paginator) {
224 1
            $this->hydrateIntermediateRelations($paginator->items());
225
        });
226
    }
227
228
    /**
229
     * Paginate the given query into a cursor paginator.
230
     *
231
     * @param  int|null  $perPage
232
     * @param  array  $columns
233 37
     * @param  string  $cursorName
234
     * @param  string|null  $cursor
235 37
     * @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 1
        return tap($this->query->cursorPaginate($perPage, $columns, $cursorName, $cursor), function (CursorPaginator $paginator) {
246
            $this->hydrateIntermediateRelations($paginator->items());
247 1
        });
248 1
    }
249
250 1
    /**
251 1
     * Set the select clause for the relation query.
252
     *
253
     * @param array $columns
254
     * @return array
255
     */
256
    protected function shouldSelect(array $columns = ['*'])
257
    {
258
        return array_merge(parent::shouldSelect($columns), $this->intermediateColumns());
259
    }
260
261
    /**
262 6
     * Chunk the results of the query.
263
     *
264 6
     * @param int $count
265
     * @param callable $callback
266 6
     * @return bool
267 2
     */
268
    public function chunk($count, callable $callback)
269 2
    {
270
        return $this->prepareQueryBuilder()->chunk($count, function (Collection $results) use ($callback) {
271
            $this->hydrateIntermediateRelations($results->all());
272 6
273
            return $callback($results);
274
        });
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 2
     * @return \Illuminate\Database\Eloquent\Builder
284
     */
285 2
    public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*'])
286
    {
287 2
        $query = parent::getRelationExistenceQuery($query, $parentQuery, $columns);
288
289 2
        if (is_array($this->foreignKeys[0])) {
290
            $column = $this->throughParent->qualifyColumn($this->foreignKeys[0][0]);
291 2
292 2
            $query->where($column, '=', $this->farParent->getMorphClass());
293 2
        }
294 2
295
        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 2
     * @return \Illuminate\Database\Eloquent\Builder
305
     */
306 2
    public function getRelationExistenceQueryForSelfRelation(Builder $query, Builder $parentQuery, $columns = ['*'])
307 1
    {
308
        $query->from($query->getModel()->getTable().' as '.$hash = $this->getRelationCountHash());
309 1
310
        $this->performJoin($query);
311
312 1
        $query->getModel()->setTable($hash);
313 1
314
        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
            $parentQuery->getQuery()->from.'.'.$query->getModel()->getKeyName(),
316 1
            '=',
317 1
            $this->getQualifiedFirstKeyName()
318 1
        );
319 1
    }
320
321 1
    /**
322
     * Restore soft-deleted models.
323
     *
324
     * @param array|string ...$columns
325
     * @return $this
326
     */
327
    public function withTrashed(...$columns)
328
    {
329 2
        if (empty($columns)) {
330
            $this->query->withTrashed();
331 2
332
            return $this;
333
        }
334
335
        if (is_array($columns[0])) {
336
            $columns = $columns[0];
337
        }
338
339 2
        $this->query->getQuery()->wheres = collect($this->query->getQuery()->wheres)
340
            ->reject(function ($where) use ($columns) {
341 2
                return $where['type'] === 'Null' && in_array($where['column'], $columns);
342
            })->values()->all();
343
344
        return $this;
345
    }
346
347
    /**
348
     * Get the "through" parent model instances.
349 2
     *
350
     * @return \Illuminate\Database\Eloquent\Model[]
351 2
     */
352
    public function getThroughParents()
353
    {
354
        return $this->throughParents;
355
    }
356
357
    /**
358
     * Get the foreign keys on the relationship.
359
     *
360
     * @return array
361
     */
362
    public function getForeignKeys()
363
    {
364
        return $this->foreignKeys;
365
    }
366
367
    /**
368
     * Get the local keys on the relationship.
369
     *
370
     * @return array
371
     */
372
    public function getLocalKeys()
373
    {
374
        return $this->localKeys;
375
    }
376
}
377