Passed
Push — master ( 7e8483...c2c113 )
by Jonas
02:50
created

HasManyDeep::chunk()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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