Completed
Push — master ( 459fb1...2cb081 )
by Michael
05:38
created

RelatedPlusTrait::scopeSetCustomOrder()   C

Complexity

Conditions 8
Paths 10

Size

Total Lines 24
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 24
c 0
b 0
f 0
rs 5.7377
cc 8
eloc 12
nc 10
nop 3
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\HasMany;
9
use Illuminate\Database\Eloquent\Relations\HasOneOrMany;
10
use Illuminate\Database\Eloquent\Relations\Relation;
11
use Illuminate\Database\Query\Expression;
12
use Illuminate\Database\Query\JoinClause;
13
use Illuminate\Support\Facades\DB;
14
use Illuminate\Support\Facades\Schema;
15
use InvalidArgumentException;
16
17
/**
18
 * Trait RelatedPlusTrait
19
 *
20
 * @property array order_fields
21
 * @property array order_defaults
22
 * @property array order_relations
23
 * @property array order_with
24
 *
25
 * @package Blasttech\WherePlus
26
 */
27
trait RelatedPlusTrait
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
                foreach ($model->attributes as $key => $value) {
38
                    if (isset($model->nullable[$key])) {
39
                        $model->{$key} = empty(trim($value)) ? null : $value;
40
                    }
41
                }
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|RelatedPlus $query
0 ignored issues
show
Documentation introduced by
Consider making the type for parameter $query a bit more specific; maybe use Builder.
Loading history...
54
     * @param string $relation_name
55
     * @param string $operator
56
     * @param string $type
57
     * @param bool $where
58
     * @param bool $related_select
59
     * @param string|null $direction
60
     *
61
     * @return Builder
62
     */
63
    public function scopeModelJoin(
64
        Builder $query,
65
        $relation_name,
0 ignored issues
show
Coding Style introduced by
$relation_name does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
66
        $operator = '=',
67
        $type = 'left',
68
        $where = false,
69
        $related_select = true,
0 ignored issues
show
Coding Style introduced by
$related_select does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
70
        $direction = null
71
    ) {
72
        /** @var Builder $this */
73
        $connection = $this->connection;
0 ignored issues
show
Bug introduced by
The property connection does not seem to exist in Illuminate\Database\Eloquent\Builder.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
74
75
        foreach ($this->parseRelationNames($relation_name) as $relation) {
0 ignored issues
show
Coding Style introduced by
$relation_name does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
76
            $table_name = $relation->getRelated()->getTable();
0 ignored issues
show
Coding Style introduced by
$table_name does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
77
            // if using a 'table' AS 'table_alias' in a from statement, otherwise alias will be the table name
78
            $from = explode(' ', $relation->getQuery()->getQuery()->from);
79
            $table_alias = array_pop($from);
0 ignored issues
show
Coding Style introduced by
$table_alias does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
80
81
            if (empty($query->getQuery()->columns)) {
82
                /** @var Model $this */
83
                $query->select($this->getTable() . ".*");
0 ignored issues
show
Bug introduced by
The method select() does not exist on Illuminate\Database\Eloquent\Builder. Did you maybe mean createSelectWithConstraint()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
84
            }
85
            if ($related_select) {
0 ignored issues
show
Coding Style introduced by
$related_select does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
86
                foreach (Schema::connection($connection)->getColumnListing($table_name) as $related_column) {
0 ignored issues
show
Coding Style introduced by
$table_name does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
87
                    $query->addSelect(
88
                        new Expression("`$table_alias`.`$related_column` AS `$table_alias.$related_column`")
0 ignored issues
show
Coding Style introduced by
$table_alias does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
89
                    );
90
                }
91
            }
92
            $query->relationJoin($table_name, $table_alias, $relation, $operator, $type, $where, $direction);
0 ignored issues
show
Coding Style introduced by
$table_name does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
93
        }
94
95
        return $query;
96
    }
97
98
    /**
99
     * Get the relations from a relation name
100
     * $relation_name can be a single relation
101
     * Usage for User model:
102
     * parseRelationNames('customer') returns [$user->customer()]
103
     * parseRelationNames('customer.contact') returns [$user->customer(), $user->customer->contact()]
104
     *
105
     * @param $relation_name
106
     * @return Relation[]
107
     */
108
    protected function parseRelationNames($relation_name)
