Passed
Push — master ( ee6bc9...8b4b12 )
by Jonas
02:07
created

HasManyDeep::intermediateAttributes()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 6
dl 0
loc 13
rs 10
c 0
b 0
f 0
cc 3
nc 3
nop 2
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
    public function __construct(Builder $query, Model $farParent, array $throughParents, array $foreignKeys, array $localKeys)
48
    {
49
        $this->throughParents = $throughParents;
50
        $this->foreignKeys = $foreignKeys;
51
        $this->localKeys = $localKeys;
52
53
        $firstKey = is_array($foreignKeys[0]) ? $foreignKeys[0][1] : $foreignKeys[0];
54
55
        parent::__construct($query, $farParent, $throughParents[0], $firstKey, $foreignKeys[1], $localKeys[0], $localKeys[1]);
56
    }
57
58
    /**
59
     * Set the base constraints on the relation query.
60
     *
61
     * @return void
62
     */
63
    public function addConstraints()
64
    {
65
        parent::addConstraints();
66
67
        if (static::$constraints) {
68
            if (is_array($this->foreignKeys[0])) {
69
                $column = $this->throughParent->qualifyColumn($this->foreignKeys[0][0]);
70
71
                $this->query->where($column, '=', $this->farParent->getMorphClass());
72
            }
73
        }
74
    }
75
76
    /**
77
     * Set the join clause on the query.
78
     *
79
     * @param  \Illuminate\Database\Eloquent\Builder|null  $query
80
     * @return void
81
     */
82
    protected function performJoin(Builder $query = null)
83
    {
84
        $query = $query ?: $this->query;
85
86
        $throughParents = array_reverse($this->throughParents);
87
        $foreignKeys = array_reverse($this->foreignKeys);
88
        $localKeys = array_reverse($this->localKeys);
89
90
        $segments = explode(' as ', $query->getQuery()->from);
91
92
        $alias = count($segments) > 1 ? $segments[1] : null;
93
94
        foreach ($throughParents as $i => $throughParent) {
95
            $predecessor = $i > 0 ? $throughParents[$i - 1] : $this->related;
96
97
            $first = $this->firstJoinColumn($query, $throughParent, $predecessor, $localKeys[$i]);
98
99
            $suffix = ($i === 0 && $alias ? $alias.'.' : '');
100
101
            $second = $this->secondJoinColumn($query, $throughParent, $predecessor, $foreignKeys[$i], $suffix);
102
103
            $query->join($throughParent->getTable(), $first, '=', $second);
104
105
            if ($this->throughParentInstanceSoftDeletes($throughParent)) {
106
                $query->whereNull($throughParent->getQualifiedDeletedAtColumn());
107
            }
108
        }
109
    }
110
111
    /**
112
     * Get the first column for the join clause.
113
     *
114
     * @param  \Illuminate\Database\Eloquent\Builder  $query
115
     * @param  \Illuminate\Database\Eloquent\Model  $throughParent
116
     * @param  \Illuminate\Database\Eloquent\Model  $predecessor
117
     * @param  array|string  $key
118
     * @return string
119
     */
120
    protected function firstJoinColumn(Builder $query, Model $throughParent, Model $predecessor, $key)
121
    {
122
        if (is_array($key)) {
123
            $query->where($throughParent->qualifyColumn($key[0]), '=', $predecessor->getMorphClass());
124
125
            $key = $key[1];
126
        }
127
128
        return $throughParent->qualifyColumn($key);
129
    }
130
131
    /**
132
     * Get the second column for the join clause.
133
     *
134
     * @param  \Illuminate\Database\Eloquent\Builder  $query
135
     * @param  \Illuminate\Database\Eloquent\Model  $throughParent
136
     * @param  \Illuminate\Database\Eloquent\Model  $predecessor
137
     * @param  array|string  $suffix
138
     * @param  string  $key
139
     * @return string
140
     */
141
    protected function secondJoinColumn(Builder $query, Model $throughParent, Model $predecessor, $key, $suffix)
142
    {
143
        if (is_array($key)) {
0 ignored issues
show
introduced by
The condition is_array($key) is always false.
Loading history...
144
            $query->where($predecessor->qualifyColumn($key[0]), '=', $throughParent->getMorphClass());
145
146
            $key = $key[1];
147
        }
148
149
        return $predecessor->qualifyColumn($suffix.$key);
0 ignored issues
show
Bug introduced by
Are you sure $suffix of type array|string can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

149
        return $predecessor->qualifyColumn(/** @scrutinizer ignore-type */ $suffix.$key);
Loading history...
150
    }
151
152
    /**
153
     * Determine whether a "through" parent instance of the relation uses SoftDeletes.
154
     *
155
     * @param  \Illuminate\Database\Eloquent\Model  $instance
156
     * @return bool
157
     */
158
    public function throughParentInstanceSoftDeletes(Model $instance)
159
    {
160
        return in_array(SoftDeletes::class, class_uses_recursive($instance));
161
    }
162
163
    /**
164
     * Set the constraints for an eager load of the relation.
165
     *
166
     * @param  array  $models
167
     * @return void
168
     */
169
    public function addEagerConstraints(array $models)
