Learn more

Validation



The mako validator provides a simple and consistent way of validating user input.


Usage

Basics

First you'll need to define a set of rules that you want to validate your input against.

$rules = [
	'username' => ['required', 'min_length(4)', 'max_length(20)'],
	'password' => ['required'],
	'email'    => ['required', 'email'],
];

The rules defined above will make sure that the username, password and email fields are present and non-empty. That the username is between 4 and 20 characters long, and that the email field contains a valid email address.

Note that most of the included validation rules will skip validation if the field is empty. The exceptions are required, not_empty, one_time_token and token. You can however override this functionality by setting validateEmptyFields to true when creating a validator instance.

Next you'll need to create a validator object. The first parameter is the input data you want to validate and the second is the set of validation rules you just defined.

$postData = $this->request->getPost();

$validator = $this->validator->create($postData->all(), $rules);

Now all that is left is to check if the input data is valid using either of the following methods: validate, isValid or isInvalid.

The getValidatedInput method returns the validated input data and throws a ValidationException if any of the rules fail. You can retrieve the validation errors using the ValidationException::getErrors() method.

$validatedInput = $validator->getValidatedInput();

Note that only validated input (input fields with at least one validation rule) is returned. If you have a field that doesn't require any special validation then you can use the optional rule to ensure that it gets returned along with the validated values.

The isValid method returns true if the input is valid and false if not, while the isInvalid method returns false when the input validates and true when not.

if ($validator->isValid()) {
	// Do something
}

Retrieving the error messages is done using the getErrors method

$errors = $validator->getErrors();

You can also assign it to a variable by passing it to either of the isValid or isInvalid methods.

if ($validator->isValid($errors)) {
	// Do something
}

Nested arrays

The validator also supports nested arrays. You can assign validation rule sets to nested fields using the "dot notation" syntax.

$rules = [
	'user.email' => ['required', 'email'],
];

You can also apply rule sets to multiple keys using wildcards.

$rules = [
	'users.*.email' => ['email'],
];

Note that wildcard rules will only be added if the input field(s) actually exists.

Conditional rules sets

You can add rule sets to your validator instance if a certain condition is met using either the addRules or addRulesIf methods.

$validator->addRulesIf('state', ['required', 'valid_us_state'], function () use ($postData) {
	return $postData->get('country') === 'United States of America';
});

You can also pass a boolean value instead of a closure. Rules added using either of the methods will be merged with any pre-existing rules assigned to the field.

Rules with dynamic arguments

Mako comes with a handy helper function called mako\f that makes it easier to build rule sets that have rules with dynamic arguments. The first parameter of the function is the name of the validation rule and any subsequent arguments are treated as rule arguments.

use function mako\f;

$rules = [
	'category' => ['required', f('in', $this->getCategoryIds())],
];

// The example above produces the same result as the following code

$rules = [
	'category' => ['required', 'in([' . implode(',', $this->getCategoryIds()) . '])'],
];

Validation rules

The following validation rules are included with Mako:

Base rules

Name Description
after Checks that the field value is a valid date after the provided date (after("Y-m-d","2012-09-25")).
alpha Checks that the field value only contains valid alpha characters.
alpha_dash Checks that the field value only contains valid alphanumeric, dash and underscore characters.
alpha_dash_unicode Checks that the field value only contains valid alphanumeric unicode, dash and underscore characters.
alpha_unicode Checks that the field value only contains valid alpha unicode characters.
alphanumeric Checks that the field value only contains valid alphanumeric characters.
alphanumeric_unicode Checks that the field value only contains valid alphanumeric unicode characters.
array Checks that the field value is an array.
before Checks that the field value is a valid date before the provided date (before("Y-m-d","2012-09-25")).
between Checks that the field value is between x and y (between(5,10)).
boolean Checks that the field contains either true or false.
boolean:false
boolean:true Checks that the field contains either true.
date Checks that the field value is a valid date (date("Y-m-d")).
different Checks that the field value is different from the value of another field (different("old_password")).
email Checks that the field value is a valid email address.
email_domain Checks that the field value contains a valid MX record.
enum Checks that the field contains a valid enum value.
exact_length Checks that the field value is of the right length (exact_length(20)).
greater_than Checks that the field value is greater than x (greater_than(5)).
greater_than_or_equal_to Checks that the field value is greater than or equal to x (greater_than_or_equal_to(5)).
hex Checks that the field value is valid HEX.
in Checks that the field value contains one of the given values (in(["foo","bar","baz"])).
ip Checks that the field value is an IP address (ip, ip("v4") or ip("v6")).
json Checks that the field value contains valid JSON.
less_than Checks that the field value is less than x (less_than(5)).
less_than_or_equal_to Checks that the field value is less than or equal to x (less_than_or_equal_to(5)).
match Checks that the field value matches the value of another field (match("password_confirmation")).
max_length Checks that the field value is short enough (max_length(20)).
min_length Checks that the field value is long enough (min_length(10)).
not_empty Checks that the field isn't empty.
not_in Checks that the field value does not contain one of the given values (not_in(["foo","bar","baz"])).
number Checks that the field contains an integer or float.
number:float Checks that the field value is a float.
number:int Checks that the field value is a integer.
number:natural Checks that the field value is a natural.
number:natural_non_zero Checks that the field value is a natural non zero.
numeric Checks that the field contains a numeric value.
numeric:float Checks that the field value is a float.
numeric:int Checks that the field value is a integer.
numeric:natural Checks that the field value is a natural.
numeric:natural_non_zero Checks that the field value is a natural non zero.
optional This is a special validation rule that never fails.
regex Checks that the field value matches a regex pattern (regex("/[a-z]+/i")).
required Checks that the field exists and isn't empty.
string Checks that the field contains a string.
time_zone Checks that the field value contains a valid PHP time zone.
url Checks that the field value is a valid URL.
uuid Checks that the field value matches a valid uuid.