0 ignored issues
show
Coding Style introduced by
$relation_name does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
109
    {
110
        $relation_names = explode('.', $relation_name);
0 ignored issues
show
Coding Style introduced by
$relation_names does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
111
        $parent_relation_name = null;
0 ignored issues
show
Coding Style introduced by
$parent_relation_name does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
112
        $relations = [];
113
114
        foreach ($relation_names as $relation_name) {
0 ignored issues
show
Coding Style introduced by
$relation_names does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
115
            if (is_null($parent_relation_name)) {
0 ignored issues
show
Coding Style introduced by
$parent_relation_name does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
116
                $relations[] = $this->$relation_name();
0 ignored issues
show
Coding Style introduced by
$relation_name does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
117
                $parent_relation_name = $this->$relation_name()->getRelated();
0 ignored issues
show
Coding Style introduced by
$parent_relation_name does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
118
            } else {
119
                $relations[] = $parent_relation_name->$relation_name();
0 ignored issues
show
Coding Style introduced by
$parent_relation_name does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
120
            }
121
        }
122
123
        return $relations;
124
    }
125
126
    /**
127
     * Join a model
128
     *
129
     * @param Builder|RelatedPlus $query
0 ignored issues
show
Documentation introduced by
Consider making the type for parameter $query a bit more specific; maybe use Builder.
Loading history...
130
     * @param string $table_name
131
     * @param string $table_alias
132
     * @param Relation $relation
133
     * @param string $operator
134
     * @param string $type
135
     * @param boolean $where
136
     * @param null $direction
137
     * @return Builder
138
     */
139
    public function scopeRelationJoin(
140
        Builder $query,
141
        $table_name,
0 ignored issues
show
Coding Style introduced by
$table_name does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
142
        $table_alias,
0 ignored issues
show
Coding Style introduced by
$table_alias does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
143
        $relation,
144
        $operator,
145
        $type,
146
        $where,
147
        $direction = null
148
    ) {
149
        if ($table_alias !== '' && $table_name !== $table_alias) {
0 ignored issues
show
Coding Style introduced by
$table_alias does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
150
            $full_table_name = $table_name . ' AS ' . $table_alias;
0 ignored issues
show
Coding Style introduced by
$full_table_name does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
151
        } else {
152
            $full_table_name = $table_name;
0 ignored issues
show
Coding Style introduced by
$full_table_name does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
153
        }
154
155
        return $query->join($full_table_name, function (JoinClause $join) use (
0 ignored issues
show
Coding Style introduced by
$full_table_name does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
156
            $table_name,
157
            $table_alias,
158
            $relation,
159
            $operator,
160
            $direction
161
        ) {
162
            // If a HasOne relation and ordered - ie join to the latest/earliest
163
            if (class_basename($relation) === 'HasOne' && !empty($relation->toBase()->orders)) {
164
                // Get first relation order (should only be one)
165
                $order = $relation->toBase()->orders[0];
166
167
                return $join->on($order['column'], $this->hasOneJoinSql($relation, $order));
168
            } else {
169
                // Get relation join columns
170
                $joinColumns = $this->getJoinColumns($relation);
171
172
                $first = $joinColumns->first;
173
                $second = $joinColumns->second;
174
                if ($table_name !== $table_alias) {
0 ignored issues
show
Coding Style introduced by
$table_name does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
175
                    $first = str_replace($table_name, $table_alias, $first);
0 ignored issues
show
Coding Style introduced by
$table_name does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
176
                    $second = str_replace($table_name, $table_alias, $second);
0 ignored issues
show
Coding Style introduced by
$table_name does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
177
                }
178
179
                $join->on($first, $operator, $second);
180
181
                // Add any where clauses from the relationship
182
                $join = $this->addWhereConstraints($join, $relation, $table_alias);
0 ignored issues
show
Coding Style introduced by
$table_alias does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
183
184
                if (!is_null($direction) && get_class($relation) === HasMany::class) {
185
                    $join = $this->hasManyJoin($join, $first, $relation, $table_alias, $direction);
0 ignored issues
show
Coding Style introduced by
$table_alias does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
Bug introduced by
It seems like $join can also be of type object<Illuminate\Database\Eloquent\Builder>; however, Blasttech\EloquentRelate...lusTrait::hasManyJoin() does only seem to accept object<Illuminate\Database\Query\JoinClause>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
186
                }
187
188
                return $join;
189
            }
190
        }, null, null, $type, $where);
191
    }
192
193
    /**
194
     * Get join sql for a HasOne relation
195
     *
196
     * @param Relation $relation
197
     * @param array $order
198
     * @return Expression
199
     */
