smwcentral/validator

SMW Central form validation library

v1.0.0 2021-01-10 22:13 UTC

This package is auto-updated.

Last update: 2024-04-11 05:20:38 UTC


README

Latest Stable Version GitHub Actions License

The lightweight form validation component used by SMW Central. Aims to provide a friendly and flexible API at a minimal runtime cost.

Installation

Install with Composer. Requires PHP 7.3+ with the JSON and mbstring extensions.

$ composer require smwcentral/validator

Example

use SMWCentral\Validation\Validator;

$validator = new Validator($_POST);
$recipient = $validator->string('recipient')->value();
$subject = $validator->string('subject')->between(1, 255)->value();
$text = $validator->string('text')->between(1, 65535)->value();

$isRecipientValid = false; /* validate manually somehow */

if(!$isRecipientValid){
    $validator->errors()->add('recipient', 'not_found');
}

if($validator->passes()){
    // use data
}

Usage

Basic usage

Construct a Validator, passing it an array of fields that may be validated, usually just $_POST.

use SMWCentral\Validation\Validator;

$validator = new Validator($_POST);

Use retrieve() to create a Variable object that represents a given field. By default, fields are required and will create a validation error if no value exists. Pass a second argument to specify a default value.

$id = $validator->retrieve('id'); // required
$description = $validator->retrieve('description', ''); // will default to an empty string

The first validation rule called on each Variable must specify the data type. All other validation rules will throw a LogicException until the type is specified.

The field's value is coerced to its given data type. A validation error is added if this isn't possible.

$variable->string();
$variable->integer();
$variable->float();
$variable->boolean();
$variable->array();

After specifying a data type, call other validation rules as appropriate. All methods on the Variable can be chained.

Validation rules that deal with the "size" of the value use the number itself for integers and floats or the length of strings and arrays.

$variable->size($exactSize);
$variable->min($min);
$variable->max($max);
$variable->between($min, $max);
$variable->in($arrayOfPossibleValues, $useStrictComparison = true);

Finally, use value() to get the value of the field. It's guaranteed to be whichever data type the Variable was specified to use (or null if the field is optional). None of the other validation rules are necessarily met until you check the result of the validation.

$value = $variable->value();

Use the passes() method on the Validator to check if all validation rules have succeeded. Use errors() to get a MessageBag with errors for each field.

if($validator->passes()){
    // all values are guaranteed to follow the validation rules
    // continue form action
}else{
    $errors = $validator->errors();

    // show errors to the user
}

Shorthand types

The Validator has shorthand methods that retrieve a variable and set its data type at once. You will practically always use these instead of retrieve().

$validator->string($name, $default = null);
$validator->integer($name, $default = null);
$validator->float($name, $default = null);
$validator->boolean($name, $default = null);
$validator->array($name, $default = null);

// is equivalent to

$validator->retrieve($name, $default = null)->string();
$validator->retrieve($name, $default = null)->integer();
$validator->retrieve($name, $default = null)->float();
$validator->retrieve($name, $default = null)->boolean();
$validator->retrieve($name, $default = null)->array();

A complete chain usually looks like this:

// Field `description` is an optional string between 0 and 65535
// characters. Its value will be stored in `$description`.
$description = $validator->string('description', '')->between(0, 65535)->value();

Bailing

By default, all validation rules called on a Variable will run. In the end, there may be multiple validation errors. Use the bail() method to ignore validation rules for a Variable after the first failure.

// If the value is shorter than 5 characters, both `min()` and `between()`
// will create a validation error.
$variable->min(5)->between(8, 10)->value();

// If the value is shorter than 5 characters, only `min()` will create a
// validation error. `between()` (and anything after it) will be ignored.
$variable->bail()->min(5)->between(8, 10)->value();

Message bags

Validation errors are collected in a MessageBag that can be accessed with the errors() method of a Validator. MessageBags hold any number of Message objects for any number of fields.

