Completed
Push — master ( 2d3c21...c88aa3 )
by Jonas
04:01
created

BelongsToThrough   A

Complexity

Total Complexity 25

Size/Duplication

Total Lines 272
Duplicated Lines 0 %

Test Coverage

Coverage 97.47%

Importance

Changes 0
Metric Value
eloc 56
dl 0
loc 272
ccs 77
cts 79
cp 0.9747
rs 10
c 0
b 0
f 0
wmc 25

10 Methods

Rating   Name   Duplication   Size   Complexity  
A hasSoftDeletes() 0 3 1
A __construct() 0 7 1
A addConstraints() 0 10 2
A addEagerConstraints() 0 9 1
A buildDictionary() 0 11 2
A match() 0 13 3
A getRelationCountQuery() 0 3 1
A getRelationQuery() 0 3 1
A getResults() 0 3 1
A initRelation() 0 7 2
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 20
    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 20
        $this->throughParents = $throughParents;
57 20
        $this->prefix = $prefix;
58 20
        $this->foreignKeyLookup = $foreignKeyLookup;
59
60 20
        parent::__construct($query, $parent);
61 20
    }
62
63
    /**
64
     * Set the base constraints on the relation query.
65
     *
66
     * @return void
67
     */
68 20
    public function addConstraints()
69
    {
70 20
        $this->query->select([$this->related->getTable().'.*']);
71
72 20
        $this->performJoins();
73
74 20
        if (static::$constraints) {
75 10
            $localValue = $this->parent[$this->getFirstForeignKeyName()];
76
77 10
            $this->query->where($this->getQualifiedFirstLocalKeyName(), '=', $localValue);
78 5
        }
79 20
    }
80
81
    /**
82
     * Set the join clauses on the query.
83
     *
84
     * @param  \Illuminate\Database\Eloquent\Builder|null  $query
85
     * @return void
86
     */
87 20
    protected function performJoins(Builder $query = null)
88
    {
89 20
        $query = $query ?: $this->query;
90
91 20
        foreach ($this->throughParents as $i => $model) {
92 20
            $table = $model->getTable();
93
94 20
            $predecessor = $i > 0 ? $this->throughParents[$i - 1] : $this->related;
95
96 20
            $first = $table.'.'.$this->getForeignKeyName($predecessor);
97
98 20
            $second = $predecessor->getQualifiedKeyName();
99
100 20
            $query->join($table, $first, '=', $second);
101
102 20
            if ($this->hasSoftDeletes($model)) {
103 20
                $this->query->whereNull($model->getQualifiedDeletedAtColumn());
104 10
            }
105 10
        }
106 20
    }
107
108
    /**
109
     * Get the foreign key for a model.
110
     *
111
     * @param  \Illuminate\Database\Eloquent\Model  $model
112
     * @return string
113
     */
114 20
    protected function getForeignKeyName(Model $model)
115
    {
116 20
        $table = $model->getTable();
117
118 20
        if (array_key_exists($table, $this->foreignKeyLookup)) {
119 2
            return $this->foreignKeyLookup[$table];
120
        }
121
122 20
        return Str::singular($table).'_id';
123
    }
124
125
    /**
126
     * Determine whether a model uses SoftDeletes.
127
     *
128
     * @param  \Illuminate\Database\Eloquent\Model  $model
129
     * @return bool
130
     */
131 20
    public function hasSoftDeletes(Model $model)
132
    {
133 20
        return in_array(SoftDeletes::class, class_uses_recursive(get_class($model)));
134
    }
135
136
    /**
137
     * Set the constraints for an eager load of the relation.
138
     *
139
     * @param  array  $models
140
     * @return void
141
     */
142 6
    public function addEagerConstraints(array $models)
143
    {
144 6
        $this->query->addSelect([
145 6
            $this->getQualifiedFirstLocalKeyName().' as '.static::THROUGH_KEY,
146 3
        ]);
147
148 6
        $keys = $this->getKeys($models, $this->getFirstForeignKeyName());
149
150 6
        $this->query->whereIn($this->getQualifiedFirstLocalKeyName(), $keys);
151 6
    }
152
153
    /**
154
     * Initialize the relation on a set of models.
155
     *
156
     * @param  \Illuminate\Database\Eloquent\Model[]  $models
157
     * @param  string  $relation
158
     * @return array
159
     */
