2
\$\begingroup\$

One of the tasks I find myself doing repeatedly is to filter the incoming inputs that will be stored in models. Examples of this include trimming, removing repeated or unnecessary pre/suffixes, commas or dashes in numbers, etc. This is similar to validation, but Laravel's built-in Validator does not appear to have this capability.

Mutators in Eloquent models does support this, but for large models creating one for each property will result in a lot of repeated code. I considered extending Validator to add this to the validation step, or adding a middleware to filter form requests, but it seems the easiest way would be to override the Model class' setAttribute function


use Illuminate\Database\Eloquent\Model;
use Log;

class BaseModel extends Model
{
    protected $filters = [];

    public function setAttribute($key, $value)
    {
        if ($this->hasFilter($key)) {
            $value = $this->executeFilter($key, $value);
        }

        return parent::setAttribute($key, $value);
    }

    protected function hasFilter($key)
    {
        return array_key_exists($key, $this->filters);
    }

    protected function getFilter($key)
    {
        return $this->filters[$key];
    }

    protected function executeFilter($key, $value)
    {
        $filter = $this->getFilter($key);
        $rules = is_string($filter) ? explode('|', $filter) : [$filter];

        foreach ($rules as $rule) {
            if (is_callable($rule)) {
                $value = call_user_func($rule, $value);
            } elseif (method_exists($this, 'filter'.ucfirst($rule))) {
                $ruleMethod = 'filter'.ucfirst($rule);
                $value = $this->$ruleMethod($value);
            } else {
                Log::warning("Unknown filter method $filter called on $key",
                    ['model' => '', 'value' => $value]);
            }
        }

        return $value;
    }
}

Example usage:

class Listing extends BaseModel
{
    protected $filters = [
        'name'           => 'trim',
        'description'    => 'trim',
        'price'          => 'price',
    ];

    protected function filterPrice($value)
    {
        return (float) ltrim(trim($value), '$');
    }
}

The $filters property contains the rules needed for each property. Each rule is separated by |, and the rule has to be a function that either exist globally (ie. built-in functions or helpers), or as a method on the model prefixed with the word 'filter'.

My questions are:

  1. Is there a better way to accomplish the same functionality (ie. running some functions on all inputs), at a middleware, controller or model level?
  2. This implementation works (I've written a few simple tests for them to verify this), but is this good design, and can it be improved? For instance, I'm curious if this can be rewritten into a trait, which I think is preferable - I don't think it is possible, but I might have missed something
\$\endgroup\$

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Browse other questions tagged or ask your own question.