Issues (36)

src/HasManyDeep.php (7 issues)

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
 * @extends \Illuminate\Database\Eloquent\Relations\HasManyThrough<TRelatedModel>
22
 */
23
class HasManyDeep extends HasManyThrough implements ConcatenableRelation
24
{
25
    use ExecutesQueries;
26
    use HasEagerLoading;
27
    use HasExistenceQueries;
0 ignored issues
show
The trait Staudenmeir\EloquentHasM...its\HasExistenceQueries requires the property $from which is not provided by Staudenmeir\EloquentHasManyDeep\HasManyDeep.
Loading history...
28
    use IsConcatenable;
29
    use IsCustomizable;
30
    use JoinsThroughParents;
0 ignored issues
show
The trait Staudenmeir\EloquentHasM...its\JoinsThroughParents requires the property $columns which is not provided by Staudenmeir\EloquentHasManyDeep\HasManyDeep.
Loading history...
31
    use RetrievesIntermediateTables;
32
    use SupportsCompositeKeys;
0 ignored issues
show
The trait Staudenmeir\EloquentHasM...s\SupportsCompositeKeys requires the property $columns which is not provided by Staudenmeir\EloquentHasManyDeep\HasManyDeep.
Loading history...
33
34
    /**
35
     * The "through" parent model instances.
36
     *
37
     * @var \Illuminate\Database\Eloquent\Model[]
38
     */
39
    protected $throughParents;
40
41
    /**
42
     * The foreign keys on the relationship.
43
     *
44
     * @var array
45
     */
46
    protected $foreignKeys;
47
48
    /**
49
     * The local keys on the relationship.
50
     *
51
     * @var array
52
     */
53
    protected $localKeys;
54
55
    /**
56
     * Create a new has many deep relationship instance.
57
     *
58
     * @param \Illuminate\Database\Eloquent\Builder $query
59
     * @param \Illuminate\Database\Eloquent\Model $farParent
60
     * @param \Illuminate\Database\Eloquent\Model[] $throughParents
61
     * @param array $foreignKeys
62
     * @param array $localKeys
63
     * @return void
64
     */
65
    public function __construct(Builder $query, Model $farParent, array $throughParents, array $foreignKeys, array $localKeys)
66
    {
67 226
        $this->throughParents = $throughParents;
68
        $this->foreignKeys = $foreignKeys;
69 226
        $this->localKeys = $localKeys;
70 226
71 226
        $firstKey = is_array($foreignKeys[0])
72
            ? $foreignKeys[0][1]
73 226
            : ($this->hasLeadingCompositeKey() ? $foreignKeys[0]->columns[0] : $foreignKeys[0]);
74 10
75 216
        $localKey = $this->hasLeadingCompositeKey() ? $localKeys[0]->columns[0] : $localKeys[0];
76
77 226
        parent::__construct($query, $farParent, $throughParents[0], $firstKey, $foreignKeys[1], $localKey, $localKeys[1]);
78
    }
79 226
80
    /**
81
     * Set the base constraints on the relation query.
82
     *
83
     * @return void
84
     */
85
    public function addConstraints()
86
    {
87 226
        if ($this->firstKey instanceof Closure || $this->localKey instanceof Closure) {
0 ignored issues
show
$this->localKey is never a sub-type of Closure.
Loading history...
88
            $this->performJoin();
89 226
        } else {
90 53
            parent::addConstraints();
91
        }
92 173
93
        if (static::$constraints) {
94
            if ($this->firstKey instanceof Closure) {
0 ignored issues
show
$this->firstKey is never a sub-type of Closure.
Loading history...
95 226
                ($this->firstKey)($this->query);
96 130
            } elseif ($this->localKey instanceof Closure) {
0 ignored issues
show
$this->localKey is never a sub-type of Closure.
Loading history...
97 19
                ($this->localKey)($this->query);
98 111
            } elseif (is_array($this->foreignKeys[0])) {
99 2
                $this->query->where(
100 109
                    $this->throughParent->qualifyColumn($this->foreignKeys[0][0]),
101 4
                    '=',
102 4
                    $this->farParent->getMorphClass()
103 4
                );
104 4
            } elseif ($this->hasLeadingCompositeKey()) {
105 4
                $this->addConstraintsWithCompositeKey();
106 105
            }
107 10
        }
108
    }
109
110
    /**
111
     * Set the join clauses on the query.
112
     *
113
     * @param \Illuminate\Database\Eloquent\Builder|null $query
114
     * @return void
115
     */
116
    protected function performJoin(Builder $query = null)
117
    {
118 226
        $query = $query ?: $this->query;
119
120 226
        $throughParents = array_reverse($this->throughParents);
121
        $foreignKeys = array_reverse($this->foreignKeys);
122 226
        $localKeys = array_reverse($this->localKeys);
123 226
124 226
        $segments = explode(' as ', $query->getQuery()->from);
0 ignored issues
show
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

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