Skip to content

Instantly share code, notes, and snippets.

@daylerees
Created January 21, 2014 09:51
Show Gist options
  • Save daylerees/8537274 to your computer and use it in GitHub Desktop.
Save daylerees/8537274 to your computer and use it in GitHub Desktop.

My Validation Base Class

I was asked how I deal with validation / create and update validation rulesets. Well here is one method I have used. Don't be afraid to build on top of what the framework has already given you. In my projects I use a base class for almost anything. You never know when you want your classes to inherit some common functionality. My BaseValidator actually has some pretty useful methods and properties in it.

<?php

namespace FooProject\Internal\Validators;

use FooProject\Internal\Sanitizers\BaseSanitizer;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Validator;

abstract class BaseValidator
{
    /**
     * A collection of validation errors.
     *
     * @var Illuminate\Support\Collection
     */
    protected $errors;

    /**
     * An array of sanitizers to be executed
     * before the validation process.
     *
     * @var array
     */
    protected $sanitizers = [];

    /**
     * Validation rules for this Validator.
     *
     * @var array
     */
    protected $rules = [];

    /**
     * An array of custom validation messages.
     *
     * @var array
     */
    protected $messages = [];

    /**
     * Set the intial errors collection.
     */
    public function __construct()
    {
        $this->errors = new Collection;
    }

    /**
     * Validate the provided data using the
     * internal rules array.
     *
     * @param  mixed $data
     * @return bool
     */
    public function validate($data, $ruleset = 'create')
    {
        // We allow collections, so transform to array.
        if ($data instanceof Collection) {
            $data = $data->toArray();
        }

        // Execute sanitizers over the data before validation.
        $this->runSanitizers($data);

        // Load the correct ruleset.
        $rules = $this->rules[$ruleset];

        // Create the validator instance and validate.
        $validator = Validator::make($data, $rules, $this->messages);
        if (!$result = $validator->passes()) {
            $this->errors = $validator->messages();
        }

        // Return the validation result.
        return $result;
    }

    /**
     * Attach a sanitizer to this validation instance
     * to be executed before the validation process.
     *
     * @param  BaseSanitizer $sanitizer
     * @return FooProject\Internal\Validators\BaseValidator
     */
    public function attachSanitizer(BaseSanitizer $sanitizer)
    {
        $this->sanitizers[] = $sanitizer;
        return $this;
    }

    /**
     * Execute all of our registered sanitizers
     * on the validation data.
     *
     * @param  array $data
     * @return void
     */
    protected function runSanitizers($data)
    {
        foreach ($this->sanitizers as $sanitizer) {
            $sanitizer->sanitize($data);
        }
    }

    /**
     * Return the error collection after a failed
     * validation attempt.
     *
     * @return Illuminate\Support\Collection
     */
    public function errors()
    {
        return $this->errors;
    }
}

Bit confusing? Well here, take a look at an example validation class.

<?php

namespace FooProject\Internal\Validators;

use FooProject\Internal\Sanitizers\UsersSanitizer;

class UsersValidator extends BaseValidator
{
    /**
     * Validation rules for this Validator.
     *
     * @var array
     */
    protected $rules = [

        'create' => [
            'username' => ['required', 'min:3', 'unique:users'],
            'password' => ['required'],
            'email'    => ['required', 'email', 'unique:users']
        ],

        'update' => [
            'username' => ['min:3', 'unique:users'],
            'email'    => ['email', 'unique:users'],
            'gender'   => ['in:MALE,FEMALE']
        ]

    ];

    /**
     * Attach a default sanitizer to this
     * validator instance.
     */
    public function __construct()
    {
        parent::__construct();

        $this->attachSanitizer(new UsersSanitizer);
    }
}

The protected $rules property contains a number of validation 'sets' that contain the traditional arrays of rules. Here we have a create and update set, where the update set has non required fields to allow for them to be missing in the 'PATCH' update format that API's use.

In the construct I add a Sanitizer. A sanitizer is a class I use to morph the input before validation, for example, a field may fail validation because it isn't uppercase, but we could easily do that ourselves and save the user an annoying error loop. So we use the Sanitizer to strtoupper() the field before we validate.

Here's an example of the validator usage.

$data = Input::get();

$validator = new UserValidator;

if ($validator->validate($data, 'update')) {
  // validation passed
}

// validation failed
$errors = $validator->errors();

Remember, this is just my way of doing this, and it's not going to be perfect for everyone. It just happens to work for me.

Invent your own systems! Enjoy!

@mikelbring
Copy link

This is pretty similar to what I do. On the update, you might need to add the ID to the unique:users for email and username. How would you go about doing that in your example? You can't just add it to the array since its dynamic. I did it in the constructor of the extending class by injecting the model I am validating against, but don't really like that way of doing it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment