Skip to content

Instantly share code, notes, and snippets.

@augustohp
Last active March 24, 2022 17:24
Show Gist options
  • Save augustohp/d9275aca4cf6d833888e to your computer and use it in GitHub Desktop.
Save augustohp/d9275aca4cf6d833888e to your computer and use it in GitHub Desktop.
How to create custom Respect\Validation rules.

Custom rules on Respect/Validation

You may not know that the most awesome validation engine for PHP out there is Respect/Validation. If you do, this is tailored for you!

All rules on Respect/Validation are meant to be used together, composing a more complex validation rule that is closer to the domain of your application than the existing ones, let's try an example:

<?php

use Respect\Validation\Validator as v;

// Value Object of some imaginary application
$user = new StdClass;
$user->name = 'augustohp';
$user->id = 1;
$user->twitter = 'augustohp';


$usernameRule = v::stringType()->alnum()->between(3,35);
$userRule = v::attribute('name', $usernameRule)
             ->attribute('id', v::intType()->positive())
             ->attribute('twitter', $usernameRule);

$userRule->validate($user); // `true`

If you don't understand anything above, go to Respect\Validation documentation. If you never seen the documentation, take an overview of the existing rules there, you are definitely want to build yours upon them.

The "important" thing I want you to notice is the $usernameRule and the way we re-use it on two different occasions. Every chain of rules return a new validator which can be used to compose others. Having a composite of many rules is useful when a validation fails: you have a very specific error message on why it failed.

So, chaining existing rules together is easy. After you get used to it you will start composing your rules to validate stuff more coherent with you business, and you will want these rules everywhere!

Creating new rules

Every Respect\Validation validation rule follows this criteria:

  1. Implements the Respect\Validation\Validatable interface.
  2. Belongs to a <vendor>\Rules namespace.
  3. Has a counterpart Exception class:
    1. Inside <vendor>\Exceptions namespace.
    2. Named <Rule Name>Exception.
    3. Implements Respect\Validation\Exceptions\ExceptionInterface.

Every rule on Respect\Validation can be instantiated alone as a regular object, so keep this in mind while creating yours. You basically have two ways of creating you rule:

  1. Implementing interfaces and having full control of what you want.
  2. Extending existing rules made to compose others.

Generally you probably want to extend an existing rule which will enable you to compose existing rules much like you are used to. If you want to do something fancy and have full control of it, you can (and should probably share with us also).

We will cover both methods, in the future. Let's do the easy one first.

Extending an existing rule

As we said before, rules were meant to be composed and used together. Rules are also stand alone instances. It is pretty easy to think everything is static due to the static builder providing the fluent interface (v::int()->positive()), that first call is static just for the sake of usage, what it is really doing is:

<?php

use Respect\Validation\Rules;

$rule = new Rules\AllOf(
    new Rules\Int(),
    new Rules\Positive
);

Everything passed to AllOf rule as an argument to its constructor is added to a rule list which must pass validation. As you are probably used to v:: all the things, you probably want to extend that class and create you own:

<?php
    
namespace Acme\Validation\Rules;

use Respect\Validation\Rules;

class Twitter extends Rules\AllOf
{
	public function __construct()
	{
		parent::__construct(
            new Rules\StringType(),
            new Rules\NoWithespace(),
			new Rules\Alnum('_'),
			new Rules\Between(1, 15, $inclusive = true)
		);
	}
}

That should provide you the means of using v::twitter() in next minutes, we still need a fancy and great message when things go bad, so:

<?php

namespace Acme\Validation\Rules;

use Respect\Validation\Exceptions;

class TwitterException extends Exceptions\AllOfException
{
	/**
	 * We will use the same messages templates as
	 * the "AllOf" exception, so nothing is needed
	 * here.
	 * 
	 * Just. The silence.
	 * http://tardis.wikia.com/wiki/The_Silence
	 */
}

You have everything to go, you just need to tell Respect\Validation of your new fancy namespace with rules:

<?php

use Respect\Validation\Validator as v;

v::with('Acme\\Validation\\Rules');

After that, you are ready to v::twitter() all the things now. And of course, still use old rules together with new ones and so on.

Hey, take a look at existing rules. Really. You may find interesting stuff like OneOf, NoneOf, In, etc.

FAQ

I did everything, Respect/Validation says it cannot find my custom rule.

Make sure you rule follows all required criteria:

  • It belongs on a namespace <My Name>\Rules\<Rule Name> (the rule must belong to a Rules namespace).
  • It has an exception following the criteria.

Also, be sure that Respect\Validation\Validator::with() method is called before you try using the rule.

Why every rule should have its own Exception?

It is way too little effort to create a new class and be able to catch specific exceptions of specific rules anywhere. If anywhere in the chain of rules you create you try to cheat this, probability has you will regret that soon enough you want to catch that bastard coming from below into the stack.

@phil123456
Copy link

how do you validate values with complex regex patterns ? like passwords or something

@imageslr
Copy link

I found that I can set template just in MyRule, and it seems there is no need for an extra MyRuleException. How can I customize my own rule without creating a corresponding Exception?

@augustohp
Copy link
Author

I found that I can set template just in MyRule, and it seems there is no need for an extra MyRuleException. How can I customize my own rule without creating a corresponding Exception?

You need because the Rule Factory will try to find it if a validation fails and assert method is used.

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