Reusable Validation Rules with Laravel Form Requests

Currently I'm working on a project with a lot of forms that have repeated form fields. For example, pretty much all entities in the system can have attachments, so a lot of forms contain the same attachment fields. I could repeat the same validation rules in all form request classes, but this quickly breaks down. What if the rules for attachments change? Let's see how we can handle this more efficiently.

Using Helper Methods

The first approach that comes to mind is using protected helper methods in our base class. Laravel comes with an abstract Request class out of the box that all form requests extend, so this would be a great place to put that.

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

abstract class Request extends FormRequest
{
    protected function attachmentRules()
    {
        return [
            'attachment.*.file' => 'required|file|min:1',
            'attachment.*.name' => 'required|string',
        ];
    }
}

Then when we have a form that has attachment fields, we can simply the helper in our rules method:

<?php

namespace App\Http\Requests;

class SaveInvoiceRequest extends Request
{
    // ...
    public function rules()
    {
        return array_merge([
            // Invoice rules..
        ], $this->attachmentRules());
    }
}

While this approach works fine, there are two things I don't like about it.

  1. Our Request class becomes littered with all sorts of methods that return validation rules.
  2. We have to use array_merge in our rules method every time we want to reuse the validation rules, which isn't very clean.

Let's see how we can fix these two issues!

Extracting a Trait

The solution to the first issue is to extract our attachmentRules method to a trait. This gives us a dedicated place to manage any validation rules related to the attachment form partial.

<?php

namespace App\Http\Requests;

trait HasAttachmentFields
{
    protected function attachmentRules()
    {
        return [
            'attachment.*.file' => 'required|file|min:1',
            'attachment.*.name' => 'required|string',
        ];
    }
}

Now we can make use of this trait in our form request classes by useing it at the top.

<?php

namespace App\Http\Requests;

class SaveInvoiceRequest extends Request
{
    use HasAttachmentFields;

    // ...

    public function rules()
    {
        return array_merge([
            // Invoice rules...
        ], $this->attachmentRules());
    }
}

The second issue is a little harder to tackle. How can we extend our rules method without using array_merge all the time?

Automatically Adding the Validation Rules

The solution I came up with is to automatically add the validation rules when setting up our validator. To do this we can use a hook that Laravel provides to form request classes called withValidator. This method gets called by Laravel after constructing the Validator instance, which is passed as an argument. This allows us to do any further processing on the Validator instance, like adding more rules.

For this to work efficiently we are going to use a convention, namely Has*Fields, where the * is the name of form partial (in this case 'Attachment'). The rules method name then is the camelCase version of this name, followed by Rules (in this case 'attachmentRules'). This means that if we have another trait named HasUserFields, the rules method in this trait should be named userRules.

Let's look at the implementation:

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Validator;

abstract class Request extends FormRequest
{
    public function withValidator(Validator $validator)
    {
        if ($rules = $this->getTraitRules()) {
            $validator->addRules($rules);
        }
    }

    protected function getTraitRules()
    {
        return array_reduce(class_uses(static::class), function ($rules, $trait) {
            $rulesMethod = $this->makeRulesMethodName($trait);

            if ($rulesMethod && method_exists($this, $rulesMethod)) {
                // If the trait name matches our convention and a <name>Rules
                // method exists, merge the result into our rules array.
                $rules = array_merge($rules, $this->{$rulesMethod}());
            }

            return $rules;
        }, []);
    }

    protected function makeRulesMethodName($trait)
    {
        preg_match('/^Has([A-Za-z]+)Fields$/', class_basename($trait), $matches);

        // If the trait matches our `Has<name>Fields` convention, get the 
        // <name> part, camelCase it and attach 'Rules' at the end.
        return isset($matches[1]) ? camel_case($matches[1]).'Rules' : null;
    }
}

I've commented the code to make it easier to follow. Basically what happens is that we loop through all traits used by the class and if it matches our convention of Has<name>Fields, we call the <name>Rules method and merge that into the result. If the result is not empty, we call the addRules method on the Validator to add the trait rules.

Now we can get rid of our array_merge call!

<?php

namespace App\Http\Requests;

class SaveInvoiceRequest extends Request
{
    use HasAttachmentFields;

    // ...

    public function rules()
    {
        return [
            // Invoice rules...
        ];
    }
}

Conclusion

Using this system, we can now easily add reusable validation rules to our form request classes! All we need to do is create a new trait with a rules method that matches our convention, use it in the form request and we're good to go! The final code of the Request class is available as a GitHub Gist. Let me know what you think of this approach!