Builder::addSearchClauses()   B
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 27
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 14
nc 2
nop 4
dl 0
loc 27
rs 8.8571
c 0
b 0
f 0
1
<?php
2
3
namespace Sofa\Eloquence;
4
5
use Sofa\Eloquence\Searchable\Column;
6
use Illuminate\Database\Query\Expression;
7
use Sofa\Hookable\Builder as HookableBuilder;
8
use Sofa\Eloquence\Searchable\ColumnCollection;
9
use Sofa\Eloquence\Contracts\Relations\JoinerFactory;
10
use Sofa\Eloquence\Contracts\Searchable\ParserFactory;
11
use Illuminate\Database\Query\Grammars\PostgresGrammar;
12
use Sofa\Eloquence\Searchable\Subquery as SearchableSubquery;
13
14
/**
15
 * @method $this leftJoin($table, $one, $operator, $two)
16
 */
17
class Builder extends HookableBuilder
18
{
19
    /**
20
     * Parser factory instance.
21
     *
22
     * @var \Sofa\Eloquence\Contracts\Searchable\ParserFactory
23
     */
24
    protected static $parser;
25
26
    /**
27
     * Joiner factory instance.
28
     *
29
     * @var \Sofa\Eloquence\Contracts\Relations\JoinerFactory
30
     */
31
    protected static $joinerFactory;
32
33
    /**
34
     * Relations joiner instance.
35
     *
36
     * @var \Sofa\Eloquence\Contracts\Relations\Joiner
37
     */
38
    protected $joiner;
39
40
    /*
41
    |--------------------------------------------------------------------------
42
    | Additional features
43
    |--------------------------------------------------------------------------
44
    */
45
46
    /**
47
     * Execute the query as a "select" statement.
48
     *
49
     * @param  array $columns
50
     * @return \Illuminate\Database\Eloquent\Collection
51
     */
52
    public function get($columns = ['*'])
53
    {
54
        if ($this->query->from instanceof Subquery) {
55
            $this->wheresToSubquery($this->query->from);
56
        }
57
58
        return parent::get($columns);
0 ignored issues
show
Bug Compatibility introduced by
The expression parent::get($columns); of type Illuminate\Database\Eloq...base\Eloquent\Builder[] adds the type Illuminate\Database\Eloquent\Builder[] to the return on line 58 which is incompatible with the return type documented by Sofa\Eloquence\Builder::get of type Illuminate\Database\Eloquent\Collection.
Loading history...
59
    }
60
61
    /**
62
     * Search through any columns on current table or any defined relations
63
     * and return results ordered by search relevance.
64
     *
65
     * @param  array|string $query
66
     * @param  array $columns
67
     * @param  boolean $fulltext
68
     * @param  float $threshold
69
     * @return $this
70
     */
71
    public function search($query, $columns = null, $fulltext = true, $threshold = null)
72
    {
73
        if (is_bool($columns)) {
74
            list($fulltext, $columns) = [$columns, []];
75
        }
76
77
        $parser = static::$parser->make();
78
79
        $words = is_array($query) ? $query : $parser->parseQuery($query, $fulltext);
80
81
        $columns = $parser->parseWeights($columns ?: $this->model->getSearchableColumns());
82
83
        if (count($words) && count($columns)) {
84
            $this->query->from($this->buildSubquery($words, $columns, $threshold));
85
        }
86
87
        return $this;
88
    }
89
90
    /**
91
     * Build the search subquery.
92
     *
93
     * @param  array $words
94
     * @param  array $mappings
95
     * @param  float $threshold
96
     * @return \Sofa\Eloquence\Searchable\Subquery
97
     */
98
    protected function buildSubquery(array $words, array $mappings, $threshold)
99
    {
100
        $subquery = new SearchableSubquery($this->query->newQuery(), $this->model->getTable());
101
102
        $columns = $this->joinForSearch($mappings, $subquery);
103
104
        $threshold = (is_null($threshold))
105
                        ? array_sum($columns->getWeights()) / 4
106
                        : (float) $threshold;
107
108
        $modelTableName = $this->model->getTable();
109
110
        // If we are dealing with a SQL Server database, we need to group by all column names
111
        if ($this->model->getConnection()->getDriverName() == 'sqlsrv') {
112
            $groupByColumns = $this->model->getConnection()->getSchemaBuilder()->getColumnListing($modelTableName);
113
            // Force column names to be fully-qualified
114
            foreach ($groupByColumns as &$column) {
115
                $column = $modelTableName . '.' . $column;
116
            }
117
        } else {
118
            $groupByColumns = $this->model->getQualifiedKeyName();
119
        }
120
121
        $subquery->select($this->model->getTable() . '.*')
0 ignored issues
show
Documentation Bug introduced by
The method select does not exist on object<Sofa\Eloquence\Searchable\Subquery>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
122
                 ->from($this->model->getTable())
123
                 ->groupBy($groupByColumns);
124
125
        $this->addSearchClauses($subquery, $columns, $words, $threshold);
126
127
        return $subquery;
128
    }
129
130
    /**
131
     * Add select and where clauses on the subquery.
132
     *
133
     * @param  \Sofa\Eloquence\Searchable\Subquery $subquery
134
     * @param  \Sofa\Eloquence\Searchable\ColumnCollection $columns
135
     * @param  array $words
136
     * @param  float $threshold
137
     * @return void
138
     */
139
    protected function addSearchClauses(
140
        SearchableSubquery $subquery,
141
        ColumnCollection $columns,
142
        array $words,
143
        $threshold
144
    ) {
145
        $whereBindings = $this->searchSelect($subquery, $columns, $words, $threshold);
0 ignored issues
show
Unused Code introduced by
The call to Builder::searchSelect() has too many arguments starting with $threshold.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
146
147
        // For morphOne/morphMany support we need to port the bindings from JoinClauses.
148
        $joinBindings = array_flatten(array_pluck((array) $subquery->getQuery()->joins, 'bindings'));
149
150
        $this->addBinding($joinBindings, 'select');
0 ignored issues
show
Documentation Bug introduced by
The method addBinding does not exist on object<Sofa\Eloquence\Builder>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
151
152
        // Developer may want to skip the score threshold filtering by passing zero
153
        // value as threshold in order to simply order full result by relevance.
154
        // Otherwise we are going to add where clauses for speed improvement.
155
        if ($threshold > 0) {
156
            $this->searchWhere($subquery, $columns, $words, $whereBindings);
157
        }
158
159
        $this->query->where('relevance', '>=', new Expression($threshold));
160
161
        $this->query->orders = array_merge(
162
            [['column' => 'relevance', 'direction' => 'desc']],
163
            (array) $this->query->orders
164
        );
165
    }
166
167
    /**
168
     * Apply relevance select on the subquery.
169
     *
170
     * @param  \Sofa\Eloquence\Searchable\Subquery $subquery
171
     * @param  \Sofa\Eloquence\Searchable\ColumnCollection $columns
172
     * @param  array $words
173
     * @return array
174
     */
175
    protected function searchSelect(SearchableSubquery $subquery, ColumnCollection $columns, array $words)
176
    {
177
        $cases = $bindings = [];
178
179
        foreach ($columns as $column) {
180
            list($cases[], $binding) = $this->buildCase($column, $words);
181
182
            $bindings = array_merge_recursive($bindings, $binding);
183
        }
184
185
        $select = implode(' + ', $cases);
186
187
        $subquery->selectRaw("max({$select}) as relevance");
0 ignored issues
show
Documentation Bug introduced by
The method selectRaw does not exist on object<Sofa\Eloquence\Searchable\Subquery>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
188
189
        $this->addBinding($bindings['select'], 'select');
0 ignored issues
show
Documentation Bug introduced by
The method addBinding does not exist on object<Sofa\Eloquence\Builder>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
190
191
        return $bindings['where'];
192
    }
193
194
    /**
195
     * Apply where clauses on the subquery.
196
     *
197
     * @param  \Sofa\Eloquence\Searchable\Subquery $subquery
198
     * @param  \Sofa\Eloquence\Searchable\ColumnCollection $columns
199
     * @param  array $words
200
     * @return void
201
     */
202
    protected function searchWhere(
203
        SearchableSubquery $subquery,
204
        ColumnCollection $columns,
205
        array $words,
206
        array $bindings
207
    ) {
208
        $operator = $this->getLikeOperator();
209
210
        $wheres = [];
211
212
        foreach ($columns as $column) {
213
            $wheres[] = implode(
214
                ' or ',
215
                array_fill(0, count($words), sprintf('%s %s ?', $column->getWrapped(), $operator))
216
            );
217
        }
218
219
        $where = implode(' or ', $wheres);
220
221
        $subquery->whereRaw("({$where})");
0 ignored issues
show
Documentation Bug introduced by
The method whereRaw does not exist on object<Sofa\Eloquence\Searchable\Subquery>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
222
223
        $this->addBinding($bindings, 'select');
0 ignored issues
show
Documentation Bug introduced by
The method addBinding does not exist on object<Sofa\Eloquence\Builder>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
224
    }
225
226
    /**
227
     * Move where clauses to subquery to improve performance.
228
     *
229
     * @param  \Sofa\Eloquence\Searchable\Subquery $subquery
230
     * @return void
231
     */
232
    protected function wheresToSubquery(SearchableSubquery $subquery)
233
    {
234
        $bindingKey = 0;
235
236
        $typesToMove = [
237
            'basic', 'in', 'notin', 'between', 'null',
238
            'notnull', 'date', 'day', 'month', 'year',
239
        ];
240
241
        // Here we are going to move all the where clauses that we might apply
242
        // on the subquery in order to improve performance, since this way
243
        // we can drastically reduce number of joined rows on subquery.
244
        foreach ((array) $this->query->wheres as $key => $where) {
245
            $type = strtolower($where['type']);
246
247
            $bindingsCount = $this->countBindings($where, $type);
248
249
            if (in_array($type, $typesToMove) && $this->model->hasColumn($where['column'])) {
250
                unset($this->query->wheres[$key]);
251
252
                $where['column'] = $this->model->getTable() . '.' . $where['column'];
253
254
                $subquery->getQuery()->wheres[] = $where;
255
256
                $whereBindings = $this->query->getRawBindings()['where'];
257
258
                $bindings = array_splice($whereBindings, $bindingKey, $bindingsCount);
259
260
                $this->query->setBindings($whereBindings, 'where');
261
262
                $this->query->addBinding($bindings, 'select');
263
264
            // if where is not to be moved onto the subquery, let's increment
265
            // binding key appropriately, so we can reliably move binding
266
            // for the next where clauses in the loop that is running.
267
            } else {
268
                $bindingKey += $bindingsCount;
269
            }
270
        }
271
    }
272
273
    /**
274
     * Get number of bindings provided for a where clause.
275
     *
276
     * @param  array   $where
277
     * @param  string  $type
278
     * @return integer
279
     */
280
    protected function countBindings(array $where, $type)
281
    {
282
        if ($this->isHasWhere($where, $type)) {
283
            return substr_count($where['column'] . $where['value'], '?');
284
        } elseif ($type === 'basic') {
285
            return (int) !$where['value'] instanceof Expression;
286
        } elseif (in_array($type, ['basic', 'date', 'year', 'month', 'day'])) {
287
            return (int) !$where['value'] instanceof Expression;
288
        } elseif (in_array($type, ['null', 'notnull'])) {
289
            return 0;
290
        } elseif ($type === 'between') {
291
            return 2;
292
        } elseif (in_array($type, ['in', 'notin'])) {
293
            return count($where['values']);
294
        } elseif ($type === 'raw') {
295
            return substr_count($where['sql'], '?');
296
        } elseif (in_array($type, ['nested', 'sub', 'exists', 'notexists', 'insub', 'notinsub'])) {
297
            return count($where['query']->getBindings());
298
        }
299
    }
300
301
    /**
302
     * Determine whether where clause is eloquent has subquery.
303
     *
304
     * @param  array  $where
305
     * @param  string $type
306
     * @return boolean
307
     */
308
    protected function isHasWhere($where, $type)
309
    {
310
        return $type === 'basic'
311
                && $where['column'] instanceof Expression
312
                && $where['value'] instanceof Expression;
313
    }
314
315
    /**
316
     * Build case clause from all words for a single column.
317
     *
318
     * @param  \Sofa\Eloquence\Searchable\Column $column
319
     * @param  array  $words
320
     * @return array
321
     */
322
    protected function buildCase(Column $column, array $words)
323
    {
324
        // THIS IS BAD
325
        // @todo refactor
326
327
        $operator = $this->getLikeOperator();
328
329
        $bindings['select'] = $bindings['where'] = array_map(function ($word) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
$bindings was never initialized. Although not strictly required by PHP, it is generally a good practice to add $bindings = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
330
            return $this->caseBinding($word);
331
        }, $words);
332
333
        $case = $this->buildEqualsCase($column, $words);
334
335
        if (strpos(implode('', $words), '*') !== false) {
336
            $leftMatching = [];
337
338 View Code Duplication
            foreach ($words as $key => $word) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
339
                if ($this->isLeftMatching($word)) {
340
                    $leftMatching[] = sprintf('%s %s ?', $column->getWrapped(), $operator);
341
                    $bindings['select'][] = $bindings['where'][$key] = $this->caseBinding($word) . '%';
342
                }
343
            }
344
345 View Code Duplication
            if (count($leftMatching)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
346
                $leftMatching = implode(' or ', $leftMatching);
347
                $score = 5 * $column->getWeight();
348
                $case .= " + case when {$leftMatching} then {$score} else 0 end";
349
            }
350
351
            $wildcards = [];
352
353 View Code Duplication
            foreach ($words as $key => $word) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
354
                if ($this->isWildcard($word)) {
355
                    $wildcards[] = sprintf('%s %s ?', $column->getWrapped(), $operator);
356
                    $bindings['select'][] = $bindings['where'][$key] = '%'.$this->caseBinding($word) . '%';
357
                }
358
            }
