Skip to content

Instantly share code, notes, and snippets.

@ryzr
Last active February 12, 2019 02:39
Show Gist options
  • Save ryzr/6370a3c510204f749db088457ba3cc5f to your computer and use it in GitHub Desktop.
Save ryzr/6370a3c510204f749db088457ba3cc5f to your computer and use it in GitHub Desktop.
Laravel Scout Filters with magic methods
<?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();
}
}
<?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