200
    public function hasOneJoinSql($relation, $order)
201
    {
202
        // Build subquery for getting first/last record in related table
203
        $subQuery = $this
204
            ->joinOne(
205
                $relation->getRelated()->newQuery(),
206
                $relation,
207
                $order['column'],
208
                $order['direction']
209
            )
210
            ->setBindings($relation->getBindings());
211
212
        return DB::raw('(' . $this->toSqlWithBindings($subQuery) . ')');
213
    }
214
215
    /**
216
     * Adds a where for a relation's join columns and and min/max for a given column
217
     *
218
     * @param Builder|RelatedPlus $query
219
     * @param Relation $relation
220
     * @param string $column
221
     * @param string $direction
222
     * @return mixed
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use Builder.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
223
     */
224
    public function joinOne($query, $relation, $column, $direction)
225
    {
226
        // Get join fields
227
        $joinColumns = $this->getJoinColumns($relation);
228
229
        return $this->selectMinMax(
230
            $query->whereColumn($joinColumns->first, '=', $joinColumns->second),
231
            $column,
232
            $direction
233
        );
234
    }
235
236
    /**
237
     * Get the join columns for a relation
238
     *
239
     * @param Relation|BelongsTo|HasOneOrMany $relation
240
     * @return \stdClass
241
     */
242
    protected function getJoinColumns($relation)
243
    {
244
        // Get keys with table names
245
        if ($relation instanceof BelongsTo) {
246
            $first = $relation->getOwnerKey();
247
            $second = $relation->getForeignKey();
248
        } else {
249
            $first = $relation->getQualifiedParentKeyName();
250
            $second = $relation->getQualifiedForeignKeyName();
251
        }
252
253
        return (object)['first' => $first, 'second' => $second];
254
    }
255
256
    /**
257
     * Adds a select for a min or max on the given column, depending on direction given
258
     *
259
     * @param Builder|RelatedPlus $query
260
     * @param string $column
261
     * @param string $direction
262
     * @return Builder
263
     */
264
    public function selectMinMax($query, $column, $direction)
265
    {
266
        $column = $this->addBackticks($column);
267
268
        if ($direction == 'asc') {
269
            return $query->select(DB::raw('MIN(' . $column . ')'));
270
        } else {
271
            return $query->select(DB::raw('MAX(' . $column . ')'));
272
        }
273
    }
274
275
    /**
276
     * Add backticks to a table/column
277
     *
278
     * @param $column
279
     * @return string
280
     */
281
    private function addBackticks($column)
282
    {
283
        return preg_match('/^[0-9a-zA-Z\.]*$/', $column) ?
284
            '`' . str_replace(['`', '.'], ['', '`.`'], $column) . '`' : $column;
285
    }
286
287
    /**
288
     * Return the sql for a query with the bindings replaced with the binding values
289
     *
290
     * @param Builder $builder
291
     * @return string
292
     */
293
    private function toSqlWithBindings(Builder $builder)
294
    {
295
        return vsprintf($this->replacePlaceholders($builder), array_map('addslashes', $builder->getBindings()));
296
    }
297
298
    /**
299
     * Replace SQL placeholders with '%s'
300
     *
301
     * @param Builder $builder
302
     * @return mixed
303
     */
304
    private function replacePlaceholders(Builder $builder)
305
    {
306
        return str_replace(['?'], ['\'%s\''], $builder->toSql());
307
    }
308
309
    /**
310
     * Add wheres if they exist for a relation
311
     *
312
     * @param Builder|JoinClause $builder
313
     * @param Relation|BelongsTo|HasOneOrMany $relation
314
     * @param string $table
315
     * @return Builder|JoinClause
316
     */
317
    protected function addWhereConstraints($builder, $relation, $table)
318
    {
319
        // Get where clauses from the relationship
320
        $wheres = collect($relation->toBase()->wheres)
321
            ->where('type', 'Basic')
322
            ->map(function ($where) use ($table) {
323
                // Add table name to column if it is absent
324
                return [$this->columnWithTableName($table, $where['column']), $where['operator'], $where['value']];
325
            })->toArray();
326
327
        if (!empty($wheres)) {
328
            $builder->where($wheres);
329
        }
330
331
        return $builder;
332
    }
333
334
    /**
335
     * Add table name to column name if table name not already included in column name
336
     *
337
     * @param string $table
338
     * @param string $column
339
     * @return string
340
     */
341
    private function columnWithTableName($table, $column)