Database rules

Name Description
exists Checks that the field value exist in the database (exists("users","email")).
unique Checks that the field value doesn't exist in the database (unique("users","email")).

File rules

Basic rules
Name Description
hash Checks that the file produces the expected hash (hash("<expected_hash>") or hash("<expected_hash>", "<algorithm>")).
hmac Checks that the file produces the expected hmac (hmac("<expected_hmac>", "key") or hmac("<expected_hmac>", "key", "<algorithm>")).
is_uploaded Checks that the file is a successful upload.
max_file_size Checks that the file is smaller or equal in size to the provided limit (max_file_size("1MiB") The accepted size units are KiB, MiB, GiB, TiB, PiB, EiB, ZiB and YiB).
max_filename_length Checks that the filename does not exeed the maximum allowed filename length (max_filename_length(10)).
mime_type Checks that the file is of the specified mime type(s) (mime_type("image/png") or mime_type(["image/png", "image/jpeg"])).

The default hash algorithm for the hash and hmac rules is sha256. Any algorithm supported by hash_file can be used.

The max_file_size and max_filename_length rules expect SplFileInfo, FileInfo or UploadedFile objects.

The hash, hmac, and mime type rules expect FileInfo or UploadedFile objects.

The is_uploaded rule expects UploadedFile objects.

Image rules
Name Description
aspect_ratio Checks that the image matches the expected aspect ratio (aspect_ratio(4, 3)).
exact_dimensions Checks that the image matches the expected dimensions (exact_dimensions(800, 600)).
max_dimensions Check that the image is smaller than or equal to the max dimensions (max_dimensions(800, 600)).
min_dimensions Check that the image is larger than or equal to the min dimensions (min_dimensions(800, 600)).

The image validation rules expect SplFileInfo, FileInfo or UploadedFile objects.

The rules use the getimagesize function to get the image size. You should make sure that the file you're validating is an image using the mime type rule before using any of the image specific rules.

Session rules

Name Description
one_time_token Checks that the field value matches a valid session one time token.
token Checks that the field value matches a valid session token.

Custom messages

All error messages are defined in the app/i18n/*/strings/validate.php language file.

Adding custom field specific error messages can be done using the overrides.messages array:

'overrides' =>
[
	'messages' =>
	[
		'username' =>
		[
			'required' => 'You need a username!',
		],
	],
],

You can also add custom field name translations using the overrides.fieldnames array:

'overrides' =>
[
	'fieldnames' =>
	[
		'email' => 'email address',
	],
],

Custom rules

You can, of course, create your own custom validator rules. All rules must implement the RuleInterface interface.

<?php

use mako\validator\rules\RuleInterface;

/**
 * Is foo validation rule.
 */
class IsFooRule implements RuleInterface
{
	/**
	 * {@inheritdoc}
	 */
	public function validateWhenEmpty(): bool
	{
		return false;
	}

	/**
	 * {@inheritdoc}
	 */
	public function validate($value, string $field, array $input): bool
	{
		return mb_strtolower($value) === 'foo';
	}

	/**
	 * {@inheritdoc}
	 */
	public function getErrorMessage(string $field): string
	{
		return sprintf('The value of the %1$s field must be "foo".', $field);
	}
}

If you want it to return error messages from a language file then you'll have to implement the I18nAwareInterface interface.

Note that there is a reusable trait (I18nAwareTrait) that implements the interface so that you don't have to write the code yourself.

You can register your custom rules with the validation factory, thus making it available to all future validator instances.

$this->validator->extend('is_foo', IsFooRule::class);

You can also register it into an existing validator instance.

$validator->extend('is_foo', IsFooRule::class);

Or you can just use the class name when setting up your input validation.

$rules = [
	'foo' => ['required', IsFooRule::class],
];

Prefix the rule name with your package name and two colons (::) if your validator is a part of a package to avoid naming collisions.


Input validation in controllers

Mako includes a middleware and a trait that will greatly reduce the ammount of boilerplate code that you need to write to validate user input in controllers.

The middleware will catch ValidationException exceptions and convert them to a Bad Request response where the response body will be delivered as HTML, JSON or XML based on the request context.

The trait includes two helpful methods, getValidatedInput and getValidatedFiles, that will allow you to validate user input as well as uploaded files.

<?php

namespace app\http\controllers;

use mako\http\routing\attributes\Middleware;
use mako\http\routing\Controller;
use mako\validator\input\http\routing\middleware\InputValidation;
use mako\validator\input\http\routing\traits\InputValidationTrait;

class Article extends Controller
{
	use InputValidationTrait;

	#[Middleware(InputValidation::class)]
	public function store(): void
	{
		$input = $this->getValidatedInput([
			'title' => ['required', 'min_length(1)', 'max_length(255)'],
			'body'  => ['required', 'min_length(1)', 'max_length(64000)'],
		]);

		// Do something with the input ...
	}
}

Note that the two methods only return validated input (input fields with at least one validation rule). If you have a field that doesn't require any special validation then you can use the optional rule to ensure that it gets returned along with the validated values.