Laravel model filter

cwfan發表於2021-03-17

namespace App\Library\Model;


use Exception;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;


/**
 * Trait WithRequestFilter
 * @package App\Library\Model
 */
trait WithRequestFilter
{
    public $aliasEnabled = false;
    protected $expression = [
        '$like',
        '$is',
        '$between',
        '$in',
        '$gt',
        '$gte',
        '$lt',
        '$lte',
        '$eq',
        '$not',
    ];
    protected $operator = [
        '$or',
        '$and',
//        '$having',
    ];
    protected $fieldAlias = [
        'permission' => 'permission_id',
        'role' => 'role_id',
        'user' => 'user_id'
    ];
    /**
     * @var string[]
     */
    protected $availableFields = ['*'];

    public function setFieldNameAlias($key, $value)
    {
        $this->fieldAlias[$key] = $value;
    }

    /**
     * @param  array  $filter
     * @return Builder
     * @throws Exception
     */
    protected function buildFilter(array $filter): Builder
    {
        if ($this instanceof Model) {
            return $this->setFilter(static::query(), $filter);
        } else {
            throw new Exception('Only available with Eloquent Model');
        }
    }

    /**
     * @param  Builder  $query
     * @param  array  $filter
     * @return Builder
     */
    protected function setFilter(Builder $query, array $filter): Builder
    {
        foreach ($filter as $key => $value) {
            if (in_array($key, $this->operator)) {
                $operator = $key;
                $this->buildOperator($query, $value, $operator);
            } else {
                $field = $key;
                $this->buildExpression($query, $field, $value);
            }
        }
        return $query;
    }

    /**
     * @param  Builder  $query
     * @param  mixed  $filter
     * @param  string  $operator
     * @throws InvalidParameterException
     */
    private function buildOperator(Builder $query, $filter, string $operator = '$and')
    {
        if ($operator === '$or') {
            $query->orWhere(function ($query) use ($filter) {
                $this->buildOperator($query, $filter);
            });
        } else {
            foreach ($filter as $key => $value) {
                if (in_array($key, $this->operator)) {
                    $operator = $key;
                    $this->buildOperator($query, $value, $operator);
                } else {
                    $field = $key;
                    $this->buildExpression($query, $field, $value);
                }
            }
        }
    }

    /**
     * @param  Builder  $query
     * @param  string  $fieldName
     * @param  mixed  $fieldValue
     * @param  bool  $not
     * @throws InvalidParameterException
     */
    private function buildExpression(Builder $query, string $fieldName, $fieldValue, $not = false)
    {
        if (is_array($fieldValue)) {
            foreach ($fieldValue as $expression => $value) {
                if ($expression === '$not') {
                    $this->buildExpression($query, $fieldName, $value, true);
                } else {
                    $this->buildCondition($query, $fieldName, $value, $expression, $not);
                }
            }
        } else {
            $this->buildCondition($query, $fieldName, $fieldValue, '$eq', $not);
        }
    }

    /**
     * @param  Builder  $query
     * @param  string  $field
     * @param  mixed  $value
     * @param  string  $expression
     * @param  bool  $negative  if it is negative,  the relation should be with `not` keyword
     * @throws InvalidParameterException
     */
    private function buildCondition(Builder $query, string $field, $value, string $expression, bool $negative = false)
    {
        $fieldName = $this->getFieldName($field);
        if (!$this->fieldAvailable($fieldName)) {
            throw new InvalidParameterException("Field name $field not available.");
        }
        switch ($expression) {
            case '$like':
                if ($negative) {
                    $query->where($fieldName, 'not like', "%$value%");
                } else {
                    $query->where($fieldName, 'like', "%$value%");
                }
                break;
            case '$is':
                if (is_null($value)) {
                    $negative ? $query->whereNotNull($fieldName) : $query->whereNull($fieldName);
                } else {
                    throw new InvalidParameterException('The value of key "$is" must be null');
                }
                break;
            case '$between':
                if (is_array($value) && count($value) === 2) {
                    $negative ? $query->whereNotBetween($fieldName, $value) : $query->whereBetween($fieldName, $value);
                } else {
                    throw new InvalidParameterException('The value of key "$between" must be an array of length 2');
                }
                break;
            case '$in':
                $values = is_array($value) ? $value : [$value];
                $negative ? $query->whereNotIn($fieldName, $value) : $query->whereIn($fieldName, $values);
                break;
            case '$gt':
                $negative ? $query->where($fieldName, '<=', $value) : $query->where($fieldName, '>', $value);
                break;
            case '$gte':
                $negative ? $query->where($fieldName, '<', $value) : $query->where($fieldName, '>=', $value);
                break;
            case '$lt':
                $negative ? $query->where($fieldName, '>=', $value) : $query->where($fieldName, '<', $value);
                break;
            case '$lte':
                $negative ? $query->where($fieldName, '>', $value) : $query->where($fieldName, '<=', $value);
                break;
            case '$eq':
                $negative ? $query->where($fieldName, '<>', $value) : $query->where($fieldName, $value);
                break;
            default:
                if (method_exists($this, 'buildHandler')) {
                    call_user_func([$this, 'buildHandler'], $query, $fieldName, $value, $expression, $negative);
                } elseif (is_array($value)) {
                    throw new InvalidParameterException("the value of field $fieldName can't be an array");
                } elseif (is_null($value)) {
                    $negative ? $query->whereNull($fieldName) : $query->whereNotNull($fieldName);
                } else {
                    $negative ? $query->where($fieldName, '<>', $value) : $query->where($fieldName, $value);
                }
        }
    }

    /**
     * @param  string  $field
     * @return string
     */
    protected function getFieldName(string $field): string
    {
        if ($this->aliasEnabled) {
            return $this->fieldAlias[$field] ?? $field;
        } else {
            return $field;
        }
    }

    protected function fieldAvailable($field): bool
    {
        if (empty($this->availableFields)) {
            return false;
        }
        if (in_array($field, $this->availableFields)) {
            return true;
        }
        if (in_array('*', $this->availableFields)) {
            return true;
        }
        return false;
    }

}
本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章