342
    {
343
        return (preg_match('/(' . $table . '\.|`' . $table . '`)/i', $column) > 0 ? '' : $table . '.') . $column;
344
    }
345
346
    /**
347
     * If the relation is one-to-many, just get the first related record
348
     *
349
     * @param JoinClause $joinClause
350
     * @param string $column
351
     * @param HasMany|Relation $relation
352
     * @param string $table
353
     * @param string $direction
354
     *
355
     * @return JoinClause
356
     */
357
    public function hasManyJoin(JoinClause $joinClause, $column, $relation, $table, $direction)
358
    {
359
        return $joinClause->where(
360
            $column,
361
            function ($subQuery) use ($table, $direction, $relation, $column) {
362
                $subQuery = $this->joinOne(
363
                    $subQuery->from($table),
364
                    $relation,
365
                    $column,
366
                    $direction
367
                );
368
369
                // Add any where statements with the relationship
370
                $subQuery = $this->addWhereConstraints($subQuery, $relation, $table);
371
372
                // Add any order statements with the relationship
373
                return $this->addOrder($subQuery, $relation, $table);
374
            }
375
        );
376
    }
377
378
    /**
379
     * Add orderBy if orders exist for a relation
380
     *
381
     * @param Builder|JoinClause $builder
382
     * @param Relation|BelongsTo|HasOneOrMany $relation
383
     * @param string $table
384
     * @return Builder
385
     */
386
    protected function addOrder($builder, $relation, $table)
387
    {
388
        if (!empty($relation->toBase()->orders)) {
389
            // Get where clauses from the relationship
390
            foreach ($relation->toBase()->orders as $order) {
391
                $builder->orderBy($this->columnWithTableName($table, $order['column']), $order['direction']);
0 ignored issues
show
Bug introduced by
The method orderBy does only exist in Illuminate\Database\Query\JoinClause, but not in Illuminate\Database\Eloquent\Builder.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
392
            }
393
        }
394
395
        return $builder;
396
    }
397
398
    /**
399
     * Set the order of a model
400
     *
401
     * @param Builder|RelatedPlus $query
0 ignored issues
show
Documentation introduced by
Consider making the type for parameter $query a bit more specific; maybe use Builder.
Loading history...
402
     * @param string $order_field
403
     * @param string $dir
404
     * @return Builder
405
     */
406
    public function scopeOrderByCustom(Builder $query, $order_field, $dir)
0 ignored issues
show
Coding Style introduced by
$order_field does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
407
    {
408
        if (!isset($this->order_fields) || !is_array($this->order_fields)) {
409
            throw new InvalidArgumentException(get_class($this) . ' order fields not set correctly.');
410
        }
411
412
        if (($order_field === '' || $dir === '')
0 ignored issues
show
Coding Style introduced by
$order_field does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
413
            && (!isset($this->order_defaults) || !is_array($this->order_defaults))) {
414
            throw new InvalidArgumentException(get_class($this) . ' order defaults not set and not overriden.');
415
        }
416
417
        // Remove order global scope if it exists
418
        /** @var Model $this */
419
        $global_scopes = $this->getGlobalScopes();
0 ignored issues
show
Coding Style introduced by
$global_scopes does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
420
        if (isset($global_scopes['order'])) {
0 ignored issues
show
Coding Style introduced by
$global_scopes does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
421
            $query->withoutGlobalScope('order');
422
        }
423
424
        $query->setCustomOrder($order_field, $dir);
0 ignored issues
show
Coding Style introduced by
$order_field does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
425
426
        return $query;
427
    }
428
429
    /**
430
     * Check if column being sorted by is from a related model
431
     *
432
     * @param Builder|RelatedPlus $query
0 ignored issues
show
Documentation introduced by
Consider making the type for parameter $query a bit more specific; maybe use Builder.
Loading history...
433
     * @param string $column
434
     * @param string $direction
435
     * @return Builder
436
     */
437
    public function scopeOrderByCheckModel(Builder $query, $column, $direction)
