GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

Builder::buildEqualsCase()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 2
dl 0
loc 8
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Sofa\Eloquence;
4
5
use Illuminate\Database\Eloquent\Collection;
6
use Illuminate\Support\Str;
7
use Sofa\Eloquence\Contracts\Relations\Joiner;
8
use Sofa\Eloquence\Searchable\Column;
9
use Illuminate\Database\Query\Expression;
10
use Sofa\Hookable\Builder as HookableBuilder;
11
use Sofa\Eloquence\Searchable\ColumnCollection;
12
use Sofa\Eloquence\Contracts\Relations\JoinerFactory;
13
use Sofa\Eloquence\Contracts\Searchable\ParserFactory;
14
use Illuminate\Database\Query\Grammars\PostgresGrammar;
15
use Sofa\Eloquence\Searchable\Subquery as SearchableSubquery;
16
17
/**
18
 * @method $this leftJoin($table, $one, $operator, $two)
19
 */
20
class Builder extends HookableBuilder
21
{
22
    /**
23
     * Parser factory instance.
24
     *
25
     * @var ParserFactory
26
     */
27
    protected static $parser;
28
29
    /**
30
     * Joiner factory instance.
31
     *
32
     * @var JoinerFactory
33
     */
34
    protected static $joinerFactory;
35
36
    /**
37
     * Relations joiner instance.
38
     *
39
     * @var Joiner
40
     */
41
    protected $joiner;
42
43
    /*
44
    |--------------------------------------------------------------------------
45
    | Additional features
46
    |--------------------------------------------------------------------------
47
    */
48
49
    /**
50
     * Execute the query as a "select" statement.
51
     *
52
     * @param  array $columns
53
     * @return Collection
54
     */
55
    public function get($columns = ['*'])
56
    {
57
        if ($this->query->from instanceof Subquery) {
58
            $this->wheresToSubquery($this->query->from);
59
        }
60
61
        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 61 which is incompatible with the return type documented by Sofa\Eloquence\Builder::get of type Illuminate\Database\Eloquent\Collection.
Loading history...
62
    }
63
64
    /**
65
     * Search through any columns on current table or any defined relations
66
     * and return results ordered by search relevance.
67
     *
68
     * @param  array|string $query
69
     * @param  array $columns
70
     * @param  bool $fulltext
71
     * @param  float $threshold
72
     * @return $this
73
     */
74
    public function search($query, $columns = null, $fulltext = true, $threshold = null)
75
    {
76
        if (is_bool($columns)) {
77
            list($fulltext, $columns) = [$columns, []];
78
        }
79
80
        $parser = static::$parser->make();
81
82
        $words = is_array($query) ? $query : $parser->parseQuery($query, $fulltext);
83
84
        $columns = $parser->parseWeights($columns ?: $this->model->getSearchableColumns());
85
86
        if (count($words) && count($columns)) {
87
            $this->query->from($this->buildSubquery($words, $columns, $threshold));
88
        }
89
90
        return $this;
91
    }
92
93
    /**
94
     * Build the search subquery.
95
     *
96
     * @param  array $words
97
     * @param  array $mappings
98
     * @param  float $threshold
99
     * @return SearchableSubquery
100
     */
101
    protected function buildSubquery(array $words, array $mappings, $threshold)
102
    {
103
        $subquery = new SearchableSubquery($this->query->newQuery(), $this->model->getTable());
104
105
        $columns = $this->joinForSearch($mappings, $subquery);
106
107
        $threshold = (is_null($threshold))
108
                        ? array_sum($columns->getWeights()) / 4
109
                        : (float) $threshold;
110
111
        $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...
112
                 ->from($this->model->getTable())
113
                 ->groupBy($this->model->getQualifiedKeyName());
114
115
        $this->addSearchClauses($subquery, $columns, $words, $threshold);
116
117
        return $subquery;
118
    }
119
120
    /**
121
     * Add select and where clauses on the subquery.
122
     *
123
     * @param SearchableSubquery $subquery
124
     * @param ColumnCollection $columns
125
     * @param  array $words
126
     * @param  float $threshold
127
     * @return void
128
     */
129
    protected function addSearchClauses(
130
        SearchableSubquery $subquery,
131
        ColumnCollection $columns,
132
        array $words,
133
        $threshold
134
    ) {
135
        $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...
136
137
        // For morphOne/morphMany support we need to port the bindings from JoinClauses.
138
        $joinBindings = collect($subquery->getQuery()->joins)->flatMap(function ($join) {
139
            return $join->getBindings();
140
        })->all();
141
142
        $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...
143
144
        // Developer may want to skip the score threshold filtering by passing zero
145
        // value as threshold in order to simply order full result by relevance.
146
        // Otherwise we are going to add where clauses for speed improvement.
147
        if ($threshold > 0) {
148
            $this->searchWhere($subquery, $columns, $words, $whereBindings);
149
        }
150
151
        $this->query->where('relevance', '>=', new Expression(number_format($threshold, 2)));
152
153
        $this->query->orders = array_merge(
154
            [['column' => 'relevance', 'direction' => 'desc']],
155
            (array) $this->query->orders
156
        );
157
    }
158
159
    /**
160
     * Apply relevance select on the subquery.
161
     *
162
     * @param SearchableSubquery $subquery
163
     * @param ColumnCollection $columns
164
     * @param  array $words
165
     * @return array
166
     */
167
    protected function searchSelect(SearchableSubquery $subquery, ColumnCollection $columns, array $words)
168
    {
169
        $cases = $bindings = [];
170
171
        foreach ($columns as $column) {
172
            list($cases[], $binding) = $this->buildCase($column, $words);
173
174
            $bindings = array_merge_recursive($bindings, $binding);
175
        }
176
177
        $select = implode(' + ', $cases);
178
179
        $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...
180
181
        $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...
182
183
        return $bindings['where'];
184
    }
185
186
    /**
187
     * Apply where clauses on the subquery.
188
     *
189
     * @param SearchableSubquery $subquery
190
     * @param ColumnCollection $columns
191
     * @param  array $words
192
     * @return void
193
     */
194
    protected function searchWhere(
195
        SearchableSubquery $subquery,
196
        ColumnCollection $columns,
197
        array $words,
198
        array $bindings
199
    ) {
200
        $operator = $this->getLikeOperator();
201
202
        $wheres = [];
203
204
        foreach ($columns as $column) {
205
            $wheres[] = implode(
206
                ' or ',
207
                array_fill(0, count($words), sprintf('%s %s ?', $column->getWrapped(), $operator))
208
            );
209
        }
210
211
        $where = implode(' or ', $wheres);
212
213
        $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...
214
215
        $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...
216
    }
217
218
    /**
219
     * Move where clauses to subquery to improve performance.
220
     *
221
     * @param SearchableSubquery $subquery
222
     * @return void
223
     */
224
    protected function wheresToSubquery(SearchableSubquery $subquery)
225
    {
226
        $bindingKey = 0;
227
228
        $typesToMove = [
229
            'basic', 'in', 'notin', 'between', 'null',
230
            'notnull', 'date', 'day', 'month', 'year',
231
        ];
232
233
        // Here we are going to move all the where clauses that we might apply
234
        // on the subquery in order to improve performance, since this way
235
        // we can drastically reduce number of joined rows on subquery.
236
        foreach ((array) $this->query->wheres as $key => $where) {
237
            $type = strtolower($where['type']);
238
239
            $bindingsCount = $this->countBindings($where, $type);
240
241
            if (in_array($type, $typesToMove) && $this->model->hasColumn($where['column'])) {
242
                unset($this->query->wheres[$key]);
243
244
                $where['column'] = $this->model->getTable() . '.' . $where['column'];
245
246
                $subquery->getQuery()->wheres[] = $where;
247
248
                $whereBindings = $this->query->getRawBindings()['where'];
249
250
                $bindings = array_splice($whereBindings, $bindingKey, $bindingsCount);
251
252
                $this->query->setBindings($whereBindings, 'where');
253
254
                $this->query->addBinding($bindings, 'select');
255
256
            // if where is not to be moved onto the subquery, let's increment
257
            // binding key appropriately, so we can reliably move binding
258
            // for the next where clauses in the loop that is running.
259
            } else {
260
                $bindingKey += $bindingsCount;
261
            }
262
        }
263
    }
264
265
    /**
266
     * Get number of bindings provided for a where clause.
267
     *
268
     * @param  array   $where
269
     * @param  string  $type
270
     * @return int
271
     */
272
    protected function countBindings(array $where, $type)
273
    {
274
        if ($this->isHasWhere($where, $type)) {
275
            return substr_count($where['column'] . $where['value'], '?');
276
        } elseif ($type === 'basic') {
277
            return (int) !$where['value'] instanceof Expression;
278
        } elseif (in_array($type, ['basic', 'date', 'year', 'month', 'day'])) {
279
            return (int) !$where['value'] instanceof Expression;
280
        } elseif (in_array($type, ['null', 'notnull'])) {
281
            return 0;
282
        } elseif ($type === 'between') {
283
            return 2;
284
        } elseif (in_array($type, ['in', 'notin'])) {
285
            return count($where['values']);
286
        } elseif ($type === 'raw') {
287
            return substr_count($where['sql'], '?');
288
        } elseif (in_array($type, ['nested', 'sub', 'exists', 'notexists', 'insub', 'notinsub'])) {
289
            return count($where['query']->getBindings());
290
        }
291
    }
292
293
    /**
294
     * Determine whether where clause is eloquent has subquery.
295
     *
296
     * @param  array  $where
297
     * @param  string $type
298
     * @return bool
299
     */
300
    protected function isHasWhere($where, $type)
301
    {
302
        return $type === 'basic'
303
                && $where['column'] instanceof Expression
304
                && $where['value'] instanceof Expression;
305
    }
306
307
    /**
308
     * Build case clause from all words for a single column.
309
     *
310
     * @param Column $column
311
     * @param  array  $words
312
     * @return array
313
     */
314
    protected function buildCase(Column $column, array $words)
315
    {
316
        // THIS IS BAD
317
        // @todo refactor
318
319
        $operator = $this->getLikeOperator();
320
321
        $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...
322
            return $this->caseBinding($word);
323
        }, $words);
