Passed
Push — master ( ae720b...07d247 )
by Michael
02:20
created

RelatedPlusTrait::scopeModelJoin()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 17
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 17
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 4
nc 2
nop 7
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 attributes
18
 * @property array nullable
19
 * @property array order_fields
20
 * @property array order_defaults
21
 * @property array order_relations
22
 * @property array order_with
23
 * @property array search_fields
24
 * @property string connection
25
 * @method Model getModel()
26
 * @method string getTable()
27
 */
28
trait RelatedPlusTrait
29
{
30
    use CustomOrderTrait, SearchTrait, HelpersTrait;
31
32
    /**
33
     * Boot method for trait
34
     *
35
     */
36
    public static function bootRelatedPlusTrait()
37
    {
38
        static::saving(function ($model) {
39
            if (!empty($model->nullable)) {
40
                /* @var \Illuminate\Database\Eloquent\Model|RelatedPlusTrait|static $model */
41
                $model->setAttributesNull();
42
            }
43
        });
44
    }
45
46
    /**
47
     * Add joins for one or more relations
48
     * This determines the foreign key relations automatically to prevent the need to figure out the columns.
49
     * Usages:
50
     * $query->modelJoin('customers')
51
     * $query->modelJoin('customer.client')
52
     *
53
     * @param Builder $query
54
     * @param string $relationName
55
     * @param string $operator
56
     * @param string $type
57
     * @param bool $where
58
     * @param bool $relatedSelect
59
     * @param string|null $direction
60
     *
61
     * @return Builder
62
     */
63
    public function scopeModelJoin(
64
        Builder $query,
65
        $relationName,
66
        $operator = '=',
67
        $type = 'left',
68
        $where = false,
69
        $relatedSelect = true,
70
        $direction = null
71
    ) {
72
        foreach ($this->parseRelationNames($this->getModel(), $relationName) as $relation) {
73
            // Add selects
74
            $query = $this->modelJoinSelects($query, $relation, $relatedSelect);
75
76
            $query->relationJoin($relation, $operator, $type, $where, $direction);
77
        }
78
79
        return $query;
80
    }
81
82
    /**
83
     * Get the relations from a relation name
84
     * $relationName can be a single relation
85
     * Usage for User model:
86
     * parseRelationNames('customer') returns [$user->customer()]
87
     * parseRelationNames('customer.contact') returns [$user->customer(), $user->customer->contact()]
88
     *
89
     * @param Model $model
90
     * @param string $relationNameString
91
     * @return RelationPlus[]
92
     */
93
    protected function parseRelationNames($model, $relationNameString)
94
    {
95
        $relationNames = explode('.', $relationNameString);
96
        $parentRelation = null;
97
        $relations = [];
98
99
        foreach ($relationNames as $relationName) {
100
            $relation = $this->getRelationFromName($model, $parentRelation, $relationName);
101
            $relations[] = new RelationPlus($relation);
102
            $parentRelation = $relation->getModel();
103
        }
104
105
        return $relations;
106
    }
107
108
    /**
109
     * @param Model $model
110
     * @param BelongsTo|HasOneOrMany|null $parentRelation
111
     * @param string $relationName
112
     * @return BelongsTo|HasOneOrMany
113
     */
114
    protected function getRelationFromName($model, $parentRelation, $relationName)
115
    {
116
        if (is_null($parentRelation)) {
117
            return $model->$relationName();
118
        }
119
120
        return $parentRelation->$relationName();
121
    }
122
123
    /**
124
     * Add selects for model join
125
     *
126
     * @param Builder $query
127
     * @param RelationPlus $relation
128
     * @param bool $relatedSelect
129
     * @return mixed
130
     */
131
    protected function modelJoinSelects($query, $relation, $relatedSelect)
132
    {
133
        if (empty($query->getQuery()->columns)) {
134
            $query->select($this->getTable() . ".*");
135
        }
136
        if ($relatedSelect) {
137
            $query = $this->selectRelated($query, $relation);
138
        }
139
140
        return $query;
141
    }
142
143
    /**
144
     * Add select for related table fields
145
     *
146
     * @param Builder $query
147
     * @param RelationPlus $relation
148
     * @return Builder
149
     */
150
    protected function selectRelated(Builder $query, $relation)
151
    {
152
        $connection = $this->connection;
153
154
        foreach (Schema::connection($connection)->getColumnListing($relation->tableName) as $relatedColumn) {
155
            $query->addSelect(
156
                new Expression("`$relation->tableAlias`.`$relatedColumn` AS `$relation->tableAlias.$relatedColumn`")
157
            );
158
        }
159
160
        return $query;
161
    }
162
163
    /**
164
     * Set the order of a model
165
     *
166
     * @param Builder $query
167
     * @param string $orderField
168
     * @param string $direction
169
     * @return Builder
170
     */
171
    public function scopeOrderByCustom(Builder $query, $orderField, $direction)
172
    {
173
        if ($this->hasOrderFieldsAndDefaults($orderField, $direction)) {
174
            $query = $this->removeGlobalScopes($this->getModel(), $query, 'order');
175
        }
176
177
        return $query->setCustomOrder($orderField, $direction);
178
    }
179
180
    /**
181
     * Use a model method to add columns or joins if in the order options
182
     *
183
     * @param Builder $query
184
     * @param string $order
185
     * @return Builder
186
     */
187
    public function scopeOrderByWith(Builder $query, $order)
188
    {
189
        if (isset($this->order_with[$order])) {
190
            $query = $this->addOrderWith($query, $order);
191
        }
192
193
        if (isset($this->order_fields[$order])) {
194
            $query = $this->addOrderJoin($query, $order);
195
        }
196
197
        return $query;
198
    }
199
200
    /**
201
     * Join a model
202
     *
203
     * @param Builder $query
204
     * @param RelationPlus $relation
205
     * @param string $operator
206
     * @param string $type
207
     * @param boolean $where
208
     * @param string $direction
209
     * @return Builder
210
     */
211
    public function scopeRelationJoin(
212
        Builder $query,
213
        $relation,
214
        $operator,
215
        $type,
216
        $where,
217
        $direction = null
218
    ) {
219
        $fullTableName = $relation->getTableWithAlias();
220
221
        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...
222
            $relation,
223
            $operator,
224
            $direction
225
        ) {
226
            return $relation->getRelationJoin($join, $operator, $direction);
227
        }, null, null, $type, $where);