$errors = $validator->errors();

// Methods that check all Messages
$errors->all();
$errors->count();
$errors->isEmpty();

// Methods that operate on specific fields
$errors->except(['fields', 'to', 'exclude']);
$errors->get('field');
$errors->getFirst('field');

// `MessageBag`s can be counted and encoded to JSON
count($errors);
json_encode($errors);

Manual validation

Variables only provide basic validation rules. You will often need to validate the data further. Retrieve the type-casted field value with value(), then run any logic you need. Add your own validation errors with the add() method of the MessageBag.

For finer control over the validation error (e.g. to provide extra arguments), you can also pass an instance of Message to errors()->add().

use SMWCentral\Validation\Message;

$recipient = $validator->string('recipient')->value();

$recipientID = findUserID($recipient);

if(!$recipientID){
    $validator->errors()->add('recipient', 'not_found');

    // is equivalent to

    $validator->errors()->add('recipient', new Message('recipient', 'not_found'));
}

if($validator->passes()){
    // manual validation has succeeded
}

CSRF tokens

The passes() method of the Validator can automatically check a CSRF token. To enable this functionality, set Validator::$tokenProvider to an instance of a custom class that implements the ITokenProvider interface.

You can use passes(false) to skip token validation if a token provider is a configured.

use SMWCentral\Validation\{ITokenProvider, Validator};

Validator::$tokenProvider = new class implements ITokenProvider {
    public function getTokenKey(Validator $validator): string {
        // the name of the field that should contain the token
        return 'token';
    }

    public function getTokenValue(Validator $validator): string {
        // the expected value of the token
        return $_SESSION['token'];
    }
};

Messages

Validation errors are represented by Message objects.

$message = $validator->errors()->getFirst('field');

echo $message;

// is equivalent to

echo $message->getTranslatedMessage();

Internally, messages only contain a key that is later passed to a message resolver to construct a human-friendly message. This package includes a rudimentary default resolver.

In most cases, you'll want to use your own resolver. Set Message::$resolver to an instance of a custom class that implements the IMessageResolver interface. Check the getMessages() method in src/DefaultMessageResolver.php for a list of message keys that the validator will use.

use SMWCentral\Validation\{Message, MessageResolver};

Message::$resolver = new class implements IMessageResolver {
    public function resolveMessage(string $field, string $key, array $args): string {
        return "{$field}: {$key}";
    }
};

Note that Messages maintain their own copy of the field name, not necessarily identical to the field name that owns the Message in the MessageBag.

The $args array passed to resolveMessage() contains a set of arbitrary additional arguments. Some validation rules will give such arguments to their messages. For example, the between() rule will add min and max arguments. You can also provide custom arguments to any Variable using the third parameter of the Validator's retrieve() method and its shorthands.

$validator->string('name', null, ['context' => 'username'])->value();

When adding validation errors manually, you can create a Message yourself to provide arguments.

use SMWCentral\Validation\Message;

$validator->errors()->add('recipient', new Message('recipient', 'not_found', ['context' => 'private_message']));

The default message resolver will directly include the field's name in the message. To ensure a coherent message is created, you can pass an argument named field to override the name:

$validator->string('new_pass')->min(8);
// DefaultMessageResolver could produce: The new_pass must have at least 8 characters.

$validator->string('new_pass', null, ['field' => 'new password'])->min(8);
// DefaultMessageResolver could produce: The new password must have at least 8 characters.

Of course, it is up to you to implement such logic in a custom message resolver.

Shorthand configuration

You can use Validator::configure() to set a message resolver and a token provider at once.

Validator::configure($messageResolver, $tokenProvider);

If null is passed as one of the arguments, it will be ignored.

Tests

Tests are in the tests directory and use PHPUnit. Run with vendor/bin/phpunit tests.

License

Released under the MIT License.

Credits

Built and maintained by Telinc1. SMW Central is property of Noivern.