HasManyDeep::getThroughParents()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
3
namespace Staudenmeir\EloquentHasManyDeep;
4
5
use Closure;
6
use Illuminate\Database\Eloquent\Builder;
7
use Illuminate\Database\Eloquent\Model;
8
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
9
use Staudenmeir\EloquentHasManyDeep\Eloquent\Relations\Traits\ExecutesQueries;
10
use Staudenmeir\EloquentHasManyDeep\Eloquent\Relations\Traits\HasEagerLoading;
11
use Staudenmeir\EloquentHasManyDeep\Eloquent\Relations\Traits\HasExistenceQueries;
12
use Staudenmeir\EloquentHasManyDeep\Eloquent\Relations\Traits\IsConcatenable;
13
use Staudenmeir\EloquentHasManyDeep\Eloquent\Relations\Traits\JoinsThroughParents;
14
use Staudenmeir\EloquentHasManyDeep\Eloquent\Relations\Traits\RetrievesIntermediateTables;
15
use Staudenmeir\EloquentHasManyDeep\Eloquent\Relations\Traits\SupportsCompositeKeys;
16
use Staudenmeir\EloquentHasManyDeep\Eloquent\Relations\Traits\IsCustomizable;
17
use Staudenmeir\EloquentHasManyDeepContracts\Interfaces\ConcatenableRelation;
18
19
/**
20
 * @template TRelatedModel of \Illuminate\Database\Eloquent\Model
21
 *
22
 * @extends \Illuminate\Database\Eloquent\Relations\HasManyThrough<TRelatedModel>
23
 */
