Completed
Push — master ( 5c78e4...7dde63 )
by Jonas
03:19
created

BelongsToThrough::first()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 1
dl 0
loc 7
ccs 5
cts 5
cp 1
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Znck\Eloquent\Relations;
4
5
use Illuminate\Database\Eloquent\Builder;
6
use Illuminate\Database\Eloquent\Collection;
7
use Illuminate\Database\Eloquent\Model;
8
use Illuminate\Database\Eloquent\Relations\Relation;
9
use Illuminate\Database\Eloquent\SoftDeletes;
10
use Illuminate\Database\Query\Expression;
11
use Illuminate\Support\Str;
12
13
class BelongsToThrough extends Relation
14
{
15
    /**
16
     * The column alias for the local key on the first "through" parent model.
17
     *
18
     * @var string
19
     */
20
    const THROUGH_KEY = 'laravel_through_key';
21
22
    /**
23
     * The "through" parent model instances.
24
     *
25
     * @var \Illuminate\Database\Eloquent\Model[]
26
     */
27
    protected $throughParents;
28
29
    /**
30
     * The foreign key prefix for the first "through" parent model.
31
     *
32
     * @var string
33
     */
34
    protected $prefix;
35
36
    /**
37
     * The custom foreign keys on the relationship.
38
     *
39
     * @var array
40
     */
41
    protected $foreignKeyLookup;
42
43
    /**
44
     * Create a new belongs to through relationship instance.
45
     *
46
     * @param \Illuminate\Database\Eloquent\Builder $query
47
     * @param \Illuminate\Database\Eloquent\Model $parent
48
     * @param \Illuminate\Database\Eloquent\Model[] $throughParents
49
     * @param string|null $localKey
50
     * @param string $prefix
51
     * @param array $foreignKeyLookup
52
     * @return void
53
     */
54 24
    public function __construct(Builder $query, Model $parent, array $throughParents, $localKey = null, $prefix = '', array $foreignKeyLookup = [])
0 ignored issues
show
Unused Code introduced by
The parameter $localKey is not used and could be removed. ( Ignorable by Annotation )

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

54
    public function __construct(Builder $query, Model $parent, array $throughParents, /** @scrutinizer ignore-unused */ $localKey = null, $prefix = '', array $foreignKeyLookup = [])

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
55
    {
56 24
        $this->throughParents = $throughParents;
57 24
        $this->prefix = $prefix;
58 24
        $this->foreignKeyLookup = $foreignKeyLookup;
59
60 24
        parent::__construct($query, $parent);
61 24
    }
62
63
    /**
64
     * Set the base constraints on the relation query.
65
     *
66
     * @return void
67
     */
68 24
    public function addConstraints()
69
    {
70 24
        $this->performJoins();
71
72 24
        if (static::$constraints) {
73 14
            $localValue = $this->parent[$this->getFirstForeignKeyName()];
74
75 14
            $this->query->where($this->getQualifiedFirstLocalKeyName(), '=', $localValue);
76 7
        }
77 24
    }
78
79
    /**
80
     * Set the join clauses on the query.
81
     *
82
     * @param \Illuminate\Database\Eloquent\Builder|null $query
83
     * @return void
84
     */
85 24
    protected function performJoins(Builder $query = null)
86
    {
87 24
        $query = $query ?: $this->query;
88
89 24
        foreach ($this->throughParents as $i => $model) {
90 24
            $table = $model->getTable();
91
92 24
            $predecessor = $i > 0 ? $this->throughParents[$i - 1] : $this->related;
93
94 24
            $first = $table.'.'.$this->getForeignKeyName($predecessor);
95
96 24
            $second = $predecessor->getQualifiedKeyName();
97
98 24
            $query->join($table, $first, '=', $second);
99
100 24
            if ($this->hasSoftDeletes($model)) {
101 20
                $this->query->whereNull($model->getQualifiedDeletedAtColumn());
102 10
            }
103 12
        }
104 24
    }
105
106
    /**
107
     * Get the foreign key for a model.
108
     *
109
     * @param \Illuminate\Database\Eloquent\Model $model
110
     * @return string
111
     */
112 24
    protected function getForeignKeyName(Model $model)
113
    {
114 24
        $table = $model->getTable();
115
116 24
        if (array_key_exists($table, $this->foreignKeyLookup)) {
117 2
            return $this->foreignKeyLookup[$table];
118
        }
119
120 24
        return Str::singular($table).'_id';
121
    }
122
123
    /**
124
     * Determine whether a model uses SoftDeletes.
125
     *
126
     * @param \Illuminate\Database\Eloquent\Model $model
127
     * @return bool
128
     */
129 24
    public function hasSoftDeletes(Model $model)
130
    {
131 24
        return in_array(SoftDeletes::class, class_uses_recursive(get_class($model)));
132
    }
133
134
    /**
135
     * Set the constraints for an eager load of the relation.
136
     *
137
     * @param array $models
138
     * @return void
139
     */
140 6
    public function addEagerConstraints(array $models)
141
    {
142 6
        $keys = $this->getKeys($models, $this->getFirstForeignKeyName());
143
144 6
        $this->query->whereIn($this->getQualifiedFirstLocalKeyName(), $keys);
145 6
    }
146
147
    /**
148
     * Initialize the relation on a set of models.
149
     *
150
     * @param \Illuminate\Database\Eloquent\Model[] $models
151
     * @param string $relation
152
     * @return array
153
     */
154 6
    public function initRelation(array $models, $relation)
155
    {
156 6
        foreach ($models as $model) {
157 6
            $model->setRelation($relation, null);
158 3
        }
159
160 6
        return $models;
161
    }
162
163
    /**
164
     * Match the eagerly loaded results to their parents.
165
     *
166
     * @param \Illuminate\Database\Eloquent\Model[] $models
167
     * @param \Illuminate\Database\Eloquent\Collection $results
168
     * @param string $relation
169
     * @return array
170
     */
171 6
    public function match(array $models, Collection $results, $relation)
172
    {
173 6
        $dictionary = $this->buildDictionary($results);
174
175 6
        foreach ($models as $model) {
176 6
            $key = $model[$this->getFirstForeignKeyName()];
177
178 6
            if (isset($dictionary[$key])) {
179 6
                $model->setRelation($relation, $dictionary[$key]);
180 3
            }
181 3
        }
182
183 6
        return $models;
184
    }
185
186
    /**
187
     * Build model dictionary keyed by the relation's foreign key.
188
     *
189
     * @param \Illuminate\Database\Eloquent\Collection $results
190
     * @return array
191
     */
192 6
    protected function buildDictionary(Collection $results)
193
    {
194 6
        $dictionary = [];
195
196 6
        foreach ($results as $result) {
197 6
            $dictionary[$result[static::THROUGH_KEY]] = $result;
198
199 6
            unset($result[static::THROUGH_KEY]);
200 3
        }
201
202 6
        return $dictionary;
203
    }
204
205
    /**
206
     * Get the results of the relationship.
207
     *
208
     * @return \Illuminate\Database\Eloquent\Model
209
     */
210 10
    public function getResults()
211
    {
212 10
        return $this->first();
213
    }
214
215
    /**
216
     * Execute the query and get the first result.
217
     *
218
     * @param array $columns
219
     * @return \Illuminate\Database\Eloquent\Model|object|static|null
220
     */
221 14
    public function first($columns = ['*'])
222
    {
223 14
        if ($columns === ['*']) {
224 14
            $columns = [$this->related->getTable().'.*'];
225 7
        }
226
227 14
        return $this->query->first($columns);
228
    }
229
230
    /**
231
     * Execute the query as a "select" statement.
232
     *
233
     * @param array $columns
234
     * @return \Illuminate\Database\Eloquent\Collection
235
     */
236 6
    public function get($columns = ['*'])
237
    {
238 6
        $columns = $this->query->getQuery()->columns ? [] : $columns;
239
240 6
        if ($columns === ['*']) {
241 3
            $columns = [$this->related->getTable().'.*'];
242 3
        }
243
244 6
        $columns[] = $this->getQualifiedFirstLocalKeyName().' as '.static::THROUGH_KEY;
245
246 6
        $this->query->addSelect($columns);
247
248 6
        return $this->query->get();
249
    }
250
251
    /**
252
     * Add the constraints for a relationship query.
253
     *
254
     * @param \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder $query
255
     * @param \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder $parent
256
     * @param array|mixed $columns
257
     * @return \Illuminate\Database\Eloquent\Builder
258
     */
259 4
    public function getRelationExistenceQuery(Builder $query, Builder $parent, $columns = ['*'])
260
    {
261 4
        $this->performJoins($query);
262
263 4
        $foreignKey = $parent->getQuery()->from.'.'.$this->getFirstForeignKeyName();
264
265 4
        $foreignKey = new Expression($query->getQuery()->getGrammar()->wrap($foreignKey));
266
267 4
        return $query->select($columns)->where(
0 ignored issues
show
Bug Best Practice introduced by
The expression return $query->select($c...me(), '=', $foreignKey) also could return the type Illuminate\Database\Query\Builder which is incompatible with the documented return type Illuminate\Database\Eloquent\Builder.
Loading history...
268 4
            $this->getQualifiedFirstLocalKeyName(), '=', $foreignKey
269 2
        );
270
    }
271
272
    /**
273
     * Add the constraints for a relationship count query.
274
     *
275
     * @param \Illuminate\Database\Eloquent\Builder $query
276
     * @param \Illuminate\Database\Eloquent\Builder $parent
277
     * @return \Illuminate\Database\Eloquent\Builder
278
     */
279 2
    public function getRelationCountQuery(Builder $query, Builder $parent)
280
    {
281 2
        return $this->getRelationExistenceQuery($query, $parent, new Expression('count(*)'));
282
    }
283
284
    /**
285
     * Add the constraints for a relationship query.
286
     *
287
     * @param \Illuminate\Database\Eloquent\Builder $query
288
     * @param \Illuminate\Database\Eloquent\Builder $parent
289
     * @param array|mixed $columns
290
     * @return \Illuminate\Database\Eloquent\Builder
291
     */
292
    public function getRelationQuery(Builder $query, Builder $parent, $columns = ['*'])
293
    {
294
        return $this->getRelationExistenceQuery($query, $parent, $columns);
295
    }
296
297
    /**
298
     * Restore soft-deleted models.
299
     *
300
     * @param array|string ...$columns
301
     * @return $this
302
     */
303 4
    public function withTrashed(...$columns)
304
    {
305 4
        if (empty($columns)) {
306 2
            $this->query->withTrashed();
307
308 2
            return $this;
309
        }
310
311 2
        if (is_array($columns[0])) {
312 2
            $columns = $columns[0];
313 1
        }
314
315 2
        $this->query->getQuery()->wheres = collect($this->query->getQuery()->wheres)
316 2
            ->reject(function ($where) use ($columns) {
317 2
                return $where['type'] === 'Null' && in_array($where['column'], $columns);
318 2
            })->values()->all();
319
320 2
        return $this;
321
    }
322
323
    /**
324
     * Get the foreign key for the first "through" parent model.
325
     *
326
     * @return string
327
     */
328 24
    public function getFirstForeignKeyName()
329
    {
330 24
        return $this->prefix.$this->getForeignKeyName(end($this->throughParents));
331
    }
332
333
    /**
334
     * Get the qualified local key for the first "through" parent model.
335
     *
336
     * @return string
337
     */
338 24
    public function getQualifiedFirstLocalKeyName()
339
    {
340 24
        return end($this->throughParents)->getQualifiedKeyName();
341
    }
342
}
343