438
    {
439
        $query->orderBy(DB::raw($column), $direction);
0 ignored issues
show
Bug introduced by
The method orderBy() does not exist on Illuminate\Database\Eloquent\Builder. Did you maybe mean enforceOrderBy()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
440
441
        $period_pos = strpos($column, '.');
0 ignored issues
show
Coding Style introduced by
$period_pos does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
442
        if (isset($this->order_relations) && ($period_pos !== false || isset($this->order_relations[$column]))) {
0 ignored issues
show
Coding Style introduced by
$period_pos does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
443
            $table = ($period_pos !== false ? substr($column, 0, $period_pos) : $column);
0 ignored issues
show
Coding Style introduced by
$period_pos does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
444
445
            if (isset($this->order_relations[$table]) &&
446
                !$this->hasJoin($query, $table, $this->order_relations[$table])) {
447
                $column_relations = $this->order_relations[$table];
0 ignored issues
show
Coding Style introduced by
$column_relations does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
448
449
                $query->modelJoin(
450
                    $column_relations,
0 ignored issues
show
Coding Style introduced by
$column_relations does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
451
                    '=',
452
                    'left',
453
                    false,
454
                    false
455
                );
456
            }
457
        }
458
459
        return $query;
460
    }
461
462
    /**
463
     * Check if this model has already been joined to a table or relation
464
     *
465
     * @param Builder $builder
466
     * @param string $table
467
     * @param \Illuminate\Database\Eloquent\Relations\Relation $relation
468
     * @return bool
469
     */
470
    protected function hasJoin(Builder $builder, $table, $relation)
471
    {
472
        $joins = $builder->getQuery()->joins;
473
        if (!is_null($joins)) {
474
            foreach ($joins as $JoinClause) {
0 ignored issues
show
Coding Style introduced by
$JoinClause does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
475
                if ($JoinClause->table == $table) {
0 ignored issues
show
Coding Style introduced by
$JoinClause does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
476
                    return true;
477
                }
478
            }
479
        }
480
481
        $eager_loads = $builder->getEagerLoads();
0 ignored issues
show
Coding Style introduced by
$eager_loads does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
482
483
        return !is_null($eager_loads) && in_array($relation, $eager_loads);
0 ignored issues
show
Coding Style introduced by
$eager_loads does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
484
    }
485
486
    /**
487
     * Set the model order
488
     *
489
     * @param Builder|RelatedPlus $query
0 ignored issues
show
Documentation introduced by
Consider making the type for parameter $query a bit more specific; maybe use Builder.
Loading history...
490
     * @param string $column
491
     * @param string $direction
492
     * @return Builder
493
     */
494
    public function scopeSetCustomOrder(Builder $query, $column, $direction)
495
    {
496
        if (isset($this->order_defaults)) {
497
            // If $column not in order_fields list, use default
498
            if ($column == '' || !isset($this->order_fields[$column])) {
499
                $column = $this->order_defaults['field'];
500
            }
501
502
            // If $direction not asc or desc, use default
503
            if ($direction == '' || !in_array(strtoupper($direction), ['ASC', 'DESC'])) {
504
                $direction = $this->order_defaults['dir'];
505
            }
506
        }
507
508
        if (!is_array($this->order_fields[$column])) {
509
            $query->orderByCheckModel($this->order_fields[$column], $direction);
510
        } else {
511
            foreach ($this->order_fields[$column] as $db_field) {
0 ignored issues
show
Coding Style introduced by
$db_field does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
512
                $query->orderByCheckModel($db_field, $direction);
0 ignored issues
show
Coding Style introduced by
$db_field does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
513
            }
514
        }
515
516
        return $query;
517
    }
518
519
    /**
520
     * Switch a query to be a subquery of a model
521
     *
522
     * @param Builder|RelatedPlus $query
0 ignored issues
show
Documentation introduced by
Consider making the type for parameter $query a bit more specific; maybe use Builder.
Loading history...
523
     * @param Builder $model
524
     * @return Builder
525
     */
526
    public function scopeSetSubquery(Builder $query, $model)
527
    {
528
        $sql = $this->toSqlWithBindings($model);
529
        $table = $model->getQuery()->from;
530
531
        return $query
532
            ->from(DB::raw("({$sql}) as " . $table))
533
            ->select($table . '.*');
534
    }
535
536
    /**
537
     * Use a model method to add columns or joins if in the order options
538
     *
539
     * @param Builder|RelatedPlus $query
0 ignored issues
show
Documentation introduced by
Consider making the type for parameter $query a bit more specific; maybe use Builder.
Loading history...
540
     * @param string $order
541
     * @return Builder
542
     */
543
    public function scopeOrderByWith(Builder $query, $order)