324
325
        $case = $this->buildEqualsCase($column, $words);
326
327
        if (strpos(implode('', $words), '*') !== false) {
328
            $leftMatching = [];
329
330 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...
331
                if ($this->isLeftMatching($word)) {
332
                    $leftMatching[] = sprintf('%s %s ?', $column->getWrapped(), $operator);
333
                    $bindings['select'][] = $bindings['where'][$key] = $this->caseBinding($word) . '%';
334
                }
335
            }
336
337 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...
338
                $leftMatching = implode(' or ', $leftMatching);
339
                $score = 5 * $column->getWeight();
340
                $case .= " + case when {$leftMatching} then {$score} else 0 end";
341
            }
342
343
            $wildcards = [];
344
345 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...
346
                if ($this->isWildcard($word)) {
347
                    $wildcards[] = sprintf('%s %s ?', $column->getWrapped(), $operator);
348
                    $bindings['select'][] = $bindings['where'][$key] = '%' . $this->caseBinding($word) . '%';
349
                }
350
            }
351
352 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...
353
                $wildcards = implode(' or ', $wildcards);
354
                $score = 1 * $column->getWeight();
355
                $case .= " + case when {$wildcards} then {$score} else 0 end";
356
            }
357
        }
358
359
        return [$case, $bindings];
360
    }
361
362
    /**
363
     * Replace '?' with single character SQL wildcards.
364
     *
365
     * @param  string $word
366
     * @return string
367
     */
368
    protected function caseBinding($word)
369
    {
370
        $parser = static::$parser->make();
371
372
        return str_replace('?', '_', $parser->stripWildcards($word));
373
    }
374
375
    /**
376
     * Build basic search case for 'equals' comparison.
377
     *
378
     * @param Column $column
379
     * @param  array  $words
380
     * @return string
381
     */
382
    protected function buildEqualsCase(Column $column, array $words)
383
    {
384
        $equals = implode(' or ', array_fill(0, count($words), sprintf('%s = ?', $column->getWrapped())));
385
386
        $score = 15 * $column->getWeight();
387
388
        return "case when {$equals} then {$score} else 0 end";
389
    }
390
391
    /**
392
     * Determine whether word ends with wildcard.
393
     *
394
     * @param  string  $word
395
     * @return bool
396
     */
397
    protected function isLeftMatching($word)
398
    {
399
        return Str::endsWith($word, '*');
400
    }
401
402
    /**
403
     * Determine whether word starts and ends with wildcards.
404
     *
405
     * @param  string  $word
406
     * @return bool
407
     */
408
    protected function isWildcard($word)