359
360 View Code Duplication
            if (count($wildcards)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
361
                $wildcards = implode(' or ', $wildcards);
362
                $score = 1 * $column->getWeight();
363
                $case .= " + case when {$wildcards} then {$score} else 0 end";
364
            }
365
        }
366
367
        return [$case, $bindings];
368
    }
369
370
    /**
371
     * Replace '?' with single character SQL wildcards.
372
     *
373
     * @param  string $word
374
     * @return string
375
     */
376
    protected function caseBinding($word)
377
    {
378
        $parser = static::$parser->make();
379
380
        return str_replace('?', '_', $parser->stripWildcards($word));
381
    }
382
383
    /**
384
     * Build basic search case for 'equals' comparison.
385
     *
386
     * @param  \Sofa\Eloquence\Searchable\Column $column
387
     * @param  array  $words
388
     * @return string
389
     */
390
    protected function buildEqualsCase(Column $column, array $words)
391
    {
392
        $equals = implode(' or ', array_fill(0, count($words), sprintf('%s = ?', $column->getWrapped())));
393
394
        $score = 15 * $column->getWeight();
395
396
        return "case when {$equals} then {$score} else 0 end";
397
    }
398
399
    /**
400
     * Determine whether word ends with wildcard.
401
     *
402
     * @param  string  $word
403
     * @return boolean
404
     */
