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\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.