Passed
Push — master ( 4f50ad...2698b3 )
by Michael
02:21
created

RelatedPlusTrait::scopeSetSubquery()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 5
nc 1
nop 2
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace Blasttech\EloquentRelatedPlus;
4
5
use Illuminate\Database\Eloquent\Builder;
6
use Illuminate\Database\Eloquent\Model;
7
use Illuminate\Database\Eloquent\Relations\BelongsTo;
8
use Illuminate\Database\Eloquent\Relations\HasOneOrMany;
9
use Illuminate\Database\Query\Expression;
10
use Illuminate\Database\Query\JoinClause;
11
use Illuminate\Support\Facades\DB;
12
use Illuminate\Support\Facades\Schema;
13
14
/**
15
 * Trait RelatedPlusTrait
16
 *
17
 * @property array nullable
18
 * @property array order_fields
19
 * @property array order_defaults
20
 * @property array order_relations
21
 * @property array order_with
22
 * @property array search_fields
23
 * @property string connection
24
 */
25
trait RelatedPlusTrait
26
{
27
    use CustomOrderTrait, SearchTrait;
28
29
    /**
30
     * Boot method for trait
31
     *
32
     */
33
    public static function bootRelatedPlusTrait()
34
    {
35
        static::saving(function ($model) {
36
            if (!empty($model->nullable)) {
37
                /* @var \Illuminate\Database\Eloquent\Model|RelatedPlusTrait|static $model */
38
                $model->setAttributesNull();
39
            }
40
        });
41
    }
42
43
    /**
44
     * Set empty fields to null
45
     */
46
    protected function setAttributesNull()
47
    {
48
        /** @var Model $this */
49
        foreach ($this->attributes as $key => $value) {
0 ignored issues
show
Bug Best Practice introduced by
The property attributes does not exist on Blasttech\EloquentRelatedPlus\RelatedPlusTrait. Did you maybe forget to declare it?
Loading history...
50
            if (isset($this->nullable[$key])) {
51
                $this->{$key} = empty(trim($value)) ? null : $value;
52
            }
53
        }
54
    }
55
56
    /**
57
     * Get the table associated with the model.
58
     *
59
     * @return string
60
     */
61
    abstract public function getTable();
62
63
    /**
64
     * Add joins for one or more relations
65
     * This determines the foreign key relations automatically to prevent the need to figure out the columns.
66
     * Usages:
67
     * $query->modelJoin('customers')
68
     * $query->modelJoin('customer.client')
69
     *
70
     * @param Builder $query
71
     * @param string $relationName
72
     * @param string $operator
73
     * @param string $type
74
     * @param bool $where
75
     * @param bool $relatedSelect
76
     * @param string|null $direction
77
     *
78
     * @return Builder
79
     */
80
    public function scopeModelJoin(
81
        Builder $query,
82
        $relationName,
83
        $operator = '=',
84
        $type = 'left',
85
        $where = false,
86
        $relatedSelect = true,
87
        $direction = null
88
    ) {
89
        foreach ($this->parseRelationNames($this->getModel(), $relationName) as $relation) {
0 ignored issues
show
Bug introduced by
It seems like getModel() 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

89
        foreach ($this->parseRelationNames($this->/** @scrutinizer ignore-call */ getModel(), $relationName) as $relation) {
Loading history...
90
            // Add selects
91
            $query = $this->modelJoinSelects($query, $relation, $relatedSelect);
92
93
            $query->relationJoin($relation, $operator, $type, $where, $direction);
94
        }
95
96
        return $query;
97
    }
98
99
    /**
100
     * Get the relations from a relation name
101
     * $relationName can be a single relation
102
     * Usage for User model:
103
     * parseRelationNames('customer') returns [$user->customer()]
104
     * parseRelationNames('customer.contact') returns [$user->customer(), $user->customer->contact()]
105
     *
106
     * @param Model $model
107
     * @param string $relationName
108
     * @return RelationPlus[]
109
     */
110
    public function parseRelationNames($model, $relationName)
111
    {
112
        $relationNames = explode('.', $relationName);
113
        $parentRelation = null;
114
        $relations = [];
115
116
        foreach ($relationNames as $relationName) {
0 ignored issues
show
introduced by
$relationName is overwriting one of the parameters of this function.
Loading history...
117
            $relation = $this->getRelationFromName($model, $parentRelation, $relationName);
118
            $relations[] = new RelationPlus($relation);
119
            $parentRelation = $relation->getModel();
120
        }
121
122
        return $relations;
123
    }
124
125
    /**
126
     * @param Model $model
127
     * @param BelongsTo|HasOneOrMany|null $parentRelation
128
     * @param string $relationName
129
     * @return BelongsTo|HasOneOrMany
130
     */
131
    protected function getRelationFromName($model, $parentRelation, $relationName)
132
    {
133
        if (is_null($parentRelation)) {
134
            return $model->$relationName();
135
        }
136
137
        return $parentRelation->$relationName();
138
    }
139
140
    /**
141
     * Add selects for model join
142
     *
143
     * @param Builder $query
144
     * @param RelationPlus $relation
145
     * @param bool $relatedSelect
146
     * @return mixed
147
     */
148
    protected function modelJoinSelects($query, $relation, $relatedSelect)
149
    {
150
        if (empty($query->getQuery()->columns)) {
151
            $query->select($this->getTable() . ".*");
152
        }
153
        if ($relatedSelect) {
154
            $query = $this->selectRelated($query, $relation);
155
        }
156
157
        return $query;
158
    }
159
160
    /**
161
     * Add select for related table fields
162
     *
163
     * @param Builder $query
164
     * @param RelationPlus $relation
165
     * @return Builder
166
     */
167
    protected function selectRelated(Builder $query, $relation)
168
    {
169
        $connection = $this->connection;
170
171
        foreach (Schema::connection($connection)->getColumnListing($relation->tableName) as $relatedColumn) {
172
            $query->addSelect(
173
                new Expression("`$relation->tableAlias`.`$relatedColumn` AS `$relation->tableAlias.$relatedColumn`")
174
            );
175
        }
176
177
        return $query;
178
    }
179
180
    /**
181
     * Set the order of a model
182
     *
183
     * @param Builder $query
184
     * @param string $orderField
185
     * @param string $direction
186
     * @return Builder
187
     */
188
    public function scopeOrderByCustom(Builder $query, $orderField, $direction)
189
    {
190
        if ($this->hasOrderFieldsAndDefaults($orderField, $direction)) {
191
            $query = RelatedPlusHelpers::removeGlobalScopes($this->getModel(), $query, 'order');
192
        }
193
194
        return $query->setCustomOrder($orderField, $direction);
195
    }
196
197
    /**
198
     * Use a model method to add columns or joins if in the order options
199
     *
200
     * @param Builder $query
201
     * @param string $order
202
     * @return Builder
203
     */
204
    public function scopeOrderByWith(Builder $query, $order)
205
    {
206
        if (isset($this->order_with[$order])) {
207
            $query = $this->addOrderWith($query, $order);
208
        }
209
210
        if (isset($this->order_fields[$order])) {
211
            $query = $this->addOrderJoin($query, $order);
212
        }
213
214
        return $query;
215
    }
216
217
    /**
218
     * Execute a scope in the order_width settings
219
     *
220
     * @param Builder $query
221
     * @param string $order
222
     * @return Builder
223
     */
224
    protected function addOrderWith(Builder $query, $order)
225
    {
226
        $with = 'with' . $this->order_with[$order];
227
228
        return $query->$with();
229
    }
230
231
    /**
232
     * Add join from order_fields
233
     *
234
     * @param Builder $query
235
     * @param string $order
236
     * @return Builder
237
     */
238
    protected function addOrderJoin(Builder $query, $order)
239
    {
240
        $orderOption = (explode('.', $this->order_fields[$order]))[0];
241
242
        if (isset($this->order_relations[$orderOption])) {
243
            $query->modelJoin(
244
                $this->order_relations[$orderOption],
245
                '=',
246
                'left',
247
                false,
248
                false
249
            );
250
        }
251
252
        return $query;
253
    }
254
255
    /**
256
     * Join a model
257
     *
258
     * @param Builder $query
259
     * @param RelationPlus $relation
260
     * @param string $operator
261
     * @param string $type
262
     * @param boolean $where
263
     * @param string $direction
264
     * @return Builder
265
     */
266
    public function scopeRelationJoin(
267
        Builder $query,
268
        $relation,
269
        $operator,
270
        $type,
271
        $where,
272
        $direction = null
273
    ) {
274
        $fullTableName = $relation->getTableWithAlias();
275
276
        return $query->join($fullTableName, function (JoinClause $join) use (
0 ignored issues
show
Bug Best Practice introduced by
The expression return $query->join($ful...l, null, $type, $where) also could return the type Illuminate\Database\Query\Builder which is incompatible with the documented return type Illuminate\Database\Eloquent\Builder.
Loading history...
277
            $relation,
278
            $operator,
279
            $direction
280
        ) {
281
            return $relation->getRelationJoin($join, $operator, $direction);
282
        }, null, null, $type, $where);
283
    }
284
285
    /**
286
     * Add where statements for the model search fields
287
     *
288
     * @param Builder $query
289
     * @param string $searchText
290
     * @return Builder
291
     */
292
    public function scopeSearch(Builder $query, $searchText = '')
293
    {
294
        $searchText = trim($searchText);
295
296
        // If search is set
297
        if ($searchText != "" && $this->hasSearchFields()) {
298
            $query = $this->checkSearchFields($query, $searchText);
299
        }
300
301
        return $query;
302
    }
303
304
    /**
305
     * Switch a query to be a subquery of a model
306
     *
307
     * @param Builder $query
308
     * @param Builder $model
309
     * @return Builder
310
     */
311
    public function scopeSetSubquery(Builder $query, $model)
312
    {
313
        $sql = RelatedPlusHelpers::toSqlWithBindings($model);
314
        $table = $model->getQuery()->from;
315
316
        return $query
0 ignored issues
show
Bug Best Practice introduced by
The expression return $query->from(Illu...->select($table . '.*') also could return the type Illuminate\Database\Query\Builder which is incompatible with the documented return type Illuminate\Database\Eloquent\Builder.
Loading history...
317
            ->from(DB::raw("({$sql}) as " . $table))
318
            ->select($table . '.*');
319
    }
320
321
    /**
322
     * Set the model order
323
     *
324
     * @param Builder $query
325
     * @param string $column
326
     * @param string $direction
327
     * @return Builder
328
     */
329
    public function scopeSetCustomOrder(Builder $query, $column, $direction)
330
    {
331
        if (isset($this->order_defaults)) {
332
            $column = $this->setOrderColumn($column);
333
            $direction = $this->setOrderDirection($direction);
334
        }
335
336
        return $this->setOrder($query, $column, $direction);
337
    }
338
339
    /**
340
     * Check if column being sorted by is from a related model
341
     *
342
     * @param Builder $query
343
     * @param string $column
344
     * @param string $direction
345
     * @return Builder
346
     */
347
    public function scopeOrderByCheckModel(Builder $query, $column, $direction)
348
    {
349
        $query->orderBy(DB::raw($column), $direction);
350
351
        if (isset($this->order_relations) && (strpos($column, '.') !== false ||
352
                isset($this->order_relations[$column]))) {
353
            $query = $this->joinRelatedTable($query, RelatedPlusHelpers::getTableFromColumn($column));
354
        }
355
356
        return $query;
357
    }
358
}
359