inpsyde / validator
Inpsyde Validator library.
Requires
- php: >=7.2
Requires (Dev)
- brain/monkey: ^2.6.1
- phpunit/phpunit: ~8.0
README
This package provides a collection of validators for WordPress.
Contents
- Installation
- What it is and how it works
- Simple Validators
- Secondary Validators
Negate
exampleBulk
examplePool
example
- Compound validators
Multi
exampleMultiOr
example
- Error codes and input data
- Validators factory
- Error messages
- Error templates
- Code-specific templates
- Error-specific templates
DataValidator
- Custom validators
- Other notes
Installation
Best served by Composer from Packagist. Package name is inpsyde/validator
.
What it is and how it works
The package provides validator objects that can be used to verify that some given data fulfill specific requirements.
Most important method for each validator is is_valid()
that receives some data and returns true
or false
, depending
on the provided data meets validator requirements.
We can distinguish among three types of validators:
- "simple"
- "secondary"
- "compound"
Simple validators are used to verify single values, according to some specifications.
Secondary validators are created taking a simple validator and modifying its behavior. Can be seen as "decorators" for validators.
Compound validators are made by combining togheter more validators.
Simple validators
This is a summary of simple validators provided as of now with the package:
All validators are defined in Inpsyde\Validator
namespace, so it is possible to use them like this:
$value = 8; $between = new Inpsyde\Validator\Between(['min' => 10, 'max' => 20, 'inclusive' => false]); if ( $between->is_valid($value) ) { echo "Value {$value} is between 10 and 20". } else { echo "Value {$value} is not between 10 and 20". }
Other validators can be used in a pretty identical fashion.
Secondary validators
At the moment, are available following secondary validators:
All secondary validators have a with_validator()
static method, that can be used as named constructor to obtain an instance.
Negate
example
Here an example on how to use Negate
to check that given value is not included in a given haystack of values:
$not_in_array = Negate::with_validator( new InArray( [ 'haystack' => [ 'foo', 'bar' ] ] ) ); $not_in_array->is_valid( 'hello' ); // true $not_in_array->is_valid( 'foo' ); // false
Bulk
example
Here an example on how to use Bulk
to check that given array contains only strings:
$array_of_strings = Bulk::with_validator( new Type( [ 'type' => 'string' ] ) ); $array_of_strings->is_valid( [ 'foo', 'bar' ] ); // true $array_of_strings->is_valid( [ 'foo', true ); // false
Pool
example
Here an example on how to use Pool
to check that given array contains at least a WP_Post
object:
$has_post = Pool::with_validator( new Type( [ 'type' => 'WP_Post' ] ) ); $has_post->is_valid( [ 'foo', new \WP_Post([ 'id' => 1 ]) ] ); // true $has_post->is_valid( [ 'foo', true ); // false
Pool
traverse the given value and returns true when first item of the value validates the inner validator.
Compound validators
At the moment, following compound validators ar available:
DataValidator
is the more powerful validator of the package, because it is the only validator implementing
ErrorLoggerAwareValidatorInterface
interface that makes it possible to obtain error messages for validated data in a very simple way.
For this reason usage of this validator is treated separately below.
Multi
example
Here an example on how to use Multi
validator, to check that given value is an array and has two items and both of
them are strings:
use Inpsyde\Validator; $two_items_string_array = new Validator\Multi( ['stop_on_failure' => TRUE ], [ new Validator\Type( [ 'type' => 'array' ] ), new Validator\Size( [ 'type' => 2 ] ), Validator\Bulk::with_validator( new Validator\Type( [ 'type' => 'string' ] ) ), ] ); $two_items_string_array->is_valid( [' foo', 'bar' ] ); // true $two_items_string_array->is_valid( [ 'foo', 1 ] ); // false $two_items_string_array->is_valid( [ 'foo', 'bar', 'baz' ] ); // false
The first constructor argument is an array of options, just like for all the "simple" validators. The second argument is an array of validators.
Please note how we used a secondary validator (Bulk
) as a child validator for Multi
: this is totally fine, because
simple, secondary and compound validators all implement the same interface.
By default all validators are executed for the given value when is_valid()
is called, but setting the option stop_on_failure
to TRUE
, the validator stops to perform validation when the first failing validator is reached.
An alternative (and less verbose) way to build a Multi
validator instance is to use the static method with_validators()
that accepts a variadic number of validator objects:
use Inpsyde\Validator; $two_items_string_array = Validator\Multi::with_validators( new Validator\Type( [ 'type' => 'array' ] ), new Validator\Size( [ 'type' => 2 ] ), Validator\Bulk::with_validator( new Validator\Type( [ 'type' => 'string' ] ) ), );
When constructed like this, the stop_on_failure
options is set to its default, that is false
, but can be set to
true
by calling stop_on_failure()
method on the obtained instance.
use Inpsyde\Validator; $two_items_string_array = Validator\Multi::with_validators(...$validators)->stop_on_failure();
MultiOr
example
MultiOr
is very similar to Multi
, but the latter combines validator with an AND
operand, the former with OR
operand.
In other words, using Multi
all the inner validators have to validate to make Multi
validate, on the contrary MultiOr
validates if at least one of inner validators validates.
Here an example on how to use MultiOr
to validate a value to be in the range from 5 to 10 or in the range 50 to 100:
use Inpsyde\Validator; $custom_range = Validator\MultiOr::with_validators( new Validator\Between( [ 'min' => 5, 'max' => 10 ] ), new Validator\Between( [ 'min' => 50, 'max' => 100 ] ), ); $custom_range->is_valid( 7 ) // true $custom_range->is_valid( 30 ) // false $custom_range->is_valid( 60 ) // true
Error codes and input data
Some validators may fail for different reasons.
For example, RegEx
validator may fail because the input provided is not a string, or because the patter is not valid
or just because the given value does not match the provided pattern.
This is why all validators came with two additional methods (alongside is_valid()
):
get_error_code()
get_input_data()
get_error_code()
returns a code that identifies the kind of error that made the validator fail.
All default error codes are available as interface constants of Inpsyde\Validator\Error\ErrorLoggerInterface
.
For example, Between
validator might return ErrorLoggerInterface::NOT_BETWEEN_STRICT
if inclusive
option is true
, or ErrorLoggerInterface::NOT_BETWEEN
if it is false
.
get_input_data()
returns an array with information on
- the validator options
- the value that was validated
For example, in the example above Validator\Between::get_input_data()
might return:
[
'min' => 10,
'max' => 20,
'value' => 8,
]
Validators factory
The package ships with a validator factory class that can be used to build validator instances starting from some configuration values.
This is useful when more validators have to built in bulk from configuration files or for lazy instantiation.
The factory has just one method create()
that accepts a validator identifier as string and an optional array of options.
Usage example:
$configuration = [ 'between' => [ 'min' => 10, 'max' => 20 ], 'not-empty' => [], 'in_array' => [ 'haystack' => [ 'a', 'b', 'c' ] ] ]; $factory = new Inpsyde\Validator\ValidatorFactory(); $validators = []; foreach($configuration as $identifier => $options) { $validators[] = $factory->create( $identifier, $options); }
To construct shipped validators, it is also possible to use as identifier their class name without namespace, like:
$configuration = [ 'Between' => [ 'min' => 10, 'max' => 20 ], 'NotEmpty' => [], 'InArray' => [ 'haystack' => [ 'a', 'b', 'c' ] ] ];
For any custom validator (see below) that implements validator interfaces, it is possible to pass the fully qualified name of the class to obtain a constructed instance.
Error messages
This package comes with objects dedicated to get error messages when validators fail.
They are:
Inpsyde\Validator\Error\ErrorLogger
Inpsyde\Validator\Error\WordPressErrorLogger
The two loggers works in the same way, however WordPressErrorLogger
has support for translations via WordPress
translation feature.
There are two step involved in showing errors using these objects:
- Log the error(s)
- Get the array of logged errors
The code looks like this:
use Inpsyde\Validator; $between = new Validator\Between([ 'min' => 10, 'max' => 20, 'inclusive' => false ]); if ( ! $between->is_valid() ) { $logger = new Validator\Error\WordPressErrorLogger(); $logger->log_error( $between->get_error_code(), $between->get_input_data() ); foreach( $logger->get_error_messages() as $error ) { echo "<p>{$error}</p>"; } }
It might seem it requires too much work, however when validating data with DataValidator
(see below) most of the code
above is not necessary.
Error templates
When using error loggers, the error messages are created using "templates": message strings that contain placeholders for values.
Every error code available as constant of ErrorLoggerInterface
has a related template.
For example, for the code ErrorLoggerInterface::NOT_BETWEEN
the related template is:
'The input <code>%value%</code> is not between <code>%min%</code> and <code>%max%</code>, inclusively.'
Where %value%
, %min%
and %max%
are placeholders that are replaced with data passed via get_input_data()
when the
error is logged.
Error templates can be customized in 2 different ways:
- replacing the template used for specific codes in the logger
- passing a specific template when logging an error
Code-specific templates
Error loggers comes with a method: use_error_template()
that can be used to set a custom error template for a given
error code.
For example:
use Inpsyde\Validator\Error; $logger = new Error\WordPressErrorLogger(); $logger->use_error_template( Error\ErrorLoggerInterface::NOT_BETWEEN, 'Hey, the value %value% is not ok.' );
Doing like this, all the errors for Error\ErrorLoggerInterface::NOT_BETWEEN
will use the given template, unless an
error-specific template is provided when logging the error.
Instead of using use_error_template()
that replaces error templates one by one, it is possible to replace more
templates at once passing to logger constructor an array of templates where array keys are the error codes:
use Inpsyde\Validator\Error; $custom_templates = [ Error\ErrorLoggerInterface::NOT_BETWEEN => 'Hey, the value %value% is not ok.', Error\ErrorLoggerInterface::NOT_BETWEEN_STRICT => 'Hey, the value %value% is not ok. Really.' ]; $logger = new Error\WordPressErrorLogger( $custom_templates );
Error-specific templates
The method log_error()
accepts a third argument to pass a specific template that will me used for that error only:
use Inpsyde\Validator\Error; $logger = new Error\WordPressErrorLogger(); $logger->log_error( $validator->get_error_code(), // code $validator->get_input_data(), // input data '%value% is wrong, try again.', // custom error message template );
When used like this, the custom template does not affect all the other messages for same code, but only the error being logged.
DataValidator
DataValidator
is the more powerful validator in the package. Beside to collect more validators, it also provides more
methods than other validators, including get_error_messages()
that returns an array of all errors occurred while
validating given data.
"Child" validators added to DataValidator
can be used to validate:
- all items of the data to validate
- specific items identified by their "key"
Add validators to all items
DataValidator
has two methods to add validators to all the items of the data to validate, they are:
add_validator()
add_validator_with_message()
The first just accepts a validator instance, the second also accepts a custom message template that will be used to build the error message when this validator fail.
Example:
use Inpsyde\Validator; $validator = new Validator\DataValidator(); $validator ->add_validator_with_message( new Validator\NotEmpty(), 'The given value must not be empty.' ) ->add_validator( new Validator\Url([ 'check_dns' => true ]) ); $validator->is_valid([ 'http://www.example.com', 'http://example.com', 'this-will-fail' ]);
Each element of the array passed to is_valid()
will be validated against both the validators added.
In the example above, note how both add_validator_with_message()
and add_validator
implements "fluent interface"
allowing to "chain" calls to them by returning an instance of validator.
Add validator to specific items
DataValidator
also has one method that allows to add validators to specific element of the given data, it is
add_validator_by_key()
.
It takes three arguments: an instance of validator, a key used to identify the data element, and optionally an error message template to use for the validator.
Example:
use Inpsyde\Validator; $validator = new Validator\DataValidator(); $validator ->add_validator_by_key( new Validator\NotEmpty(), 'name', 'Name cannot be empty.' ) ->add_validator_by_key( new Validator\Url(), 'homepage', 'Homepage must be a valid URL.' ) $valid = $validator->is_valid([ 'name' => 'Inpsyde', 'homepage' => 'http://www.inpsyde.com', ]); if (! $valid) { foreach( $validator->get_error_messages() as $error ) { echo "<p>{$error}</p>"; } }
DataValidator
is the only validator that supports get_error_messages()
to obtain an array of all
error occurred.
Customize error message templates
By using add_validator_by_key()
and add_validator_with_message()
it is possible to customize the error template at
validator level, however, DataValidator
constructor optionally takes as first argument an instance of error logger
that will be used to build all messages.
So, it is possible to create an error logger instance with custom error messages (as shown above) and pass it to
DataValidator
constructor:
use Inpsyde\Validator\Error; use Inpsyde\Validator\DataValidator; $custom_templates = [ Error\ErrorLoggerInterface::NOT_BETWEEN => 'Hey, the value %value% is not ok.', Error\ErrorLoggerInterface::NOT_BETWEEN_STRICT => 'Hey, the value %value% is not ok. Really.' ]; $logger = new Error\WordPressErrorLogger( $custom_templates ); $validator = new DataValidator( $logger );
Item keys in error messages
When using DataValidator
, there is an additional placeholder:'%key%'
, that will be replaced with the item key of the
value that caused the error.
By default, error messages have no '%key%'
placeholder, so the string "<code>%key%</code>: "
is prepended to default message.
For example, the default message template
The input
%value%
is not a valid URL.
becomes:
%key%
: The input%value%
is not a valid URL.
This only happens when no custom error template is provided using add_validator_by_key()
or add_validator_with_message()
,
when custom error template is provided, nothing is prepended to it, but if the custom template contains the '%key%'
placeholder
it will be replaced as well.
Item key labels for error messages
Sometimes it might be desirable that the error message does not contain the item key, but a label.
For example, if the validator is used to validate data coming from an HTML form, would be nice if the error message would contain the input label, and not input name.
However, the input name (that will be the key in the submitted data array) is also needed to let DataValidator
identify
which validator apply to each field.
For this reason, both add_validator_by_key()
and add_validator_with_message()
support a special syntax for their second
argument$key
: an array of two element, with keys 'key'
and 'label'
.
Example:
use Inpsyde\Validator; $validator = new DataValidator(); $validator->add_validator_by_key( new Validator\NotEmpty(), [ 'key' => 'username', 'label' => __( 'User name', , 'txtdomain' ) ], // key param is an array here sprintf( __( '%s must not be empty.', 'txtdomain' ), %key% ) ); if ( ! $validator->is_valid( [ 'username' => '' ] ) ) { $messages = $validator->get_error_messages(); }
In the example above, because username
key has an empty value, the error message built will be the translated version of
User name must not be empty.
Note how the label is used to replace the placeholder instead of the key.
Custom validators
It is possible to create custom validators to be used with the package.
Custom validators should implement the interface ExtendedValidatorInterface
which contains the following methods:
get_error_code()
get_input_data()
get_error_messages()
(deprecated)is_valid()
The package ships with 2 traits that can be used to implement the first 3 methods, leaving only is_valid()
to implementers.
Particularly consider GetErrorMessagesTrait
that contains implementation for the deprecated get_error_messages()
(see "Upgrading from version 1.0" below for more info).
Custom validator example
A trivial custom validator could be something like this:
namespace MyPlugin\Validator; use Inpsyde\Validator\ExtendedValidatorInterface; use Inpsyde\Validator\GetErrorMessagesTrait; use Inpsyde\Validator\ValidatorDataGetterTrait; use Inpsyde\Validator\Error\ErrorLoggerInterface; class YesNo implements ExtendedValidatorInterface { const ERROR_CODE = 'not_yes_no'; use GetErrorMessagesTrait; use ValidatorDataGetterTrait; public function is_valid( $value ) { /** @see ValidatorDataGetterTrait */ $this->input_data[ 'value' => $value ]; if ( ! is_string( $value ) ) { // this is a default error $this->error_code = ErrorLoggerInterface::INVALID_TYPE_NON_STRING; return false; } if ( ! in_array( strtolower( $value ), [ 'yes', 'no' ], true ) ) { // custom error $this->error_code = self::ERROR_CODE; return false; } return true; } }
The validator might emit two error codes in case of error, one of them is a default error code, the other is custom.
If the validator is intended to be used with DataValidator
, it is necessary to add the custom code to the error logger,
something like:
namespace MyPlugin; use Inpsyde\Validator\DataValidator; use Inpsyde\Validator\Error\WordPressErrorLogger; $yes_no_message = sprintf( __( 'Accepted values are only "yes" and "no". "%s" was given.', 'txtdmn' ), '%value%' ); $logger = new WordPressErrorLogger([ Validator\YesNo::ERROR_CODE => $message ]); $validator = new DataValidator( $logger ); $validator->add_validator_by_key( new Validator\YesNo(), 'accepted' );
Upgrading from version 1.0
- The interface
ExtendedValidatorInterface
that extendsValidatorInterface
and containsget_error_code()
andget_input_data()
, was introduced in version 1.1 of the package. In version 1.0 validators implemented justValidatorInterface
. get_error_messages()
is deprecated from version 1.1- The whole reason for
ExtendedValidatorInterface
existence is to maintain backward compatibility with any custom validator built for version 1.0 and so extendingValidatorInterface
(we could not add methods to it without breaking compatibility).
For reasons above starting from version 2.0:
get_error_messages()
will be removedget_error_code()
andget_input_data()
will be added toValidatorInterface
ExtendedValidatorInterface
will thus become empty, and will just extendValidatorInterface
. It will be maintained for backward compatibility with custom validators written for 1.1+, but will be deprecated and removed from version3.0
.
Other notes
Bugs, technical hints or contribute
Please give us feedback, contribute and file technical bugs on GitHub Repo.
License
Good news, this plugin is free for everyone! Since it's released under the MIT, you can use it free of charge on your personal or commercial blog.
Changelog
See commits or read short version.