Passed
Push — master ( 3263b5...c3fb84 )
by Jonas
15:03 queued 18s
created

getRelationExistenceQueryWithCompositeKey()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 6
c 1
b 0
f 0
nc 2
nop 1
dl 0
loc 9
rs 10
1
<?php
2
3
namespace Staudenmeir\EloquentJsonRelations\Relations\Traits\CompositeKeys;
4
5
use Illuminate\Database\Eloquent\Builder;
6
use Illuminate\Database\Eloquent\Collection;
7
use Illuminate\Database\Eloquent\Model;
8
use Illuminate\Support\Collection as BaseCollection;
9
10
trait SupportsHasManyJsonCompositeKeys
11
{
12
    /**
13
     * Determine whether the relationship has a composite key.
14
     *
15
     * @return bool
16
     */
17
    protected function hasCompositeKey(): bool
18
    {
19
        return is_array($this->foreignKey);
20
    }
21
22
    /**
23
     * Set the base constraints on the relation query for a composite key.
24
     *
25
     * @return void
26
     */
27
    protected function addConstraintsWithCompositeKey(): void
28
    {
29
        $columns = array_slice($this->localKey, 1);
30
31
        foreach ($columns as $column) {
32
            $this->query->where(
33
                $this->related->qualifyColumn($column),
34
                '=',
35
                $this->parent->$column
36
            );
37
        }
38
    }
39
40
    /**
41
     * Set the constraints for an eager load of the relation for a composite key.
42
     *
43
     * @param array $models
44
     * @return void
45
     */
46
    protected function addEagerConstraintsWithCompositeKey(array $models): void
47
    {
48
        $keys = (new BaseCollection($models))->map(
0 ignored issues
show
Bug introduced by
$models of type array is incompatible with the type Illuminate\Contracts\Support\Arrayable expected by parameter $items of Illuminate\Support\Collection::__construct(). ( Ignorable by Annotation )

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

48
        $keys = (new BaseCollection(/** @scrutinizer ignore-type */ $models))->map(
Loading history...
49
            function (Model $model) {
50
                return array_map(
51
                    fn (string $column) => $model[$column],
52
                    $this->localKey
53
                );
54
            }
55
        )->values()->unique(null, true)->all();
56
57
        $this->query->where(
58
            function (Builder $query) use ($keys) {
59
                foreach ($keys as $key) {
60
                    $query->orWhere(
61
                        function (Builder $query) use ($key) {
62
                            foreach ($this->foreignKey as $i => $column) {
63
                                if ($i === 0) {
64
                                    $this->whereJsonContainsOrMemberOf(
0 ignored issues
show
Bug introduced by
It seems like whereJsonContainsOrMemberOf() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

64
                                    $this->/** @scrutinizer ignore-call */ 
65
                                           whereJsonContainsOrMemberOf(
Loading history...
65
                                        $query,
66
                                        $this->path,
67
                                        $key[$i],
68
                                        fn ($parentKey) => $this->parentKeyToArray($parentKey)
0 ignored issues
show
Bug introduced by
It seems like parentKeyToArray() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

68
                                        fn ($parentKey) => $this->/** @scrutinizer ignore-call */ parentKeyToArray($parentKey)
Loading history...
69
                                    );
70
                                } else {
71
                                    $query->where(
72
                                        $this->related->qualifyColumn($column),
73
                                        '=',
74
                                        $key[$i]
75
                                    );
76
                                }
77
                            }
78
                        }
79
                    );
80
                }
81
            }
82
        );
83
    }
84
85
    /**
86
     * Match the eagerly loaded results to their parents for a composite key.
87
     *
88
     * @param array $models
89
     * @param \Illuminate\Database\Eloquent\Collection $results
90
     * @param string $relation
91
     * @return array
92
     */
93
    protected function matchWithCompositeKey(array $models, Collection $results, string $relation): array
94
    {
95
        $dictionary = $this->buildDictionaryWithCompositeKey($results);
96
97
        foreach ($models as $model) {
98
            $values = array_map(
99
                fn ($key) => $model->$key,
100
                $this->localKey
101
            );
102
103
            $key = implode("\0", $values);
104
105
            if (isset($dictionary[$key])) {
106
                $model->setRelation(
107
                    $relation,
108
                    $this->getRelationValue($dictionary, $key, 'many')
0 ignored issues
show
Bug introduced by
It seems like getRelationValue() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

108
                    $this->/** @scrutinizer ignore-call */ 
109
                           getRelationValue($dictionary, $key, 'many')
Loading history...
109
                );
110
            }
111
        }
112
113
        return $models;
114
    }
115
116
    /**
117
     * Build model dictionary keyed by the relation's composite foreign key.
118
     *
119
     * @param \Illuminate\Database\Eloquent\Collection $results
120
     * @return array
121
     */
122
    protected function buildDictionaryWithCompositeKey(Collection $results): array
123
    {
124
        $dictionary = [];
125
126
        $foreignKey = $this->getForeignKeyName();
0 ignored issues
show
Bug introduced by
It seems like getForeignKeyName() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

126
        /** @scrutinizer ignore-call */ 
127
        $foreignKey = $this->getForeignKeyName();
Loading history...
127
128
        $additionalColumns = $this->getAdditionalForeignKeyNames();
129
130
        foreach ($results as $result) {
131
            $additionalValues = array_map(
132
                fn (string $column) => $result->getAttribute($column),
133
                $additionalColumns
134
            );
135
136
            foreach($result->$foreignKey as $value) {
137
                $values = [$value, ...$additionalValues];
138
139
                $key = implode("\0", $values);
140
141
                $dictionary[$key][] = $result;
142
            }
143
        }
144
145
        return $dictionary;
146
    }
147
148
    /**
149
     * Add the constraints for a relationship query for a composite key.
150
     *
151
     * @param \Illuminate\Database\Eloquent\Builder $query
152
     * @return void
153
     */
154
    public function getRelationExistenceQueryWithCompositeKey(Builder $query): void
155
    {
156
        $columns = $this->getAdditionalForeignKeyNames();
157
158
        foreach ($columns as $i => $column) {
159
            $query->whereColumn(
160
                $this->parent->qualifyColumn($column),
161
                '=',
162
                $query->qualifyColumn($this->localKey[$i])
163
            );
164
        }
165
    }
166
167
    /**
168
     * Get the plain additional foreign keys.
169
     *
170
     * @return array
171
     */
172
    protected function getAdditionalForeignKeyNames(): array
173
    {
174
        $names = [];
175
176
        $columns = array_slice($this->foreignKey, 1, preserve_keys: true);
177
178
        foreach ($columns as $i => $column) {
179
            $segments = explode('.', $column);
180
181
            $names[$i] = end($segments);
182
        }
183
184
        return $names;
185
    }
186
}
187