Descendants::addExpression()   A
last analyzed

Complexity

Conditions 3
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 3

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 3
c 2
b 0
f 0
dl 0
loc 7
ccs 4
cts 4
cp 1
rs 10
cc 3
nc 2
nop 3
crap 3
1
<?php
2
3
namespace Staudenmeir\LaravelAdjacencyList\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\HasMany;
9
use Illuminate\Database\Query\Expression;
10
use Staudenmeir\EloquentHasManyDeepContracts\Interfaces\ConcatenableRelation;
11
use Staudenmeir\LaravelAdjacencyList\Eloquent\Relations\Traits\Concatenation\IsConcatenableDescendantsRelation;
12
use Staudenmeir\LaravelAdjacencyList\Eloquent\Relations\Traits\IsRecursiveRelation;
13
14
/**
15
 * @template TRelatedModel of \Illuminate\Database\Eloquent\Model
16
 * @extends HasMany<TRelatedModel>
17
 */
18
class Descendants extends HasMany implements ConcatenableRelation
19
{
20
    use IsConcatenableDescendantsRelation;
0 ignored issues
show
Bug introduced by
The trait Staudenmeir\LaravelAdjac...ableDescendantsRelation requires the property $laravel_through_key which is not provided by Staudenmeir\LaravelAdjac...t\Relations\Descendants.
Loading history...
21
    use IsRecursiveRelation {
22
        buildDictionary as baseBuildDictionary;
23
    }
24
25
    /**
26
     * Set the base constraints on the relation query.
27
     *
28
     * @return void
29
     */
30 252
    public function addConstraints()
31
    {
32 252
        if (static::$constraints) {
33 135
            $constraint = function (Builder $query) {
34 135
                if ($this->andSelf) {
35 50
                    $query->where($this->getQualifiedLocalKeyName(), '=', $this->getParentKey());
36
                } else {
37 85
                    $query->where($this->foreignKey, '=', $this->getParentKey())
38 85
                        ->whereNotNull($this->foreignKey);
39
                }
40 135
            };
41
42 135
            $this->addExpression($constraint);
43
        }
44
    }
45
46
    /**
47
     * Set the constraints for an eager load of the relation.
48
     *
49
     * @param array $models
50
     * @return void
51
     */
52 78
    public function addEagerConstraints(array $models)
53
    {
54 78
        $whereIn = $this->whereInMethod($this->parent, $this->localKey);
55
56 78
        $column = $this->andSelf ? $this->getQualifiedLocalKeyName() : $this->foreignKey;
57
58 78
        $keys = $this->getKeys($models, $this->localKey);
59
60 78
        $constraint = function (Builder $query) use ($whereIn, $column, $keys) {
61 78
            $query->$whereIn($column, $keys);
62 78
        };
63
64 78
        $this->addExpression($constraint);
65
    }
66
67
    /**
68
     * Match the eagerly loaded results to their parents.
69
     *
70
     * @param array $models
71
     * @param \Illuminate\Database\Eloquent\Collection $results
72
     * @param string $relation
73
     * @return array
74
     */
75 36
    public function match(array $models, Collection $results, $relation)
76
    {
77 36
        $dictionary = $this->buildDictionary($results);
78
79 36
        foreach ($models as $model) {
80 36
            $key = $model->{$this->localKey};
81
82 36
            if (isset($dictionary[$key])) {
83 36
                $value = $this->related->newCollection($dictionary[$key]);
84
85 36
                $model->setRelation($relation, $value);
86
            }
87
        }
88
89 36
        return $models;
90
    }
91
92
    /**
93
     * Build model dictionary.
94
     *
95
     * @param \Illuminate\Database\Eloquent\Collection $results
96
     * @return array
97
     */
98 36
    protected function buildDictionary(Collection $results)
99
    {
100 36
        if ($this->andSelf) {
101 24
            return $this->baseBuildDictionary($results);
102
        }
103
104 12
        $dictionary = $results->keyBy($this->localKey);
105
106 12
        $foreignKey = $this->getForeignKeyName();
107
108 12
        return $results->mapToDictionary(function (Model $result) use ($dictionary, $foreignKey) {
109 12
            if ($result->hasNestedPath()) {
110 12
                $key = $dictionary[$result->getFirstPathSegment()]->{$foreignKey};
111
            } else {
112 12
                $key = $result->{$foreignKey};
113
            }
114
115 12
            return [$key => $result];
116 12
        })->all();
117
    }
118
119
    /**
120
     * Add the constraints for a relationship query.
121
     *
122
     * @param \Illuminate\Database\Eloquent\Builder $query
123
     * @param \Illuminate\Database\Eloquent\Builder $parentQuery
124
     * @param array|mixed $columns
125
     * @return \Illuminate\Database\Eloquent\Builder
126
     */
127 42
    public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*'])
128
    {
129 42
        if ($query->getQuery()->from === $parentQuery->getQuery()->from) {
130 24
            return $this->getRelationExistenceQueryForSelfRelation($query, $parentQuery, $columns);
131
        }
132
133 18
        $first = $this->andSelf
134 12
            ? $query->getQuery()->from.'.'.$this->localKey
0 ignored issues
show
Bug introduced by
Are you sure $query->getQuery()->from of type Illuminate\Database\Query\Expression|string can be used in concatenation? ( Ignorable by Annotation )

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

134
            ? /** @scrutinizer ignore-type */ $query->getQuery()->from.'.'.$this->localKey
Loading history...
135 6
            : $this->foreignKey;
136
137 18
        $constraint = function (Builder $query) use ($first) {
138 18
            $query->whereColumn(
139 18
                $first,
140 18
                '=',
141 18
                $this->getQualifiedParentKeyName()
142 18
            );
143 18
        };
144
145 18
        return $this->addExpression($constraint, $query->select($columns));
0 ignored issues
show
Bug introduced by
It seems like $query->select($columns) can also be of type Illuminate\Database\Query\Builder; however, parameter $query of Staudenmeir\LaravelAdjac...ndants::addExpression() does only seem to accept Illuminate\Database\Eloquent\Builder|null, 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

145
        return $this->addExpression($constraint, /** @scrutinizer ignore-type */ $query->select($columns));
Loading history...
146
    }
147
148
    /**
149
     * Add the constraints for a relationship query on the same table.
150
     *
151
     * @param \Illuminate\Database\Eloquent\Builder $query
152
     * @param \Illuminate\Database\Eloquent\Builder $parentQuery
153
     * @param array|mixed $columns
154
     * @return \Illuminate\Database\Eloquent\Builder
155
     */
156 24
    public function getRelationExistenceQueryForSelfRelation(Builder $query, Builder $parentQuery, $columns = ['*'])
157
    {
158 24
        if ($columns instanceof Expression) {
159 12
            $columns = $this->replaceTableHash($query, $columns);
160
        }
161
162 24
        $table = $this->getRelationCountHash();
163
164 24
        $from = $query->getModel()->getTable().' as '.$table;
165
166 24
        $query->getModel()->setTable($table);
167
168 24
        $first = $this->andSelf
169 15
            ? $table.'.'.$this->localKey
170 9
            : $table.'.'.$this->getForeignKeyName();
171
172 24
        $constraint = function (Builder $query) use ($first) {
173 24
            $query->whereColumn(
174 24
                $first,
175 24
                '=',
176 24
                $this->getQualifiedParentKeyName()
177 24
            );
178 24
        };
179
180 24
        return $this->addExpression($constraint, $query->select($columns), $from);
0 ignored issues
show
Bug introduced by
It seems like $query->select($columns) can also be of type Illuminate\Database\Query\Builder; however, parameter $query of Staudenmeir\LaravelAdjac...ndants::addExpression() does only seem to accept Illuminate\Database\Eloquent\Builder|null, 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

180
        return $this->addExpression($constraint, /** @scrutinizer ignore-type */ $query->select($columns), $from);
Loading history...
181
    }
182
183
    /**
184
     * Add a recursive expression to the query.
185
     *
186
     * @param callable $constraint
187
     * @param \Illuminate\Database\Eloquent\Builder|null $query
188
     * @param string|null $from
189
     * @return \Illuminate\Database\Eloquent\Builder
190
     */
191 187
    protected function addExpression(callable $constraint, ?Builder $query = null, $from = null)
192
    {
193 187
        $query = $query ?: $this->query;
194
195 187
        $initialDepth = $this->andSelf ? 0 : 1;
196
197 187
        return $query->withRelationshipExpression('desc', $constraint, $initialDepth, $from);
198
    }
199
}
200