409
    {
410
        return Str::endsWith($word, '*') && Str::startsWith($word, '*');
411
    }
412
413
    /**
414
     * Get driver-specific case insensitive like operator.
415
     *
416
     * @return string
417
     */
418
    public function getLikeOperator()
419
    {
420
        $grammar = $this->query->getGrammar();
421
422
        if ($grammar instanceof PostgresGrammar) {
423
            return 'ilike';
424
        }
425
426
        return 'like';
427
    }
428
429
    /**
430
     * Join related tables on the search subquery.
431
     *
432
     * @param  array $mappings
433
     * @param SearchableSubquery $subquery
434
     * @return ColumnCollection
435
     */
436
    protected function joinForSearch($mappings, $subquery)
437
    {
438
        $mappings = is_array($mappings) ? $mappings : (array) $mappings;
439
440
        $columns = new ColumnCollection;
441
442
        $grammar = $this->query->getGrammar();
443
444
        $joiner = static::$joinerFactory->make($subquery->getQuery(), $this->model);
445
446
        // Here we loop through the search mappings in order to join related tables
447
        // appropriately and build a searchable column collection, which we will
448
        // use to build select and where clauses with correct table prefixes.
449
        foreach ($mappings as $mapping => $weight) {
450
            if (strpos($mapping, '.') !== false) {
451
                list($relation, $column) = $this->model->parseMappedColumn($mapping);
452
453
                $related = $joiner->leftJoin($relation);
454
455
                $columns->add(
456
                    new Column($grammar, $related->getTable(), $column, $mapping, $weight)
457
                );
458
            } else {
459
                $columns->add(
460
                    new Column($grammar, $this->model->getTable(), $mapping, $mapping, $weight)
461
                );
462
            }
463
        }
464
465
        return $columns;
466
    }
467
468
    /**
469
     * Prefix selected columns with table name in order to avoid collisions.
470
     *
471
     * @return $this
472
     */
473
    public function prefixColumnsForJoin()
474
    {
475
        if (!$columns = $this->query->columns) {
476
            return $this->select($this->model->getTable() . '.*');
477
        }
478
479
        foreach ($columns as $key => $column) {
480
            if ($this->model->hasColumn($column)) {
481
                $columns[$key] = $this->model->getTable() . '.' . $column;
482
            }
483
        }
484
485
        $this->query->columns = $columns;
486
487
        return $this;
488
    }
489
490
    /**
491
     * Join related tables.
492
     *
493
     * @param  array|string $relations
494
     * @param  string $type
495
     * @return $this
496
     */
497
    public function joinRelations($relations, $type = 'inner')
498
    {
499
        if (is_null($this->joiner)) {
500
            $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...
501
        }
502
503
        if (!is_array($relations)) {
504
            list($relations, $type) = [func_get_args(), 'inner'];
505
        }
506
507
        foreach ($relations as $relation) {
508
            $this->joiner->join($relation, $type);
509
        }
510
511
        return $this;
512
    }
513
514
    /**
515
     * Left join related tables.
516
     *
517
     * @param  array|string $relations
518
     * @return $this
519
     */
520
    public function leftJoinRelations($relations)
521
    {
522
        $relations = is_array($relations) ? $relations : func_get_args();
523
524
        return $this->joinRelations($relations, 'left');
525
    }
526
527
    /**
528
     * Right join related tables.
529
     *
530
     * @param  array|string $relations
531
     * @return $this
532
     */
533
    public function rightJoinRelations($relations)
534
    {
535
        $relations = is_array($relations) ? $relations : func_get_args();
536
537
        return $this->joinRelations($relations, 'right');
538
    }
539
540
    /**
541
     * Set search query parser factory instance.
542
     *
543
     * @param ParserFactory $factory
544
     */
545
    public static function setParserFactory(ParserFactory $factory)
546
    {
547
        static::$parser = $factory;
548
    }
549
550
    /**
551
     * Set the relations joiner factory instance.
552
     *
553
     * @param JoinerFactory $factory
554
     */
555
    public static function setJoinerFactory(JoinerFactory $factory)
556
    {
557
        static::$joinerFactory = $factory;
558
    }
559
}
560