Passed
Push — master ( 25b545...25c88b )
by Jonas
13:00
created

CreatesMergeViews::getRelationshipColumns()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 7
nc 3
nop 1
dl 0
loc 15
ccs 8
cts 8
cp 1
crap 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Staudenmeir\LaravelMergedRelations\Schema\Builders;
4
5
use Illuminate\Database\Eloquent\Relations\BelongsTo;
6
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
7
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
8
use Illuminate\Database\Eloquent\Relations\HasOneOrMany;
9
use Illuminate\Database\Eloquent\Relations\Relation;
10
use RuntimeException;
11
12
trait CreatesMergeViews
13
{
14
    /**
15
     * Create a view that merges relationships.
16
     *
17
     * @param string $name
18
     * @param \Illuminate\Database\Eloquent\Relations\Relation[] $relations
19
     * @param bool $duplicates
20
     * @param bool $orReplace
21
     * @return void
22
     */
23 52
    public function createMergeView($name, array $relations, $duplicates = true, $orReplace = false)
24
    {
25 52
        $this->removeConstraints($relations);
26
27 52
        $union = $duplicates ? 'unionAll' : 'union';
28
29 52
        $query = $this->getQuery($relations, $union);
30
31 52
        $this->createView($name, $query, null, $orReplace);
0 ignored issues
show
Bug introduced by
The method createView() does not exist on Staudenmeir\LaravelMerge...lders\CreatesMergeViews. Did you maybe mean createMergeView()? ( Ignorable by Annotation )

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

31
        $this->/** @scrutinizer ignore-call */ 
32
               createView($name, $query, null, $orReplace);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
32
    }
33
34
    /**
35
     * Create a view that merges relationships without duplicates.
36
     *
37
     * @param string $name
38
     * @param \Illuminate\Database\Eloquent\Relations\Relation[] $relations
39
     * @return void
40
     */
41 4
    public function createMergeViewWithoutDuplicates($name, array $relations)
42
    {
43 4
        $this->createMergeView($name, $relations, false);
44
    }
45
46
    /**
47
     * Create a view that merges relationships or replace an existing one.
48
     *
49
     * @param string $name
50
     * @param array $relations
51
     * @param bool $duplicates
52
     * @return void
53
     */
54 8
    public function createOrReplaceMergeView($name, array $relations, $duplicates = true)
55
    {
56 8
        $this->createMergeView($name, $relations, $duplicates, true);
57
    }
58
59
    /**
60
     * Create a view that merges relationships or replace an existing one without duplicates.
61
     *
62
     * @param string $name
63
     * @param array $relations
64
     * @return void
65
     */
66 4
    public function createOrReplaceMergeViewWithoutDuplicates($name, array $relations)
67
    {
68 4
        $this->createOrReplaceMergeView($name, $relations, false);
69
    }
70
71
    /**
72
     * Remove the foreign key constraints from the relationships.
73
     *
74
     * @param \Illuminate\Database\Eloquent\Relations\Relation[] $relations
75
     * @return void
76
     */
77 52
    protected function removeConstraints(array $relations)
78
    {
79 52
        foreach ($relations as $relation) {
80 52
            $foreignKey = $this->getOriginalForeignKey($relation);
81
82 52
            $relation->getQuery()->getQuery()->wheres = collect($relation->getQuery()->getQuery()->wheres)
83 52
                ->reject(function ($where) use ($foreignKey) {
84 52
                    return $where['column'] === $foreignKey;
85 52
                })->values()->all();
86
        }
87
    }
88
89
    /**
90
     * Get the foreign key of the original relationship.
91
     *
92
     * @param \Illuminate\Database\Eloquent\Relations\Relation $relation
93
     * @return string
94
     */
95 52
    protected function getOriginalForeignKey(Relation $relation)
96
    {
97 52
        if ($relation instanceof BelongsTo) {
98 8
            return $relation->getQualifiedOwnerKeyName();
99
        }
100
101 48
        if ($relation instanceof BelongsToMany) {
102 24
            return $relation->getQualifiedForeignPivotKeyName();
103
        }
104
105 24
        if ($relation instanceof HasManyThrough) {
106 24
            return $relation->getQualifiedFirstKeyName();
107
        }
108
109 20
        if ($relation instanceof HasOneOrMany) {
110 20
            return $relation->getQualifiedForeignKeyName();
111
        }
112
113
        throw new RuntimeException('This type of relationship is not supported.'); // @codeCoverageIgnore
114
    }
115
116
    /**
117
     * Get the merge query.
118
     *
119
     * @param \Illuminate\Database\Eloquent\Relations\Relation[] $relations
120
     * @param string $union
121
     * @return \Illuminate\Database\Eloquent\Builder
122
     */
123 52
    protected function getQuery(array $relations, $union)
124
    {
125 52
        $grammar = $this->connection->getQueryGrammar();
126
127 52
        $pdo = $this->connection->getPdo();
128
129 52
        $columns = $this->getRelationshipColumns($relations);
130
131 52
        $allColumns = array_unique(array_merge(...array_values($columns)));
132
133 52
        $query = null;
134
135 52
        foreach ($relations as $relation) {
136 52
            $relationQuery = $relation->getQuery();
137
138 52
            $from = $relationQuery->getQuery()->from;
139
140 52
            $foreignKey = $this->getMergedForeignKey($relation);
141
142 52
            $model = $relation->getRelated()->getMorphClass();
143
144 52
            $placeholders = [];
145
146 52
            foreach ($allColumns as $column) {
147 52
                if (in_array($column, $columns[$from])) {
148 52
                    $relationQuery->addSelect($from.'.'.$column);
149
                } else {
150 24
                    $relationQuery->selectRaw('null as '.$grammar->wrap($column));
151
152 24
                    $placeholders[] = $column;
153
                }
154
            }
155
156 52
            $with = array_keys($relationQuery->getEagerLoads());
157
158 52
            $relationQuery->selectRaw($grammar->wrap($foreignKey).' as laravel_foreign_key')
159 52
                ->selectRaw($pdo->quote($model).' as laravel_model')
160 52
                ->selectRaw($pdo->quote(implode(',', $placeholders)).' as laravel_placeholders')
161 52
                ->selectRaw($pdo->quote(implode(',', $with)).' as laravel_with');
162
163 52
            $this->addRelationQueryConstraints($relation);
164
165 52
            if (!$query) {
166 52
                $query = $relationQuery;
167
            } else {
168 48
                $query->$union($relationQuery);
169
            }
170
        }
171
172 52
        return $query;
173
    }
174
175
    /**
176
     * Get the columns of all relationship tables.
177
     *
178
     * @param \Illuminate\Database\Eloquent\Relations\Relation[] $relations
179
     * @return array
180
     */
181 52
    protected function getRelationshipColumns(array $relations)
182
    {
183 52
        $columns = [];
184
185 52
        foreach ($relations as $relation) {
186 52
            $table = $relation->getQuery()->getQuery()->from;
187
188 52
            if (!isset($columns[$table])) {
189 52
                $listing = $relation->getRelated()->getConnection()->getSchemaBuilder()->getColumnListing($table);
190
191 52
                $columns[$table] = $listing;
192
            }
193
        }
194
195 52
        return $columns;
196
    }
197
198
    /**
199
     * Get the foreign key for the merged relationship.
200
     *
201
     * @param \Illuminate\Database\Eloquent\Relations\Relation $relation
202
     * @return string
203
     */
204 52
    protected function getMergedForeignKey(Relation $relation)
205
    {
206 52
        if ($relation instanceof BelongsTo) {
207 8
            return $relation->getQualifiedParentKeyName();
208
        }
209
210 48
        if ($this->isHasManyDeepRelationWithLeadingBelongsTo($relation)) {
211 4
            return $relation->getFarParent()->getQualifiedKeyName();
212
        }
213
214 44
        return $this->getOriginalForeignKey($relation);
215
    }
216
217
    /**
218
     * Add relation-specific constraints to the query.
219
     *
220
     * @param \Illuminate\Database\Eloquent\Relations\Relation $relation
221
     * @return void
222
     */
223 52
    protected function addRelationQueryConstraints(Relation $relation)
224
    {
225 52
        if ($relation instanceof BelongsTo) {
226 8
            $relation->getQuery()->distinct()
227 8
                          ->join(
228 8
                              $relation->getParent()->getTable(),
229 8
                              $relation->getQualifiedForeignKeyName(),
230 8
                              '=',
231 8
                              $relation->getQualifiedOwnerKeyName()
232 8
                          );
233
        }
234
235 52
        if ($this->isHasManyDeepRelationWithLeadingBelongsTo($relation)) {
236 4
            $relation->getQuery()
237 4
                     ->join(
238 4
                         $relation->getFarParent()->getTable(),
239 4
                         $relation->getQualifiedLocalKeyName(),
240 4
                         '=',
241 4
                         $relation->getQualifiedFirstKeyName()
242 4
                     );
243
        }
244
    }
245
246
    /**
247
     * Determine if the relationship is a HasManyDeep relationship that starts with a BelongsTo relationship.
248
     *
249
     * @param \Illuminate\Database\Eloquent\Relations\Relation $relation
250
     * @return bool
251
     */
252 52
    protected function isHasManyDeepRelationWithLeadingBelongsTo(Relation $relation): bool
253
    {
254 52
        return is_a($relation, 'Staudenmeir\EloquentHasManyDeep\HasManyDeep', true) &&
255 52
            $relation->getFirstKeyName() === $relation->getParent()->getKeyName();
256
    }
257
}
258