Passed
Push — master ( 02cb4f...6f5c0c )
by Jonas
09:17 queued 07:20
created

BelongsToThrough::getLocalKeyName()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 1
dl 0
loc 9
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\Concerns\SupportsDefaultModels;
9
use Illuminate\Database\Eloquent\Relations\Relation;
10
use Illuminate\Database\Eloquent\SoftDeletes;
11
use Illuminate\Support\Str;
12
13
class BelongsToThrough extends Relation
14
{
15
    use SupportsDefaultModels;
16
17
    /**
18
     * The column alias for the local key on the first "through" parent model.
19
     *
20
     * @var string
21
     */
22
    public const THROUGH_KEY = 'laravel_through_key';
23
24
    /**
25
     * The "through" parent model instances.
26
     *
27
     * @var \Illuminate\Database\Eloquent\Model[]
28
     */
29
    protected $throughParents;
30
31
    /**
32
     * The foreign key prefix for the first "through" parent model.
33
     *
34
     * @var string
35
     */
36
    protected $prefix;
37
38
    /**
39
     * The custom foreign keys on the relationship.
40
     *
41
     * @var array
42
     */
43
    protected $foreignKeyLookup;
44
45
    /**
46
     * The custom local keys on the relationship.
47
     *
48
     * @var array
49
     */
50
    protected $localKeyLookup;
51
52
    /**
53
     * Create a new belongs to through relationship instance.
54
     *
55
     * @param \Illuminate\Database\Eloquent\Builder $query
56
     * @param \Illuminate\Database\Eloquent\Model $parent
57
     * @param \Illuminate\Database\Eloquent\Model[] $throughParents
58
     * @param string|null $localKey
59
     * @param string $prefix
60
     * @param array $foreignKeyLookup
61
     * @param array $localKeyLookup
62
     *
63
     * @return void
64
     */
65 17
    public function __construct(
66
        Builder $query,
67
        Model $parent,
68
        array $throughParents,
69
        $localKey = null,
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

69
        /** @scrutinizer ignore-unused */ $localKey = null,

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...
70
        $prefix = '',
71
        array $foreignKeyLookup = [],
72
        array $localKeyLookup = []
73
    ) {
74 17
        $this->throughParents = $throughParents;
75 17
        $this->prefix = $prefix;
76 17
        $this->foreignKeyLookup = $foreignKeyLookup;
77 17
        $this->localKeyLookup = $localKeyLookup;
78
79 17
        parent::__construct($query, $parent);
80
    }
81
82
    /**
83
     * Set the base constraints on the relation query.
84
     *
85
     * @return void
86
     */
87 17
    public function addConstraints()
88
    {
89 17
        $this->performJoins();
90
91 17
        if (static::$constraints) {
92 11
            $localValue = $this->parent[$this->getFirstForeignKeyName()];
93
94 11
            $this->query->where($this->getQualifiedFirstLocalKeyName(), '=', $localValue);
95
        }
96
    }
97
98
    /**
99
     * Set the join clauses on the query.
100
     *
101
     * @param \Illuminate\Database\Eloquent\Builder|null $query
102
     * @return void
103
     */
104 17
    protected function performJoins(Builder $query = null)
105
    {
106 17
        $query = $query ?: $this->query;
107
108 17
        foreach ($this->throughParents as $i => $model) {
109 17
            $predecessor = $i > 0 ? $this->throughParents[$i - 1] : $this->related;
110
111 17
            $first = $model->qualifyColumn($this->getForeignKeyName($predecessor));
112
113 17
            $second = $predecessor->qualifyColumn($this->getLocalKeyName($predecessor));
114
115 17
            $query->join($model->getTable(), $first, '=', $second);
116
117 17
            if ($this->hasSoftDeletes($model)) {
118 13
                $column = $model->getQualifiedDeletedAtColumn();
119
120 13
                $query->withGlobalScope(__CLASS__ . ":{$column}", function (Builder $query) use ($column) {
121 10
                    $query->whereNull($column);
122 13
                });
123
            }
124
        }
125
    }
126
127
    /**
128
     * Get the foreign key for a model.
129
     *
130
     * @param \Illuminate\Database\Eloquent\Model|null $model
131
     * @return string
132
     */
133 17
    public function getForeignKeyName(Model $model = null)
134
    {
135 17
        $table = explode(' as ', ($model ?? $this->parent)->getTable())[0];
136
137 17
        if (array_key_exists($table, $this->foreignKeyLookup)) {
138 3
            return $this->foreignKeyLookup[$table];
139
        }
140
141 16
        return Str::singular($table) . '_id';
142
    }
143
144
    /**
145
     * Get the local key for a model.
146
     *
147
     * @param \Illuminate\Database\Eloquent\Model $model
148
     * @return string
149
     */
150 17
    public function getLocalKeyName(Model $model): string
151
    {
152 17
        $table = explode(' as ', $model->getTable())[0];
153
154 17
        if (array_key_exists($table, $this->localKeyLookup)) {
155 1
            return $this->localKeyLookup[$table];
156
        }
157
158 17
        return $model->getKeyName();
159
    }
160
161
    /**
162
     * Determine whether a model uses SoftDeletes.
163
     *
164
     * @param \Illuminate\Database\Eloquent\Model $model
165
     * @return bool
166
     */
167 17
    public function hasSoftDeletes(Model $model)
168
    {
169 17
        return in_array(SoftDeletes::class, class_uses_recursive($model));
170
    }
171
172
    /**
173
     * Set the constraints for an eager load of the relation.
174
     *
175
     * @param array $models
176
     * @return void
177
     */
178 4
    public function addEagerConstraints(array $models)
179
    {
180 4
        $keys = $this->getKeys($models, $this->getFirstForeignKeyName());
181
182 4
        $this->query->whereIn($this->getQualifiedFirstLocalKeyName(), $keys);
183
    }
184
185
    /**
186
     * Initialize the relation on a set of models.
187
     *
188
     * @param \Illuminate\Database\Eloquent\Model[] $models
189
     * @param string $relation
190
     * @return array
191
     */
192 4
    public function initRelation(array $models, $relation)
193
    {
194 4
        foreach ($models as $model) {
195 4
            $model->setRelation($relation, $this->getDefaultFor($model));
196
        }
197
198 4
        return $models;
199
    }
200
201
    /**
202
     * Match the eagerly loaded results to their parents.
203
     *
204
     * @param \Illuminate\Database\Eloquent\Model[] $models
205
     * @param \Illuminate\Database\Eloquent\Collection $results
206
     * @param string $relation
207
     * @return array
208
     */
209 4
    public function match(array $models, Collection $results, $relation)
210
    {
211 4
        $dictionary = $this->buildDictionary($results);
212
213 4
        foreach ($models as $model) {
214 4
            $key = $model[$this->getFirstForeignKeyName()];
215
216 4
            if (isset($dictionary[$key])) {
217 4
                $model->setRelation($relation, $dictionary[$key]);
218
            }
219
        }
220
221 4
        return $models;
222
    }
223
224
    /**
225
     * Build model dictionary keyed by the relation's foreign key.
226
     *
227
     * @param \Illuminate\Database\Eloquent\Collection $results
228
     * @return array
229
     */
230 4
    protected function buildDictionary(Collection $results)
231
    {
232 4
        $dictionary = [];
233
234 4
        foreach ($results as $result) {
235 4
            $dictionary[$result[static::THROUGH_KEY]] = $result;
236
237 4
            unset($result[static::THROUGH_KEY]);
238
        }
239
240 4
        return $dictionary;
241
    }
242
243
    /**
244
     * Get the results of the relationship.
245
     *
246
     * @return \Illuminate\Database\Eloquent\Model
247
     */
248 7
    public function getResults()
249
    {
250 7
        return $this->first() ?: $this->getDefaultFor($this->parent);
251
    }
252
253
    /**
254
     * Execute the query and get the first result.
255
     *
256
     * @param array $columns
257
     * @return \Illuminate\Database\Eloquent\Model|object|static|null
258
     */
259 9
    public function first($columns = ['*'])
260
    {
261 9
        if ($columns === ['*']) {
262 9
            $columns = [$this->related->getTable() . '.*'];
263
        }
264
265 9
        return $this->query->first($columns);
266
    }
267
268
    /**
269
     * Execute the query as a "select" statement.
270
     *
271
     * @param array $columns
272
     * @return \Illuminate\Database\Eloquent\Collection
273
     */
274 4
    public function get($columns = ['*'])
275
    {
276 4
        $columns = $this->query->getQuery()->columns ? [] : $columns;
277
278 4
        if ($columns === ['*']) {
279 4
            $columns = [$this->related->getTable() . '.*'];
280
        }
281
282 4
        $columns[] = $this->getQualifiedFirstLocalKeyName() . ' as ' . static::THROUGH_KEY;
283
284 4
        $this->query->addSelect($columns);
285
286 4
        return $this->query->get();
287
    }
288
289
    /**
290
     * Add the constraints for a relationship query.
291
     *
292
     * @param \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder $query
293
     * @param \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder $parent
294
     * @param array|mixed $columns
295
     * @return \Illuminate\Database\Eloquent\Builder
296
     */
297 3
    public function getRelationExistenceQuery(Builder $query, Builder $parent, $columns = ['*'])
298
    {
299 3
        $this->performJoins($query);
300
301 3
        $foreignKey = $parent->getQuery()->from . '.' . $this->getFirstForeignKeyName();
302
303 3
        return $query->select($columns)->whereColumn(
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...
304 3
            $this->getQualifiedFirstLocalKeyName(),
305 3
            '=',
306 3
            $foreignKey
307 3
        );
308
    }
309
310
    /**
311
     * Restore soft-deleted models.
312
     *
313
     * @param array|string ...$columns
314
     * @return $this
315
     */
316 3
    public function withTrashed(...$columns)
317
    {
318 3
        if (empty($columns)) {
319 1
            $this->query->withTrashed();
320
321 1
            return $this;
322
        }
323
324 2
        if (is_array($columns[0])) {
325 2
            $columns = $columns[0];
326
        }
327
328 2
        foreach ($columns as $column) {
329 2
            $this->query->withoutGlobalScope(__CLASS__ . ":$column");
330
        }
331
332 2
        return $this;
333
    }
334
335
    /**
336
     * Get the "through" parent model instances.
337
     *
338
     * @return \Illuminate\Database\Eloquent\Model[]
339
     */
340 1
    public function getThroughParents()
341
    {
342 1
        return $this->throughParents;
343
    }
344
345
    /**
346
     * Get the foreign key for the first "through" parent model.
347
     *
348
     * @return string
349
     */
350 17
    public function getFirstForeignKeyName()
351
    {
352 17
        return $this->prefix . $this->getForeignKeyName(end($this->throughParents));
353
    }
354
355
    /**
356
     * Get the qualified local key for the first "through" parent model.
357
     *
358
     * @return string
359
     */
360 17
    public function getQualifiedFirstLocalKeyName()
361
    {
362 17
        $lastThroughParent = end($this->throughParents);
363
364 17
        return $lastThroughParent->qualifyColumn($this->getLocalKeyName($lastThroughParent));
365
    }
366
367
    /**
368
     * Make a new related instance for the given model.
369
     *
370
     * @param \Illuminate\Database\Eloquent\Model $parent
371
     * @return \Illuminate\Database\Eloquent\Model
372
     */
373 4
    protected function newRelatedInstanceFor(Model $parent)
0 ignored issues
show
Unused Code introduced by
The parameter $parent 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

373
    protected function newRelatedInstanceFor(/** @scrutinizer ignore-unused */ Model $parent)

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...
374
    {
375 4
        return $this->related->newInstance();
376
    }
377
}
378