405
    protected function isLeftMatching($word)
406
    {
407
        return ends_with($word, '*');
408
    }
409
410
    /**
411
     * Determine whether word starts and ends with wildcards.
412
     *
413
     * @param  string  $word
414
     * @return boolean
415
     */
416
    protected function isWildcard($word)
417
    {
418
        return ends_with($word, '*') && starts_with($word, '*');
419
    }
420
421
    /**
422
     * Get driver-specific case insensitive like operator.
423
     *
424
     * @return string
425
     */
426
    public function getLikeOperator()
427
    {
428
        $grammar = $this->query->getGrammar();
429
430
        if ($grammar instanceof PostgresGrammar) {
431
            return 'ilike';
432
        }
433
434
        return 'like';
435
    }
436
437
    /**
438
     * Join related tables on the search subquery.
439
     *
440
     * @param  array $mappings
441
     * @param  \Sofa\Eloquence\Searchable\Subquery $subquery
442
     * @return \Sofa\Eloquence\Searchable\ColumnCollection
443
     */
444
    protected function joinForSearch($mappings, $subquery)
445
    {
446
        $mappings = is_array($mappings) ? $mappings : (array) $mappings;
447
448
        $columns = new ColumnCollection;
449
450
        $grammar = $this->query->getGrammar();
451
452
        $joiner = static::$joinerFactory->make($subquery->getQuery(), $this->model);
453
454
        // Here we loop through the search mappings in order to join related tables
455
        // appropriately and build a searchable column collection, which we will
456
        // use to build select and where clauses with correct table prefixes.
457
        foreach ($mappings as $mapping => $weight) {
458
            if (strpos($mapping, '.') !== false) {
459
                list($relation, $column) = $this->model->parseMappedColumn($mapping);
460
461
                $related = $joiner->leftJoin($relation);
462
463
                $columns->add(
464
                    new Column($grammar, $related->getTable(), $column, $mapping, $weight)
465
                );
466
            } else {
467
                $columns->add(
468
                    new Column($grammar, $this->model->getTable(), $mapping, $mapping, $weight)
469
                );
470
            }
471
        }
472
473
        return $columns;
474
    }