544
    {
545
        if (isset($this->order_with[$order])) {
546
            $with = 'with' . $this->order_with[$order];
547
548
            $query->$with();
549
        }
550
551
        if (isset($this->order_fields[$order])) {
552
            $order_option = (explode('.', $this->order_fields[$order]))[0];
0 ignored issues
show
Coding Style introduced by
$order_option does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
553
554
            if (isset($this->order_relations[$order_option])) {
0 ignored issues
show
Coding Style introduced by
$order_option does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
555
                $query->modelJoin(
556
                    $this->order_relations[$order_option],
0 ignored issues
show
Coding Style introduced by
$order_option does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
557
                    '=',
558
                    'left',
559
                    false,
560
                    false
561
                );
562
            }
563
        }
564
565
        return $query;
566
    }
567
568
    /**
569
     * Add where statements for the model search fields
570
     *
571
     * @param Builder|RelatedPlus $query
0 ignored issues
show
Documentation introduced by
Consider making the type for parameter $query a bit more specific; maybe use Builder.
Loading history...
572
     * @param string $search
573
     * @return Builder
574
     */
575
    public function scopeSearch(Builder $query, $search = '')
576
    {
577
        $search = trim($search);
578
579
        // If search is set
580
        if ($search != "") {
581
            if (!isset($this->search_fields) || !is_array($this->search_fields) || empty($this->search_fields)) {
582
                throw new InvalidArgumentException(get_class($this) . ' search properties not set correctly.');
583
            } else {
584
                $search_fields = $this->search_fields;
0 ignored issues
show
Coding Style introduced by
$search_fields does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
Bug introduced by
The property search_fields does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
585
                /** @var Model $this */
586
                $table = $this->getTable();
587
                $query->where(function (Builder $query) use ($search_fields, $table, $search) {
588
                    foreach ($search_fields as $search_field => $search_field_parameters) {
0 ignored issues
show
Coding Style introduced by
$search_fields does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
589
                        if (!isset($search_field_parameters['regex']) ||
0 ignored issues
show
Coding Style introduced by
$search_field_parameters does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
590
                            preg_match($search_field_parameters['regex'], $search)) {
0 ignored issues
show
Coding Style introduced by
$search_field_parameters does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
591
                            $search_column = is_array($search_field_parameters)
0 ignored issues
show
Coding Style introduced by
$search_column does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
592
                                ? $search_field : $search_field_parameters;
0 ignored issues
show
Coding Style introduced by
$search_field does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
593
                            $search_operator = isset($search_field_parameters['operator'])
0 ignored issues
show
Coding Style introduced by
$search_operator does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
594
                                ? $search_field_parameters['operator'] : 'like';
0 ignored issues
show
Coding Style introduced by
$search_field_parameters does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
595
                            $search_value = isset($search_field_parameters['value'])
0 ignored issues
show
Coding Style introduced by
$search_value does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
596
                                ? $search_field_parameters['value'] : '%{{search}}%';
0 ignored issues
show
Coding Style introduced by
$search_field_parameters does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
597
598
                            if (isset($search_field_parameters['relation'])) {
0 ignored issues
show
Coding Style introduced by
$search_field_parameters does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
599
                                $relation = $search_field_parameters['relation'];
0 ignored issues
show
Coding Style introduced by
$search_field_parameters does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
600
                                $related_table = $this->$relation()->getRelated()->getTable();
0 ignored issues
show
Coding Style introduced by
$related_table does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
601
602
                                $query->orWhere(function (Builder $query) use (
603
                                    $search,
604
                                    $search_column,
605
                                    $search_field_parameters,
606
                                    $relation,
607
                                    $related_table
608
                                ) {
609
                                    $query->orWhereHas($relation, function (Builder $query2) use (
610
                                        $search,
611
                                        $search_column,
612
                                        $search_field_parameters,
613
                                        $related_table
614
                                    ) {
615
                                        $query2->where($related_table . '.' . $search_column, 'like', $search . '%');
0 ignored issues
show
Coding Style introduced by
$related_table does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
616
                                    });
617
                                });
618
                            } else {
619
                                $query->orWhere(
620
                                    $table . '.' . $search_column,
0 ignored issues
show
Coding Style introduced by
$search_column does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
621
                                    $search_operator,
0 ignored issues
show
Coding Style introduced by
$search_operator does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
622
                                    str_replace('{{search}}', $search, $search_value)
0 ignored issues
show
Coding Style introduced by
$search_value does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
623
                                );
624
                            }
625
                        }
626
                    }
627
628
                    return $query;
629
                });
630
            }
631
        }
632
633
        return $query;
634
    }
635
}
636