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