Passed
Push — master ( aae9a3...adb00a )
by Jonas
02:44
created

HasManyDeep::performJoin()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 18
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 5

Importance

Changes 0
Metric Value
cc 5
eloc 10
nc 5
nop 1
dl 0
loc 18
ccs 11
cts 11
cp 1
crap 5
rs 9.6111
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 RetrievesIntermediateTables;
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 39
    public function __construct(Builder $query, Model $farParent, array $throughParents, array $foreignKeys, array $localKeys)
48
    {
49 39
        $this->throughParents = $throughParents;
50 39
        $this->foreignKeys = $foreignKeys;
51 39
        $this->localKeys = $localKeys;
52
53 39
        $firstKey = is_array($foreignKeys[0]) ? $foreignKeys[0][1] : $foreignKeys[0];
54
55 39
        parent::__construct($query, $farParent, $throughParents[0], $firstKey, $foreignKeys[1], $localKeys[0], $localKeys[1]);
56 39
    }
57
58
    /**
59
     * Set the base constraints on the relation query.
60
     *
61
     * @return void
62
     */
63 39
    public function addConstraints()
64
    {
65 39
        parent::addConstraints();
66
67 39
        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 39
    }
75
76
    /**
77
     * Set the join clauses on the query.
78
     *
79
     * @param  \Illuminate\Database\Eloquent\Builder|null  $query
80
     * @return void
81
     */
82 39
    protected function performJoin(Builder $query = null)
83
    {
84 39
        $query = $query ?: $this->query;
85
86 39
        $throughParents = array_reverse($this->throughParents);
87 39
        $foreignKeys = array_reverse($this->foreignKeys);
88 39
        $localKeys = array_reverse($this->localKeys);
89
90 39
        $segments = explode(' as ', $query->getQuery()->from);
91
92 39
        $alias = $segments[1] ?? null;
93
94 39
        foreach ($throughParents as $i => $throughParent) {
95 39
            $predecessor = $throughParents[$i - 1] ?? $this->related;
96
97 39
            $prefix = $i === 0 && $alias ? $alias.'.' : '';
98
99 39
            $this->joinThroughParent($query, $throughParent, $predecessor, $foreignKeys[$i], $localKeys[$i], $prefix);
100
        }
101 39
    }
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 39
    protected function joinThroughParent(Builder $query, Model $throughParent, Model $predecessor, $foreignKey, $localKey, $prefix)
115
    {
116 39
        if (is_array($localKey)) {
117 4
            $query->where($throughParent->qualifyColumn($localKey[0]), '=', $predecessor->getMorphClass());
118
119 4
            $localKey = $localKey[1];
120
        }
121
122 39
        $first = $throughParent->qualifyColumn($localKey);
123
124 39
        if (is_array($foreignKey)) {
125 4
            $query->where($predecessor->qualifyColumn($foreignKey[0]), '=', $throughParent->getMorphClass());
126
127 4
            $foreignKey = $foreignKey[1];
128
        }
129
130 39
        $second = $predecessor->qualifyColumn($prefix.$foreignKey);
131
132 39
        $query->join($throughParent->getTable(), $first, '=', $second);
133
134 39
        if ($this->throughParentInstanceSoftDeletes($throughParent)) {
135 21
            $query->whereNull($throughParent->getQualifiedDeletedAtColumn());
136
        }
137 39
    }
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 39
    public function throughParentInstanceSoftDeletes(Model $instance)
146
    {
147 39
        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 5
    public function addEagerConstraints(array $models)
157
    {
158 5
        parent::addEagerConstraints($models);
159
160 5
        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 5
    }
166
167
    /**
168
     * Execute the query as a "select" statement.
169
     *
170
     * @param  array  $columns
171
     * @return \Illuminate\Database\Eloquent\Collection
172
     */
173 16
    public function get($columns = ['*'])
174
    {
175 16
        $models = parent::get($columns);
176
177 16
        $this->hydrateIntermediateRelations($models->all());
178
179 16
        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 19
    protected function shouldSelect(array $columns = ['*'])
233
    {
234 19
        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(), '=', $this->getQualifiedFirstKeyName()
292
        );
293
    }
294
295
    /**
296
     * Restore soft-deleted models.
297
     *
298
     * @param  string  ...$columns
299
     * @return $this
300
     */
301 2
    public function withTrashed(...$columns)
302
    {
303 2
        if (empty($columns)) {
304 1
            $this->query->withTrashed();
305
306 1
            return $this;
307
        }
308
309 1
        if (is_array($columns[0])) {
0 ignored issues
show
introduced by
The condition is_array($columns[0]) is always false.
Loading history...
310 1
            $columns = $columns[0];
311
        }
312
313 1
        $this->query->getQuery()->wheres = collect($this->query->getQuery()->wheres)
314
            ->reject(function ($where) use ($columns) {
315 1
                return $where['type'] === 'Null' && in_array($where['column'], $columns);
316 1
            })->values()->all();
317
318 1
        return $this;
319
    }
320
321
    /**
322
     * Get the "through" parent model instances.
323
     *
324
     * @return \Illuminate\Database\Eloquent\Model[]
325
     */
326 2
    public function getThroughParents()
327
    {
328 2
        return $this->throughParents;
329
    }
330
331
    /**
332
     * Get the foreign keys on the relationship.
333
     *
334
     * @return array
335
     */
336 2
    public function getForeignKeys()
337
    {
338 2
        return $this->foreignKeys;
339
    }
340
341
    /**
342
     * Get the local keys on the relationship.
343
     *
344
     * @return array
345
     */
346 2
    public function getLocalKeys()
347
    {
348 2
        return $this->localKeys;
349
    }
350
}
351