Last active
February 12, 2019 02:39
-
-
Save ryzr/6370a3c510204f749db088457ba3cc5f to your computer and use it in GitHub Desktop.
Laravel Scout Filters with magic methods
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App; | |
use Laravel\Scoute\Builder; | |
use Illuminate\Http\Request; | |
abstract class QueryFilters | |
{ | |
/** | |
* The request object. | |
* | |
* @var Request | |
*/ | |
protected $request; | |
/** | |
* The builder instance. | |
* | |
* @var Builder | |
*/ | |
protected $builder; | |
/** | |
* Create a new QueryFilters instance. | |
* | |
* @param Request $request | |
*/ | |
public function __construct(Request $request) | |
{ | |
$this->request = $request; | |
} | |
/** | |
* Apply the filters to the builder. | |
* | |
* @param Builder $builder | |
* @return Builder | |
*/ | |
public function apply(Builder $builder) | |
{ | |
$this->builder = $builder; | |
foreach ($this->filters() as $name => $value) { | |
if (! $this->filterExists($name)) { | |
continue; | |
} | |
if (strlen($value)) { | |
$this->$name($value); | |
} else { | |
$this->$name(); | |
} | |
} | |
return $this->builder; | |
} | |
/** | |
* Determine whether a filter exists for the parameter | |
* | |
* @param $name | |
* | |
* @return bool | |
*/ | |
protected function filterExists($name): bool | |
{ | |
return method_exists($this, $name); | |
} | |
/** | |
* Get all request filters data. | |
* | |
* @return array | |
*/ | |
public function filters() | |
{ | |
return $this->request->all(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App; | |
use Laravel\Scoute\Builder; | |
class TrackFilters extends QueryFilters | |
{ | |
/** | |
* @var array List of filters accepting min/max constraints | |
*/ | |
protected $numericRangeFilters = [ | |
'popularity', | |
'acousticness', | |
'danceability', | |
'energy', | |
'liveness', | |
'loudness', | |
'speechiness', | |
'valence', | |
'tempo', | |
]; | |
/** | |
* Converts "filter_min" to "filter" | |
* | |
* @param string $name | |
* | |
* @return string | |
*/ | |
protected function minFilterName(string $name): string | |
{ | |
return str_before($name, '_min'); | |
} | |
/** | |
* Converts "filter_max" to "filter" | |
* | |
* @param string $name | |
* | |
* @return string | |
*/ | |
protected function maxFilterName(string $name): string | |
{ | |
return str_before($name, '_max'); | |
} | |
/** | |
* Checks whether the filter is defined as a "numeric range" filter. | |
* | |
* @param $filterName | |
* | |
* @return bool | |
*/ | |
protected function rangeFilterExists($filterName) | |
{ | |
return in_array($filterName, $this->numericRangeFilters); | |
} | |
/** | |
* Extends default behaviour to check our dynamic numericRangeFilters | |
* | |
* @param string $name | |
* | |
* @return string | |
*/ | |
protected function filterExists($name) | |
{ | |
return parent::filterExists($name) || | |
$this->rangeFilterExists($this->minFilterName($name)) || | |
$this->rangeFilterExists($this->maxFilterName($name)); | |
} | |
/** | |
* Applies a greater-than/less-than constraint to our Scout query; | |
* If a constraint has already been applied, we'll replace it with | |
* a "whereBetween" query so that we can constrain both the min, | |
* and the max. | |
* | |
* @param $filterName | |
* @param $operator | |
* @param $value | |
* | |
* @return mixed | |
*/ | |
protected function applyRangeFilter($filterName, $operator, $value) | |
{ | |
if (isset($this->builder->wheres[$filterName])) { | |
$range = []; | |
$previousValueNumeric = preg_replace('/^\D+/', '', $this->builder->wheres[$filterName]); | |
if(starts_with($this->builder->wheres[$filterName], '>') && starts_with($operator, '<')) { | |
$range[0] = $previousValueNumeric; | |
$range[1] = $value; | |
} else if(starts_with($this->builder->wheres[$filterName], '<') && starts_with($operator, '>')) { | |
$range[0] = $value; | |
$range[1] = $previousValueNumeric; | |
} | |
if(count($range) === 2) { | |
return $this->builder->whereBetween($filterName, $range); | |
} | |
} | |
return $this->builder->where($filterName, $operator, $value); | |
} | |
/** | |
* Catch all method to dynamically apply our "numeric range" filters | |
* | |
* @param $name | |
* @param $arguments | |
*/ | |
public function __call($name, $arguments) | |
{ | |
if ($this->rangeFilterExists($filterName = $this->minFilterName($name))) { | |
$this->applyRangeFilter($filterName, '>=', floatval($arguments[0])); | |
} | |
if ($this->rangeFilterExists($filterName = $this->maxFilterName($name))) { | |
$this->applyRangeFilter($filterName, '<=', floatval($arguments[0])); | |
} | |
throw new BadMethodCallException(sprintf( | |
'Call to undefined method %s::%s()', static::class, $name | |
)); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment