Passed
Push — master ( 768f3e...ff949a )
by Michael
02:36
created

RelatedPlusTrait::searchThis()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 11
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 7
nc 1
nop 5
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
 * @property array search_fields
25
 *
26
 * @package Blasttech\WherePlus
27
 */
28
trait RelatedPlusTrait
29
{
30
    /**
31
     * Boot method for trait
32
     *
33
     */
34
    public static function bootRelatedPlusTrait()
35
    {
36
        static::saving(function ($model) {
37
            if (!empty($model->nullable)) {
38
                foreach ($model->attributes as $key => $value) {
39
                    if (isset($model->nullable[$key])) {
40
                        $model->{$key} = empty(trim($value)) ? null : $value;
41
                    }
42
                }
43
            }
44
        });
45
    }
46
47
    /**
48
     * Add joins for one or more relations
49
     * This determines the foreign key relations automatically to prevent the need to figure out the columns.
50
     * Usages:
51
     * $query->modelJoin('customers')
52
     * $query->modelJoin('customer.client')
53
     *
54
     * @param Builder $query
55
     * @param string $relationName
56
     * @param string $operator
57
     * @param string $type
58
     * @param bool $where
59
     * @param bool $relatedSelect
60
     * @param string|null $direction
61
     *
62
     * @return Builder
63
     */
64
    public function scopeModelJoin(
65
        Builder $query,
66
        $relationName,
67
        $operator = '=',
68
        $type = 'left',
69
        $where = false,
70
        $relatedSelect = true,
71
        $direction = null
72
    ) {
73
        $connection = $this->connection;
0 ignored issues
show
Bug introduced by
The property connection 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...
74
75
        foreach ($this->parseRelationNames($relationName) as $relation) {
76
            $tableName = $relation->getRelated()->getTable();
77
            // if using a 'table' AS 'tableAlias' in a from statement, otherwise alias will be the table name
78
            $from = explode(' ', $relation->getQuery()->getQuery()->from);
79
            $tableAlias = array_pop($from);
80
81
            if (empty($query->getQuery()->columns)) {
82
                $query->select($this->getTable() . ".*");
0 ignored issues
show
Bug introduced by
It seems like getTable() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
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...
83
            }
84
            if ($relatedSelect) {
85
                foreach (Schema::connection($connection)->getColumnListing($tableName) as $relatedColumn) {
86
                    $query->addSelect(
87
                        new Expression("`$tableAlias`.`$relatedColumn` AS `$tableAlias.$relatedColumn`")
88
                    );
89
                }
90
            }
91
            $query->relationJoin($tableName, $tableAlias, $relation, $operator, $type, $where, $direction);
92
        }
93
94
        return $query;
95
    }
96
97
    /**
98
     * Get the relations from a relation name
99
     * $relationName can be a single relation
100
     * Usage for User model:
101
     * parseRelationNames('customer') returns [$user->customer()]
102
     * parseRelationNames('customer.contact') returns [$user->customer(), $user->customer->contact()]
103
     *
104
     * @param string $relationName
105
     * @return Relation[]
106
     */
107
    protected function parseRelationNames($relationName)
108
    {
109
        $relationNames = explode('.', $relationName);
110
        $parentRelationName = null;
111
        $relations = [];
112
113
        foreach ($relationNames as $relationName) {
114
            if (is_null($parentRelationName)) {
115
                $relations[] = $this->$relationName();
116
                $parentRelationName = $this->$relationName()->getRelated();
117
            } else {
118
                $relations[] = $parentRelationName->$relationName();
119
            }
120
        }
121
122
        return $relations;
123
    }
124
125
    /**
126
     * Join a model
127
     *
128
     * @param Builder $query
129
     * @param string $tableName
130
     * @param string $tableAlias
131
     * @param Relation $relation
132
     * @param string $operator
133
     * @param string $type
134
     * @param boolean $where
135
     * @param null $direction
136
     * @return Builder
137
     */
138
    public function scopeRelationJoin(
139
        Builder $query,
140
        $tableName,
141
        $tableAlias,
142
        $relation,
143
        $operator,
144
        $type,
145
        $where,
146
        $direction = null
147
    ) {
148
        if ($tableAlias !== '' && $tableName !== $tableAlias) {
149
            $fullTableName = $tableName . ' AS ' . $tableAlias;
150
        } else {
151
            $fullTableName = $tableName;
152
        }
153
154
        return $query->join($fullTableName, function (JoinClause $join) use (
155
            $tableName,
156
            $tableAlias,
157
            $relation,
158
            $operator,
159
            $direction
160
        ) {
161
            // If a HasOne relation and ordered - ie join to the latest/earliest
162
            if (class_basename($relation) === 'HasOne' && !empty($relation->toBase()->orders)) {
163
                return $this->hasOneJoin($relation, $join);
164
            } else {
165
                return $this->hasManyJoin($relation, $join, $tableName, $tableAlias, $operator, $direction);
166
            }
167
        }, null, null, $type, $where);
168
    }
169
170
    /**
171
     * Join a HasOne relation which is ordered
172
     *
173
     * @param Relation $relation
174
     * @param JoinClause $join
175
     * @return JoinClause
176
     */
177
    private function hasOneJoin($relation, $join)
178
    {
179
        // Get first relation order (should only be one)
180
        $order = $relation->toBase()->orders[0];
181
182
        return $join->on($order['column'], $this->hasOneJoinSql($relation, $order));
183
    }
184
185
    /**
186
     * Get join sql for a HasOne relation
187
     *
188
     * @param Relation $relation
189
     * @param array $order
190
     * @return Expression
191
     */
192
    public function hasOneJoinSql($relation, $order)
193
    {
194
        // Build subquery for getting first/last record in related table
195
        $subQuery = $this
196
            ->joinOne(
197
                $relation->getRelated()->newQuery(),
198
                $relation,
199
                $order['column'],
200
                $order['direction']
201
            )
202
            ->setBindings($relation->getBindings());
203
204
        return DB::raw('(' . $this->toSqlWithBindings($subQuery) . ')');
205
    }
206
207
    /**
208
     * Join a HasMany Relation
209
     *
210
     * @param Relation $relation
211
     * @param JoinClause $join
212
     * @param string $tableName
213
     * @param string $tableAlias
214
     * @param string $operator
215
     * @param string $direction
216
     * @return Builder|JoinClause
217
     */
218
    private function hasManyJoin($relation, $join, $tableName, $tableAlias, $operator, $direction)
219
    {
220
        // Get relation join columns
221
        $joinColumns = $this->getJoinColumns($relation);
222
223
        $first = $joinColumns->first;
224
        $second = $joinColumns->second;
225
        if ($tableName !== $tableAlias) {
226
            $first = str_replace($tableName, $tableAlias, $first);
227
            $second = str_replace($tableName, $tableAlias, $second);
228
        }
229
230
        $join->on($first, $operator, $second);
231
232
        // Add any where clauses from the relationship
233
        $join = $this->addWhereConstraints($join, $relation, $tableAlias);
234
235
        if (!is_null($direction) && get_class($relation) === HasMany::class) {
236
            $join = $this->hasManyJoinWhere($join, $first, $relation, $tableAlias, $direction);
0 ignored issues
show
Bug introduced by
It seems like $join can also be of type object<Illuminate\Database\Eloquent\Builder>; however, Blasttech\EloquentRelate...ait::hasManyJoinWhere() 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...
237
        }
238
239
        return $join;
240
    }
241
242
    /**
243
     * Adds a where for a relation's join columns and and min/max for a given column
244
     *
245
     * @param Builder $query
246
     * @param Relation $relation
247
     * @param string $column
248
     * @param string $direction
249
     * @return Builder
250
     */
251
    public function joinOne($query, $relation, $column, $direction)
252
    {
253
        // Get join fields
254
        $joinColumns = $this->getJoinColumns($relation);
255
256
        return $this->selectMinMax(
257
            $query->whereColumn($joinColumns->first, '=', $joinColumns->second),
258
            $column,
259
            $direction
260
        );
261
    }
262
263
    /**
264
     * Get the join columns for a relation
265
     *
266
     * @param Relation|BelongsTo|HasOneOrMany $relation
267
     * @return \stdClass
268
     */
269
    protected function getJoinColumns($relation)
270
    {
271
        // Get keys with table names
272
        if ($relation instanceof BelongsTo) {
273
            $first = $relation->getOwnerKey();
274
            $second = $relation->getForeignKey();
275
        } else {
276
            $first = $relation->getQualifiedParentKeyName();
277
            $second = $relation->getQualifiedForeignKeyName();
278
        }
279
280
        return (object)['first' => $first, 'second' => $second];
281
    }
282
283
    /**
284
     * Adds a select for a min or max on the given column, depending on direction given
285
     *
286
     * @param Builder $query
287
     * @param string $column
288
     * @param string $direction
289
     * @return Builder
290
     */
291
    public function selectMinMax($query, $column, $direction)
292
    {
293
        $column = $this->addBackticks($column);
294
295
        if ($direction == 'asc') {
296
            return $query->select(DB::raw('MIN(' . $column . ')'));
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...
297
        } else {
298
            return $query->select(DB::raw('MAX(' . $column . ')'));
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...
299
        }
300
    }
301
302
    /**
303
     * Add backticks to a table/column
304
     *
305
     * @param string $column
306
     * @return string
307
     */
308
    private function addBackticks($column)
309
    {
310
        return preg_match('/^[0-9a-zA-Z\.]*$/', $column) ?
311
            '`' . str_replace(['`', '.'], ['', '`.`'], $column) . '`' : $column;
312
    }
313
314
    /**
315
     * Return the sql for a query with the bindings replaced with the binding values
316
     *
317
     * @param Builder $builder
318
     * @return string
319
     */
320
    private function toSqlWithBindings(Builder $builder)
321
    {
322
        return vsprintf($this->replacePlaceholders($builder), array_map('addslashes', $builder->getBindings()));
323
    }
324
325
    /**
326
     * Replace SQL placeholders with '%s'
327
     *
328
     * @param Builder $builder
329
     * @return string
330
     */
331
    private function replacePlaceholders(Builder $builder)
332
    {
333
        return str_replace(['?'], ['\'%s\''], $builder->toSql());
334
    }
335
336
    /**
337
     * Add wheres if they exist for a relation
338
     *
339
     * @param Builder|JoinClause $builder
340
     * @param Relation|BelongsTo|HasOneOrMany $relation
341
     * @param string $table
342
     * @return Builder|JoinClause
343
     */
344
    protected function addWhereConstraints($builder, $relation, $table)
345
    {
346
        // Get where clauses from the relationship
347
        $wheres = collect($relation->toBase()->wheres)
348
            ->where('type', 'Basic')
349
            ->map(function ($where) use ($table) {
350
                // Add table name to column if it is absent
351
                return [$this->columnWithTableName($table, $where['column']), $where['operator'], $where['value']];
352
            })->toArray();
353
354
        if (!empty($wheres)) {
355
            $builder->where($wheres);
356
        }
357
358
        return $builder;
359
    }
360
361
    /**
362
     * Add table name to column name if table name not already included in column name
363
     *
364
     * @param string $table
365
     * @param string $column
366
     * @return string
367
     */
368
    private function columnWithTableName($table, $column)
369
    {
370
        return (preg_match('/(' . $table . '\.|`' . $table . '`)/i', $column) > 0 ? '' : $table . '.') . $column;
371
    }
372
373
    /**
374
     * If the relation is one-to-many, just get the first related record
375
     *
376
     * @param JoinClause $joinClause
377
     * @param string $column
378
     * @param HasMany|Relation $relation
379
     * @param string $table
380
     * @param string $direction
381
     *
382
     * @return JoinClause
383
     */
384
    public function hasManyJoinWhere(JoinClause $joinClause, $column, $relation, $table, $direction)
385
    {
386
        return $joinClause->where(
387
            $column,
388
            function ($subQuery) use ($table, $direction, $relation, $column) {
389
                $subQuery = $this->joinOne(
390
                    $subQuery->from($table),
391
                    $relation,
392
                    $column,
393
                    $direction
394
                );
395
396
                // Add any where statements with the relationship
397
                $subQuery = $this->addWhereConstraints($subQuery, $relation, $table);
398
399
                // Add any order statements with the relationship
400
                return $this->addOrder($subQuery, $relation, $table);
401
            }
402
        );
403
    }
404
405
    /**
406
     * Add orderBy if orders exist for a relation
407
     *
408
     * @param Builder|JoinClause $builder
409
     * @param Relation|BelongsTo|HasOneOrMany $relation
410
     * @param string $table
411
     * @return Builder
412
     */
413
    protected function addOrder($builder, $relation, $table)
414
    {
415
        if (!empty($relation->toBase()->orders)) {
416
            // Get where clauses from the relationship
417
            foreach ($relation->toBase()->orders as $order) {
418
                $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...
419
            }
420
        }
421
422
        return $builder;
423
    }
424
425
    /**
426
     * Set the order of a model
427
     *
428
     * @param Builder $query
429
     * @param string $orderField
430
     * @param string $dir
431
     * @return Builder
432
     */
433
    public function scopeOrderByCustom(Builder $query, $orderField, $dir)
434
    {
435
        if (!isset($this->order_fields) || !is_array($this->order_fields)) {
436
            throw new InvalidArgumentException(get_class($this) . ' order fields not set correctly.');
437
        }
438
439
        if (($orderField === '' || $dir === '')
440
            && (!isset($this->order_defaults) || !is_array($this->order_defaults))) {
441
            throw new InvalidArgumentException(get_class($this) . ' order defaults not set and not overriden.');
442
        }
443
444
        // Remove order global scope if it exists
445
        /** @var Model $this */
446
        $globalScopes = $this->getGlobalScopes();
447
        if (isset($globalScopes['order'])) {
448
            $query->withoutGlobalScope('order');
449
        }
450
451
        return $query->setCustomOrder($orderField, $dir);
452
    }
453
454
    /**
455
     * Check if column being sorted by is from a related model
456
     *
457
     * @param Builder $query
458
     * @param string $column
459
     * @param string $direction
460
     * @return Builder
461
     */
462
    public function scopeOrderByCheckModel(Builder $query, $column, $direction)
463
    {
464
        $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...
465
466
        $periodPos = strpos($column, '.');
467
        if (isset($this->order_relations) && ($periodPos !== false || isset($this->order_relations[$column]))) {
468
            $table = ($periodPos !== false ? substr($column, 0, $periodPos) : $column);
469
470
            if (isset($this->order_relations[$table]) &&
471
                !$this->hasJoin($query, $table, $this->order_relations[$table])) {
472
                $columnRelations = $this->order_relations[$table];
473
474
                $query->modelJoin(
475
                    $columnRelations,
476
                    '=',
477
                    'left',
478
                    false,
479
                    false
480
                );
481
            }
482
        }
483
484
        return $query;
485
    }
486
487
    /**
488
     * Check if this model has already been joined to a table or relation
489
     *
490
     * @param Builder $builder
491
     * @param string $table
492
     * @param \Illuminate\Database\Eloquent\Relations\Relation $relation
493
     * @return bool
494
     */
495
    protected function hasJoin(Builder $builder, $table, $relation)
496
    {
497
        if (!$this->checkJoins($builder, $table)) {
498
            return $this->checkEagerLoads($builder, $relation);
499
        } else {
500
            return true;
501
        }
502
    }
503
504
    /**
505
     * Check if model is currently joined to $table
506
     *
507
     * @param Builder $builder
508
     * @param string $table
509
     * @return bool
510
     */
511
    private function checkJoins(Builder $builder, $table)
0 ignored issues
show
Coding Style introduced by
function checkJoins() does not seem to conform to the naming convention (^(?:is|has|should|may|supports)).

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
    {
513
        $joins = $builder->getQuery()->joins;
514
        if (!is_null($joins)) {
515
            foreach ($joins as $joinClause) {
516
                if ($joinClause->table == $table) {
517
                    return true;
518
                }
519
            }
520
        }
521
522
        return false;
523
    }
524
525
    /**
526
     * Check if relation exists in eager loads
527
     *
528
     * @param Builder $builder
529
     * @param \Illuminate\Database\Eloquent\Relations\Relation $relation
530
     * @return bool
531
     */
532
    private function checkEagerLoads(Builder $builder, $relation)
0 ignored issues
show
Coding Style introduced by
function checkEagerLoads() does not seem to conform to the naming convention (^(?:is|has|should|may|supports)).

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...
533
    {
534
        $eagerLoads = $builder->getEagerLoads();
535
536
        return !is_null($eagerLoads) && in_array($relation, $eagerLoads);
537
    }
538
539
    /**
540
     * Set the model order
541
     *
542
     * @param Builder $query
543
     * @param string $column
544
     * @param string $direction
545
     * @return Builder
546
     */
547
    public function scopeSetCustomOrder(Builder $query, $column, $direction)
548
    {
549
        if (isset($this->order_defaults)) {
550
            // If $column not in order_fields list, use default
551
            if ($column == '' || !isset($this->order_fields[$column])) {
552
                $column = $this->order_defaults['field'];
553
            }
554
555
            // If $direction not asc or desc, use default
556
            if ($direction == '' || !in_array(strtoupper($direction), ['ASC', 'DESC'])) {
557
                $direction = $this->order_defaults['dir'];
558
            }
559
        }
560
561
        if (!is_array($this->order_fields[$column])) {
562
            $query->orderByCheckModel($this->order_fields[$column], $direction);
563
        } else {
564
            foreach ($this->order_fields[$column] as $dbField) {
565
                $query->orderByCheckModel($dbField, $direction);
566
            }
567
        }
568
569
        return $query;
570
    }
571
572
    /**
573
     * Switch a query to be a subquery of a model
574
     *
575
     * @param Builder $query
576
     * @param Builder $model
577
     * @return Builder
578
     */
579
    public function scopeSetSubquery(Builder $query, $model)
580
    {
581
        $sql = $this->toSqlWithBindings($model);
582
        $table = $model->getQuery()->from;
583
584
        return $query
585
            ->from(DB::raw("({$sql}) as " . $table))
586
            ->select($table . '.*');
587
    }
588
589
    /**
590
     * Use a model method to add columns or joins if in the order options
591
     *
592
     * @param Builder $query
593
     * @param string $order
594
     * @return Builder
595
     */
596
    public function scopeOrderByWith(Builder $query, $order)
597
    {
598
        if (isset($this->order_with[$order])) {
599
            $with = 'with' . $this->order_with[$order];
600
601
            $query->$with();
602
        }
603
604
        if (isset($this->order_fields[$order])) {
605
            $orderOption = (explode('.', $this->order_fields[$order]))[0];
606
607
            if (isset($this->order_relations[$orderOption])) {
608
                $query->modelJoin(
609
                    $this->order_relations[$orderOption],
610
                    '=',
611
                    'left',
612
                    false,
613
                    false
614
                );
615
            }
616
        }
617
618
        return $query;
619
    }
620
621
    /**
622
     * Add where statements for the model search fields
623
     *
624
     * @param Builder $query
625
     * @param string $searchText
626
     * @return Builder
627
     */
628
    public function scopeSearch(Builder $query, $searchText = '')
629
    {
630
        $searchText = trim($searchText);
631
632
        // If search is set
633
        if ($searchText != "") {
634
            if (!isset($this->search_fields) || !is_array($this->search_fields) || empty($this->search_fields)) {
635
                throw new InvalidArgumentException(get_class($this) . ' search properties not set correctly.');
636
            } else {
637
                $query = $this->checkSearchFields($query, $searchText);
638
            }
639
        }
640
641
        return $query;
642
    }
643
644
    /**
645
     * Add where statements for search fields to search for searchText
646
     *
647
     * @param Builder $query
648
     * @param string $searchText
649
     * @return Builder
650
     */
651
    private function checkSearchFields($query, $searchText)
652
    {
653
        return $query->where(function (Builder $query) use ($searchText) {
654
            if (isset($this->search_fields) && !empty($this->search_fields)) {
655
                /** @var Model $this */
656
                $table = $this->getTable();
657
                foreach ($this->search_fields as $searchField => $searchFieldParameters) {
658
                    $query = $this->checkSearchField($query, $table, $searchField, $searchFieldParameters, $searchText);
659
                }
660
            }
661
662
            return $query;
663
        });
664
    }
665
666
    /**
667
     * Add where statement for a search field
668
     *
669
     * @param Builder $query
670
     * @param string $table
671
     * @param string $searchField
672
     * @param array $searchFieldParameters
673
     * @param string $searchText
674
     * @return Builder
675
     */
676
    private function checkSearchField($query, $table, $searchField, $searchFieldParameters, $searchText)
677
    {
678
        if (!isset($searchFieldParameters['regex']) || preg_match($searchFieldParameters['regex'], $searchText)) {
679
            $searchColumn = is_array($searchFieldParameters) ? $searchField : $searchFieldParameters;
680
681
            if (isset($searchFieldParameters['relation'])) {
682
                return $this->searchRelation($query, $searchFieldParameters, $searchColumn, $searchText);
683
            } else {
684
                return $this->searchThis($query, $searchFieldParameters, $table, $searchColumn, $searchText);
685
            }
686
        } else {
687
            return $query;
688
        }
689
    }
690
691
    /**
692
     * Add where condition to search current model
693
     *
694
     * @param Builder $query
695
     * @param array $searchFieldParameters
696
     * @param string $table
697
     * @param string $searchColumn
698
     * @param string $searchText
699
     * @return Builder
700
     */
701
    public function searchThis(Builder $query, $searchFieldParameters, $table, $searchColumn, $searchText)
702
    {
703
        $searchOperator = $searchFieldParameters['operator'] ?? 'like';
704
        $searchValue = $searchFieldParameters['value'] ?? '%{{search}}%';
705
706
        return $query->orWhere(
707
            $table . '.' . $searchColumn,
708
            $searchOperator,
709
            str_replace('{{search}}', $searchText, $searchValue)
710
        );
711
    }
712
713
    /**
714
     * Add where condition to search a relation
715
     *
716
     * @param Builder $query
717
     * @param array $searchFieldParameters
718
     * @param string $searchColumn
719
     * @param string $searchText
720
     * @return Builder
721
     */
722
    private function searchRelation(Builder $query, $searchFieldParameters, $searchColumn, $searchText)
723
    {
724
        $relation = $searchFieldParameters['relation'];
725
        $relatedTable = $this->$relation()->getRelated()->getTable();
726
727
        return $query->orWhere(function (Builder $query) use (
728
            $searchText,
729
            $searchColumn,
730
            $searchFieldParameters,
731
            $relation,
732
            $relatedTable
733
        ) {
734
            return $query->orWhereHas($relation, function (Builder $query2) use (
735
                $searchText,
736
                $searchColumn,
737
                $searchFieldParameters,
738
                $relatedTable
739
            ) {
740
                return $query2->where($relatedTable . '.' . $searchColumn, 'like', $searchText . '%');
741
            });
742
        });
743
    }
744
}
745