475
476
    /**
477
     * Prefix selected columns with table name in order to avoid collisions.
478
     *
479
     * @return $this
480
     */
481
    public function prefixColumnsForJoin()
482
    {
483
        if (!$columns = $this->query->columns) {
484
            return $this->select($this->model->getTable() . '.*');
485
        }
486
487
        foreach ($columns as $key => $column) {
488
            if ($this->model->hasColumn($column)) {
489
                $columns[$key] = $this->model->getTable() . '.' . $column;
490
            }
491
        }
492
493
        $this->query->columns = $columns;
494
495
        return $this;
496
    }
497
498
    /**
499
     * Join related tables.
500
     *
501
     * @param  array|string $relations
502
     * @param  string $type
503
     * @return $this
504
     */
505
    public function joinRelations($relations, $type = 'inner')
506
    {
507
        if (is_null($this->joiner)) {
508
            $this->joiner = static::$joinerFactory->make($this);
0 ignored issues
show
Documentation introduced by
$this is of type this<Sofa\Eloquence\Builder>, but the function expects a object<Illuminate\Database\Query\Builder>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
509
        }
510
511
        if (!is_array($relations)) {
512
            list($relations, $type) = [func_get_args(), 'inner'];
513
        }
514
515
        foreach ($relations as $relation) {
516
            $this->joiner->join($relation, $type);
517
        }
518
519
        return $this;
520
    }