170
    {
171
        parent::addEagerConstraints($models);
172
173
        if (is_array($this->foreignKeys[0])) {
174
            $column = $this->throughParent->qualifyColumn($this->foreignKeys[0][0]);
175
176
            $this->query->where($column, '=', $this->farParent->getMorphClass());
177
        }
178
    }
179
180
    /**
181
     * Execute the query as a "select" statement.
182
     *
183
     * @param  array  $columns
184
     * @return \Illuminate\Database\Eloquent\Collection
185
     */
186
    public function get($columns = ['*'])
187
    {
188
        $models = parent::get($columns);
189
190
        $this->hydrateIntermediateRelations($models->all());
191
192
        return $models;
193
    }
194
195
    /**
196
     * Get a paginator for the "select" statement.
197
     *
198
     * @param  int  $perPage
199
     * @param  array  $columns
200
     * @param  string  $pageName
201
     * @param  int  $page
202
     * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
203
     */
204
    public function paginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null)
205
    {
206
        $columns = $this->shouldSelect($columns);
207
208
        unset($columns[array_search($this->getQualifiedFirstKeyName(), $columns)]);
209
210
        $this->query->addSelect($columns);
211
212
        return tap($this->query->paginate($perPage, $columns, $pageName, $page), function (Paginator $paginator) {
213
            $this->hydrateIntermediateRelations($paginator->items());
214
        });
215
    }
216
217
    /**
218
     * Paginate the given query into a simple paginator.
219
     *
220
     * @param  int  $perPage
221
     * @param  array  $columns
222
     * @param  string  $pageName
223
     * @param  int|null  $page
224
     * @return \Illuminate\Contracts\Pagination\Paginator
225
     */
226
    public function simplePaginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null)
227
    {
228
        $columns = $this->shouldSelect($columns);
229
230
        unset($columns[array_search($this->getQualifiedFirstKeyName(), $columns)]);
231
232
        $this->query->addSelect($columns);
233
234
        return tap($this->query->simplePaginate($perPage, $columns, $pageName, $page), function (Paginator $paginator) {
235
            $this->hydrateIntermediateRelations($paginator->items());
236
        });
237
    }
238
239
    /**
240
     * Set the select clause for the relation query.
241
     *
242
     * @param  array  $columns
243
     * @return array
244
     */
245
    protected function shouldSelect(array $columns = ['*'])
246
    {
247
        return array_merge(parent::shouldSelect($columns), $this->intermediateColumns());
248
    }
249
250
    /**
251
     * Chunk the results of the query.
252
     *
253
     * @param  int  $count
254
     * @param  callable  $callback
255
     * @return bool
256
     */
257
    public function chunk($count, callable $callback)
258
    {
259
        return $this->prepareQueryBuilder()->chunk($count, function (Collection $results) use ($callback) {
260
            $this->hydrateIntermediateRelations($results->all());
261
262
            return $callback($results);
263
        });
264
    }
265
266
    /**
267
     * Add the constraints for a relationship query.
268
     *
269
     * @param  \Illuminate\Database\Eloquent\Builder  $query
270
     * @param  \Illuminate\Database\Eloquent\Builder  $parentQuery
271
     * @param  array|mixed  $columns
272
     * @return \Illuminate\Database\Eloquent\Builder
273
     */
274
    public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*'])
275
    {
276
        $query = parent::getRelationExistenceQuery($query, $parentQuery, $columns);
277
278
        if (is_array($this->foreignKeys[0])) {
279
            $column = $this->throughParent->qualifyColumn($this->foreignKeys[0][0]);
280
281
            $query->where($column, '=', $this->farParent->getMorphClass());
282
        }
283
284
        return $query;
285
    }
286
287
    /**
288
     * Add the constraints for a relationship query on the same table.
289
     *
290
     * @param  \Illuminate\Database\Eloquent\Builder  $query
291
     * @param  \Illuminate\Database\Eloquent\Builder  $parentQuery
292
     * @param  array|mixed  $columns
293
     * @return \Illuminate\Database\Eloquent\Builder
294
     */
295
    public function getRelationExistenceQueryForSelfRelation(Builder $query, Builder $parentQuery, $columns = ['*'])
296
    {
297
        $query->from($query->getModel()->getTable().' as '.$hash = $this->getRelationCountHash());
298
299
        $this->performJoin($query);
300
301
        $query->getModel()->setTable($hash);
302
303
        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...
304
            $parentQuery->getQuery()->from.'.'.$query->getModel()->getKeyName(), '=', $this->getQualifiedFirstKeyName()
305
        );
306
    }
307
308
    /**
309
     * Restore soft-deleted models.
310
     *
311
     * @param  string  ...$columns
312
     * @return $this
313
     */
314
    public function withTrashed(...$columns)
315
    {
316
        if (empty($columns)) {
317
            $this->query->withTrashed();
318
319
            return $this;
320
        }
321
322
        if (is_array($columns[0])) {
0 ignored issues
show
introduced by
The condition is_array($columns[0]) is always false.
Loading history...
323
            $columns = $columns[0];
324
        }
325
326
        $this->query->getQuery()->wheres = collect($this->query->getQuery()->wheres)
327
            ->reject(function ($where) use ($columns) {
328
                return $where['type'] === 'Null' && in_array($where['column'], $columns);
329
            })->values()->all();
330
331
        return $this;
332
    }
333
}
334