228
    }
229
230
    /**
231
     * Add where statements for the model search fields
232
     *
233
     * @param Builder $query
234
     * @param string $searchText
235
     * @return Builder
236
     */
237
    public function scopeSearch(Builder $query, $searchText = '')
238
    {
239
        $searchText = trim($searchText);
240
241
        // If search is set
242
        if ($searchText != "" && $this->hasSearchFields()) {
243
            $query = $this->checkSearchFields($query, $searchText);
244
        }
245
246
        return $query;
247
    }
248
249
    /**
250
     * Switch a query to be a subquery of a model
251
     *
252
     * @param Builder $query
253
     * @param Builder $model
254
     * @return Builder
255
     */
256
    public function scopeSetSubquery(Builder $query, $model)
257
    {
258
        $sql = $this->toSqlWithBindings($model);
259
        $table = $model->getQuery()->from;
260
261
        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...
262
            ->from(DB::raw("({$sql}) as " . $table))
263
            ->select($table . '.*');
264
    }
265
266
    /**
267
     * Set the model order
268
     *
269
     * @param Builder $query
270
     * @param string $column
271
     * @param string $direction
272
     * @return Builder
273
     */
274
    public function scopeSetCustomOrder(Builder $query, $column, $direction)
275
    {
276
        if (isset($this->order_defaults)) {
277
            $column = $this->setOrderColumn($column);
278
            $direction = $this->setOrderDirection($direction);
279
        }
280
281
        return $this->setOrder($query, $column, $direction);
282
    }
283
284
    /**
285
     * Check if column being sorted by is from a related model
286
     *
287
     * @param Builder $query
288
     * @param string $column
289
     * @param string $direction
290
     * @return Builder
291
     */
292
    public function scopeOrderByCheckModel(Builder $query, $column, $direction)
293
    {
294
        $query->orderBy(DB::raw($column), $direction);
295
296
        if (isset($this->order_relations) && (strpos($column, '.') !== false ||
297
                isset($this->order_relations[$column]))) {
298
            $query = $this->joinRelatedTable($query, $this->getTableFromColumn($column));
299
        }
300
301
        return $query;
302
    }
303
}
304