521
522
    /**
523
     * Left join related tables.
524
     *
525
     * @param  array|string $relations
526
     * @return $this
527
     */
528
    public function leftJoinRelations($relations)
529
    {
530
        $relations = is_array($relations) ? $relations : func_get_args();
531
532
        return $this->joinRelations($relations, 'left');
533
    }
534
535
    /**
536
     * Right join related tables.
537
     *
538
     * @param  array|string $relations
539
     * @return $this
540
     */
541
    public function rightJoinRelations($relations)
542
    {
543
        $relations = is_array($relations) ? $relations : func_get_args();
544
545
        return $this->joinRelations($relations, 'right');
546
    }
547
548
    /**
549
     * Set search query parser factory instance.
550
     *
551
     * @param \Sofa\Eloquence\Contracts\Searchable\ParserFactory $factory
552
     */
553
    public static function setParserFactory(ParserFactory $factory)
554
    {
555
        static::$parser = $factory;
556
    }
557
558
    /**
559
     * Set the relations joiner factory instance.
560
     *
561
     * @param \Sofa\Eloquence\Contracts\Relations\JoinerFactory $factory
562
     */
563
    public static function setJoinerFactory(JoinerFactory $factory)
564
    {
565
        static::$joinerFactory = $factory;
566
    }
567
}
568