24
class HasManyDeep extends HasManyThrough implements ConcatenableRelation
25
{
26
    use ExecutesQueries;
27
    use HasEagerLoading;
28
    use HasExistenceQueries;
0 ignored issues
show
Bug introduced by
The trait Staudenmeir\EloquentHasM...its\HasExistenceQueries requires the property $from which is not provided by Staudenmeir\EloquentHasManyDeep\HasManyDeep.
Loading history...
29
    use IsConcatenable;
30
    use IsCustomizable;
31
    use JoinsThroughParents;
0 ignored issues
show
Bug introduced by
The trait Staudenmeir\EloquentHasM...its\JoinsThroughParents requires the property $columns which is not provided by Staudenmeir\EloquentHasManyDeep\HasManyDeep.
Loading history...
32
    use RetrievesIntermediateTables;
33
    use SupportsCompositeKeys;
0 ignored issues
show
Bug introduced by
The trait Staudenmeir\EloquentHasM...s\SupportsCompositeKeys requires the property $columns which is not provided by Staudenmeir\EloquentHasManyDeep\HasManyDeep.
Loading history...
34
35
    /**
36
     * The "through" parent model instances.
37
     *
38
     * @var \Illuminate\Database\Eloquent\Model[]
39
     */
40
    protected $throughParents;
41
42
    /**
43
     * The foreign keys on the relationship.
44
     *
45
     * @var array
46
     */
47
    protected $foreignKeys;
48
49
    /**
50
     * The local keys on the relationship.
51
     *
52
     * @var array
53
     */
54
    protected $localKeys;
55
56
    /**
57
     * Create a new has many deep relationship instance.
58
     *
59
     * @param \Illuminate\Database\Eloquent\Builder $query
60
     * @param \Illuminate\Database\Eloquent\Model $farParent
61
     * @param \Illuminate\Database\Eloquent\Model[] $throughParents
62
     * @param array $foreignKeys
63
     * @param array $localKeys
64
     * @return void
65
     */
66
    public function __construct(Builder $query, Model $farParent, array $throughParents, array $foreignKeys, array $localKeys)
67
    {
68
        $this->throughParents = $throughParents;
69
        $this->foreignKeys = $foreignKeys;
70
        $this->localKeys = $localKeys;
71
72
        $firstKey = is_array($foreignKeys[0])
73
            ? $foreignKeys[0][1]
74
            : ($this->hasLeadingCompositeKey() ? $foreignKeys[0]->columns[0] : $foreignKeys[0]);
75
76
        $localKey = $this->hasLeadingCompositeKey() ? $localKeys[0]->columns[0] : $localKeys[0];
77
78
        parent::__construct($query, $farParent, $throughParents[0], $firstKey, $foreignKeys[1], $localKey, $localKeys[1]);
79
    }
80
81
    /**
82
     * Set the base constraints on the relation query.
83
     *
84
     * @return void
85
     */
86
    public function addConstraints()
87
    {
88
        if ($this->firstKey instanceof Closure || $this->localKey instanceof Closure) {
0 ignored issues
show
introduced by
$this->localKey is never a sub-type of Closure.
Loading history...
89
            $this->performJoin();
90
        } else {
91
            parent::addConstraints();
92
        }
93
94
        if (static::$constraints) {
95
            if ($this->firstKey instanceof Closure) {
0 ignored issues
show
introduced by
$this->firstKey is never a sub-type of Closure.
Loading history...
96
                ($this->firstKey)($this->query);
97
            } elseif ($this->localKey instanceof Closure) {
0 ignored issues
show
introduced by
$this->localKey is never a sub-type of Closure.
Loading history...
98
                ($this->localKey)($this->query);
99
            } elseif (is_array($this->foreignKeys[0])) {
100
                $this->query->where(
101
                    $this->throughParent->qualifyColumn($this->foreignKeys[0][0]),
102
                    '=',
103
                    $this->farParent->getMorphClass()
104
                );
105
            } elseif ($this->hasLeadingCompositeKey()) {
106
                $this->addConstraintsWithCompositeKey();
107
            }
108
        }
109
    }
110
111
    /**
112
     * Set the join clauses on the query.
113
     *
114
     * @param \Illuminate\Database\Eloquent\Builder|null $query
115
     * @return void
116
     */
117
    protected function performJoin(?Builder $query = null)
118
    {
119
        $query = $query ?: $this->query;
120
121
        $throughParents = array_reverse($this->throughParents);
122
        $foreignKeys = array_reverse($this->foreignKeys);
123
        $localKeys = array_reverse($this->localKeys);
124
125
        $segments = explode(' as ', $query->getQuery()->from);
0 ignored issues
show
Bug introduced by
It seems like $query->getQuery()->from can also be of type Illuminate\Database\Query\Expression; however, parameter $string of explode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

125
        $segments = explode(' as ', /** @scrutinizer ignore-type */ $query->getQuery()->from);
Loading history...
126
127
        $alias = $segments[1] ?? null;
128
129
        foreach ($throughParents as $i => $throughParent) {
130
            $predecessor = $throughParents[$i - 1] ?? $this->related;
131
132
            $prefix = $i === 0 && $alias ? $alias.'.' : '';
133
134
            $this->joinThroughParent($query, $throughParent, $predecessor, $foreignKeys[$i], $localKeys[$i], $prefix);
135
        }
136
    }
137
138
    /**
139
     * Set the select clause for the relation query.
140
     *
141
     * @param array $columns
142
     * @return array
143
     */
144
    protected function shouldSelect(array $columns = ['*'])
145
    {
146
        if ($columns == ['*']) {
147
            $columns = [$this->related->getTable().'.*'];
148
        }
149
150
        $alias = 'laravel_through_key';
151
152
        if ($this->customThroughKeyCallback) {
153
            $throughKey = ($this->customThroughKeyCallback)($alias);
154
155
            if (is_array($throughKey)) {
156
                $columns = array_merge($columns, $throughKey);
157
            } else {
158
                $columns[] = $throughKey;
159
            }
160
        } else {
161
            $columns[] = $this->getQualifiedFirstKeyName() . " as $alias";
162
        }
163
164
        if ($this->hasLeadingCompositeKey()) {
165
            $columns = array_merge(
166
                $columns,
167
                $this->shouldSelectWithCompositeKey()
168
            );
169
        }
170
171
        return array_merge($columns, $this->intermediateColumns());
172
    }
173
174
    /**
175
     * Restore soft-deleted models.
176
     *
177
     * @param array|string ...$columns
178
     * @return $this
179
     */
180
    public function withTrashed(...$columns)
181
    {
182
        if (empty($columns)) {
183
            /** @phpstan-ignore method.notFound */
184
            $this->query->withTrashed();
185
186
            return $this;
187
        }
188
189
        if (is_array($columns[0])) {
190
            $columns = $columns[0];
191
        }
192
193
        foreach ($columns as $column) {
194
            $this->query->withoutGlobalScope(__CLASS__ . ":$column");
195
        }
196
197
        return $this;
198
    }
199
200
    /**
201
     * Get the far parent model instance.
202
     *
203
     * @return \Illuminate\Database\Eloquent\Model
204
     */
205
    public function getFarParent(): Model
206
    {
207
        return $this->farParent;
208
    }
209
210
    /**
211
     * Get the "through" parent model instances.
212
     *
213
     * @return \Illuminate\Database\Eloquent\Model[]
214
     */
215
    public function getThroughParents()
216
    {
217
        return $this->throughParents;
218
    }
219
220
    /**
221
     * Get the foreign keys on the relationship.
222
     *
223
     * @return array
224
     */
225
    public function getForeignKeys()
226
    {
227
        return $this->foreignKeys;
228
    }
229
230
    /**
231
     * Get the local keys on the relationship.
232
     *
233
     * @return array
234
     */
235
    public function getLocalKeys()
236
    {
237
        return $this->localKeys;
238
    }
239
}
240