Passed
Push — master ( 272168...5aab3e )
by Jonas
03:54
created

createMergeViewWithoutDuplicates()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 2
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
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 48
    public function createMergeView($name, array $relations, $duplicates = true, $orReplace = false)
24
    {
25 48
        $this->removeConstraints($relations);
26
27 48
        $union = $duplicates ? 'unionAll' : 'union';
28
29 48
        $query = $this->getQuery($relations, $union);
30
31 48
        $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 48
    }
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 4
    }
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 8
    }
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 4
    }
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 48
    protected function removeConstraints(array $relations)
78
    {
79 48
        foreach ($relations as $relation) {
80 48
            $foreignKey = $this->getOriginalForeignKey($relation);
81
82 48
            $relation->getQuery()->getQuery()->wheres = collect($relation->getQuery()->getQuery()->wheres)
83 48
                ->reject(function ($where) use ($foreignKey) {
84 48
                    return $where['column'] === $foreignKey;
85 48
                })->values()->all();
86
        }
87 48
    }
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 48
    protected function getOriginalForeignKey(Relation $relation)
96
    {
97 48
        if ($relation instanceof BelongsTo) {
98 4
            return $relation->getQualifiedOwnerKeyName();
99
        }
100
101 44
        if ($relation instanceof BelongsToMany) {
102 24
            return $relation->getQualifiedForeignPivotKeyName();
103
        }
104
105 20
        if ($relation instanceof HasManyThrough) {
106 20
            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 48
    protected function getQuery(array $relations, $union)
124
    {
125 48
        $grammar = $this->connection->getQueryGrammar();
126
127 48
        $pdo = $this->connection->getPdo();
128
129 48
        $columns = $this->getColumns($relations);
130
131 48
        $allColumns = array_unique(array_merge(...array_values($columns)));
132
133 48
        $query = null;
134
135 48
        foreach ($relations as $relation) {
136 48
            $relationQuery = $relation->getQuery();
137
138 48
            $from = $relationQuery->getQuery()->from;
139
140 48
            $foreignKey = $this->getMergedForeignKey($relation);
141
142 48
            $placeholders = [];
143
144 48
            foreach ($allColumns as $column) {
145 48
                if (in_array($column, $columns[$from])) {
146 48
                    $relationQuery->addSelect($from.'.'.$column);
147
                } else {
148 24
                    $relationQuery->selectRaw('null as '.$grammar->wrap($column));
149
150 24
                    $placeholders[] = $column;
151
                }
152
            }
153
154 48
            $relationQuery->selectRaw($grammar->wrap($foreignKey).' as laravel_foreign_key')
155 48
                ->selectRaw($pdo->quote(get_class($relation->getRelated())).' as laravel_model')
156 48
                ->selectRaw($pdo->quote(implode(',', $placeholders)).' as laravel_placeholders')
157 48
                ->selectRaw($pdo->quote(implode(',', array_keys($relationQuery->getEagerLoads()))).' as laravel_with');
158
159 48
            $this->addRelationQueryConstraints($relation);
160
161 48
            if (!$query) {
162 48
                $query = $relationQuery;
163
            } else {
164 44
                $query->$union($relationQuery);
165
            }
166
        }
167
168 48
        return $query;
169
    }
170
171
    /**
172
     * Get the columns of all relationship tables.
173
     *
174
     * @param \Illuminate\Database\Eloquent\Relations\Relation[] $relations
175
     * @return array
176
     */
177 48
    protected function getColumns(array $relations)
178
    {
179 48
        $columns = [];
180
181 48
        foreach ($relations as $relation) {
182 48
            $table = $relation->getQuery()->getQuery()->from;
183
184 48
            if (!isset($columns[$table])) {
185 48
                $listing = $relation->getRelated()->getConnection()->getSchemaBuilder()->getColumnListing($table);
186
187 48
                $columns[$table] = $listing;
188
            }
189
        }
190
191 48
        return $columns;
192
    }
193
194
    /**
195
     * Get the foreign key for the merged relationship.
196
     *
197
     * @param \Illuminate\Database\Eloquent\Relations\Relation $relation
198
     * @return string
199
     */
200 48
    protected function getMergedForeignKey(Relation $relation)
201
    {
202 48
        if ($relation instanceof BelongsTo) {
203 4
            return $relation->getQualifiedParentKeyName();
204
        }
205
206 44
        return $this->getOriginalForeignKey($relation);
207
    }
208
209
    /**
210
     * Add relation-specific constraints to query.
211
     *
212
     * @param \Illuminate\Database\Eloquent\Relations\Relation $relation
213
     * @return void
214
     */
215 48
    protected function addRelationQueryConstraints(Relation $relation)
216
    {
217 48
        if ($relation instanceof BelongsTo) {
218 4
            $relation->getQuery()->distinct()
219 4
                          ->join(
220 4
                              $relation->getParent()->getTable(),
221 4
                              $relation->getQualifiedForeignKeyName(),
222 4
                              '=',
223 4
                              $relation->getQualifiedOwnerKeyName()
224
                          );
225
        }
226 48
    }
227
}
228