160 6
    public function initRelation(array $models, $relation)
161
    {
162 6
        foreach ($models as $model) {
163 6
            $model->setRelation($relation, null);
164 3
        }
165
166 6
        return $models;
167
    }
168
169
    /**
170
     * Match the eagerly loaded results to their parents.
171
     *
172
     * @param  \Illuminate\Database\Eloquent\Model[]  $models
173
     * @param  \Illuminate\Database\Eloquent\Collection  $results
174
     * @param  string  $relation
175
     * @return array
176
     */
177 6
    public function match(array $models, Collection $results, $relation)
178
    {
179 6
        $dictionary = $this->buildDictionary($results);
180
181 6
        foreach ($models as $model) {
182 6
            $key = $model[$this->getFirstForeignKeyName()];
183
184 6
            if (isset($dictionary[$key])) {
185 6
                $model->setRelation($relation, $dictionary[$key]);
186 3
            }
187 3
        }
188
189 6
        return $models;
190
    }
191
192
    /**
193
     * Build model dictionary keyed by the relation's foreign key.
194
     *
195
     * @param  \Illuminate\Database\Eloquent\Collection  $results
196
     * @return array
197
     */
198 6
    protected function buildDictionary(Collection $results)
199
    {
200 6
        $dictionary = [];
201
202 6
        foreach ($results as $result) {
203 6
            $dictionary[$result[static::THROUGH_KEY]] = $result;
204
205 6
            unset($result[static::THROUGH_KEY]);
206 3
        }
207
208 6
        return $dictionary;
209
    }
210
211
    /**
212
     * Get the results of the relationship.
213
     *
214
     * @return \Illuminate\Database\Eloquent\Model
215
     */
216 10
    public function getResults()
217
    {
218 10
        return $this->query->first();
219
    }
220
221
    /**
222
     * Add the constraints for a relationship query.
223
     *
224
     * @param  \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder  $query
225
     * @param  \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder  $parent
226
     * @param  array|mixed  $columns
227
     * @return \Illuminate\Database\Eloquent\Builder
228
     */
229 4
    public function getRelationExistenceQuery(Builder $query, Builder $parent, $columns = ['*'])
230
    {
231 4
        $this->performJoins($query);
232
233 4
        $foreignKey = $parent->getQuery()->from.'.'.$this->getFirstForeignKeyName();
234
235 4
        $foreignKey = new Expression($query->getQuery()->getGrammar()->wrap($foreignKey));
236
237 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...
238 4
            $this->getQualifiedFirstLocalKeyName(), '=', $foreignKey
239 2
        );
240
    }
241
242
    /**
243
     * Add the constraints for a relationship count query.
244
     *
245
     * @param  \Illuminate\Database\Eloquent\Builder  $query
246
     * @param  \Illuminate\Database\Eloquent\Builder  $parent
247
     * @return \Illuminate\Database\Eloquent\Builder
248
     */
249 2
    public function getRelationCountQuery(Builder $query, Builder $parent)
250
    {
251 2
        return $this->getRelationExistenceQuery($query, $parent, new Expression('count(*)'));
252
    }
253
254
    /**
255
     * Add the constraints for a relationship query.
256
     *
257
     * @param  \Illuminate\Database\Eloquent\Builder  $query
258
     * @param  \Illuminate\Database\Eloquent\Builder  $parent
259
     * @param  array|mixed $columns
260
     * @return \Illuminate\Database\Eloquent\Builder
261
     */
262
    public function getRelationQuery(Builder $query, Builder $parent, $columns = ['*'])
263
    {
264
        return $this->getRelationExistenceQuery($query, $parent, $columns);
265
    }
266
267
    /**
268
     * Get the foreign key for the first "through" parent model.
269
     *
270
     * @return string
271
     */
272 20
    public function getFirstForeignKeyName()
273
    {
274 20
        return $this->prefix.$this->getForeignKeyName(end($this->throughParents));
275
    }
276
277
    /**
278
     * Get the qualified local key for the first "through" parent model.
279
     *
280
     * @return string
281
     */
282 20
    public function getQualifiedFirstLocalKeyName()
283
    {
284 20
        return end($this->throughParents)->getQualifiedKeyName();
285
    }
286
}
287