How Can Validity PHP Validation Library Simplify the Process of Only Accepting Correct Data Entered Passed to Your PHP Applications - PHP Validity Checker package blog

Recommend this page to a friend!
  All package blogs All package blogs   PHP Validity Checker PHP Validity Checker   Blog PHP Validity Checker package blog   RSS 1.0 feed RSS 2.0 feed   Blog How Can Validity PHP ...  
  Post a comment Post a comment   See comments See comments (0)   Trackbacks (0)  

Author:

Updated on: 2021-04-12

Posted on: 2021-04-12

Viewers: 422 (April 2021 until June 2021)

Last month viewers: 63 (June 2021)

Package: PHP Validity Checker

As we all know, validation of data values entered by the users or passed to pages from external sources like the current HTTP request parameters, is very important to protect our PHP applications against possible abuses caused by security exploits.

Read this article to learn more about how the PHP Validity Checker library can help you to make your PHP applications more secure.




Loaded Article

In this article you will learn:

Introduction to the PHP Validity Checker Library

In $_GET We Trust

PHP Validity Checker: Yet Another Package

Learn by Example: Take Me to the Code

Creating Fields

Required Fields

Default Values

Simple Constraints

Callbacks as Validators

Dates

Validating Arrays

Validating Compound Values

Tips and Tricks

Labels

Download the PHP Validity Checker Library or Install it with PHP Composer Tool

Conclusion


Introduction to the PHP Validity Checker Library

Validity is a simple validation and filtration package for PHP. It can be used to validate both single values like integers and strings, and complex nested structures, using however complex validation logic you can think of.

Nice default errors messages in English or Russian. Easily extensible with a custom language package, with always having ability to provide a custom error message for any rule on every field.

In $_GET We Trust

A self-confident developer is a bad developer. NO.USER.INPUT.SHOULD.BE.TRUSTED. There is a lot of PHP code that is written with an implication that a certain input parameter contains a certain type of information. For example:

$searchQuery = trim($_GET["query"]);

Sometimes it can look a bit better:

$searchQuery = trim($_GET["query"] ?? "");

or:

$searchQuery = empty($_GET["query"]) ? "" : trim($_GET["query"]);

What if I simply tweak the URL in the browser address bar in a way that the address that used to be "http://example.com/search.php?query=hack" becomes "http://example.com/search.php?query%5B%5D=hack". %5B%5D is actually square braces [].

After this tweak, $_GET["query"], that used to be a string, suddenly becomes an array. All the examples above start to produce an error of level E_WARNING: PHP Warning: trim() expects parameter 1 to be string, array given. This may or may not have a serious consequence, but it still is undesirable.

In order to check for a proper value, you could have used this code:

$searchQuery = filter_input( INPUT_GET, "query", FILTER_SANITIZE_STRING);

In fact, if you only need to check if the input valid based on simple type rules, while at the same you need to ensure that the data is secure, the PHP's filter extension is certainly worth checking.

Simple checks like for int, float, booleat are really simple. However, when your validation rules become more sophisticated, you will probably find that filter functions have a messy interface with the need to specify all the parameters in either "options" or "flags" key of the $options argument.

Also, most of the time, its the user who enters invalid input. Many users do it without an intention. So it would be nice of you to inform about validation errors in a way that they can understand, preferably, in a language of a user choice.

Filter functions do not do that. They only can return FALSE when the input is incorrect.

Every major PHP framework has a solution for this problem, like for instance Zend Framework, Yii2, Symfony, Phalcon.

Personally, I do not like any of them.

  • Zend Framework offers robust validation utilities. It's awesome that you can just install the validator package with composer and use it.

    In my opinion, though, it's a bit overcomplicated, and has too many dependencies (only 2 mandatory though, but some validators will require additional packages).

    Internationalization is provided in different packages as well, and, in my opinion, is also complicated.

    I also had a lot of weird issues with Zend Validators back in the times of ZF version 1, and those simply make me avoid ZF where possible.

    Other then that, it seems to be a cool project, you just have to know a few tips:

    1) Zend Validators have fluent setter methods for most of the options, which makes them so much easier to use than instantiating with ugly $options (the way that is promoted in documentation);

    2) They are callable.

  • Yii2 also has validators in place. I completely dislike the way they declare validation rules for the models (in form of associative arrays).

    Ad-Hoc validators do not seem to have a nice interface, I just don't like things like this:

    $model->addRule(['name', 'email'], 'string', ['max' => 128]). Adding custom error messages also seems messy.

  • Symfony requires 2 classes to validate a single value: Constraint and ConstraintValidator. To validate email, those would be Email and EmailValidator.

    I could not find an easy way to invoke validation on a variable and stopped looking in this direction.

  • Phalcon offers a lot of validators. However, the validate method signature is such: validate (Phalcon\Validation $validation, mixed $field).

    There is no way to check a single rule without having to instantiate a Validation object.

    Adding multiple rules to the same field is also not good in my opinion: you have to call Validation->add('field',..) for every rule. Validator->setOption() method does not provide "fluent" interface, so it is not chainable.

PHP Validity Checker: Yet Another Package

Diversity is good. I made validity. I use it for my projects. I find it convenient. That is why I decided to add it to Github and Packagist. It is a reasonably small validation package that easily covers all my needs:

  • validating basic data types, without having to type a lot,
  • validating arrays, validating against complex logic,
  • validating values depending on other fields' values,
  • filtering of value
  • internationalization, nice error messages for all cases — out-of-the-box
  • ability to specify custom message for each and every case

Learn by Example: Take Me to the Code

OK. You asked for it.


use \validity\FieldSet, \validity\Field;

$name = Field::pattern( "name", "/^[A-Z][a-zA-Z\- ]+$/" )-> setRequired();
$greeting = Field::enum( "greeting", ["mr", "mrs"] )->setRequired();
$subscriptions = Field::int( "subscriptions" )->setMin(1)->expectArray();
$email = Field::email("email")->setRequiredIf(
    function(FieldSet $fieldSet) {
        return (bool) $fieldSet->getFiltered("subscriptions");
    }
);
$dateOfBirth = Field::date( "date_of_birth" )->setMax( "-18years" )->setRequired();
$education = Field::string("education")
     ->setMinLength(10)
     ->setMaxLength(100)
     ->expectArray()
     ->setArrayMinLength(0)
     ->setArrayMaxLength(3)
     ->setArraySkipEmpty(true);

$fieldSet = (new FieldSet())
        ->add($name)
        ->add($greeting)
        ->add($subscriptions)
        ->add($email)
        ->add($dateOfBirth)
        ->add($education);

if ( $fieldSet->isValid( $_POST )) {
    $data = $fieldSet->getFiltered();
    // do something with $data
} else {
    // display errors summary
    echo $fieldSet->getErrors()->toString();
}

In this code example, no custom messages are used. Because the language is not specified for the FieldSet constructor, the default language class (English) will be used to provide pretty neat error messages.

However, for every call that specifies a validation rule, you may supply a custom message, and it will then override the one from language pack. Messages can be provided in form of a template. {label} is always replaced by the field's label.

Creating Fields

Fields are created using named constructors:

  • Field::int(string $name, string $message = null),
  • Field::float(string $name, string $message = null),
  • Field::bool(string $name, string $message = null),
  • Field::string(string $name, string $message = null),
  • Field::date(string $name, string $message = null),
  • Field::datetime(string $name, string $message = null),
  • Field::enum(string $name, array $values, string $message = null),
  • Field::email(string $name, string $message = null),
  • Field::phone(string $name, string $message = null),
  • Field::pattern(string $name, string $pattern, string $message = null),
  • Field::assoc(string $name, FieldSet $innerFieldSet, $message = null, $errorSeparator = "; ").

Every named constructor returns a Field of a corresponding type. Most of them are pretty much self-explanatory, with an exception of Assoc, which you can know more about in Validating compound values section.

In all cases, $message is used as the error text in case the input value cannot be interpreted as int, float, etc..

Required Fields

Field can be marked as required:

Field::string( "username" )->setRequired( "Please enter username" );

Sometimes, a field is only required in case some other fields are filled (or any other conditions are met).

Field::email("email")->setRequiredIf(
    function(FieldSet $fieldSet) {
        return (bool) $fieldSet->getFiltered("subscriptions");
    }
);

The setRequiredIf() function accepts either a boolean or a callback as argument. the In first case, it simply marks the field as required or removes this mark.

When a callable is used, it is evaluated in the process of validation, and the field is only considered as required if the callback returns TRUE. In the example above, the email field is required in case the user has chosen to subscribe to some mailing list.

Default Values

Every field can have assigned a default value:

Field::int("price")->setDefault(100, true, false);

The two remaining arguments are $replaceEmpty and $replaceInvalid. In case $replaceEmpty is set to TRUE (default behavior), the default value is used for the field in case no value was specified in input.

In case $replaceInvalid is set to TRUE (defaults to FALSE), then, should the input value fail any validation, it is replaced silently with the default, and no error is raised.

Simple Constraints

Numeric fields (int and float), as well as Date and Datetime fields can be assigned minimum and maximum values:

Field::int("price")->setMin(1);
Field::date("date_of_birth")->setMax("-18years");

String values can be set to accept a minimum and maximum string length:

Field::string("name")
    ->setMinLength(2)
    ->setMaxLength(100);

In case an array is expected, it can also be limited:

Field::string("education")
    ->setMaxLength(100)
    ->expectArray()
    ->setArrayMinLength(0)
    ->setArrayMaxLength(3)

In the last case, the setMaxLength(100) cakk limits the length of every string in the education array, while setArrayMinLength() and setArrayMaxLength() set the limits for the array size. So we expect from 0 to 3 entries in education, each entry being a string with a maximum length of 100 characters.

Callbacks as Validators

This is the most powerful validation feature:

Field::string("username")
    ->addCallbackRule(
        function($value, FieldSet $fieldSet) {
            return !Users::usernameExists($value);
        }
    );

When the callback is called, it is passed 3 arguments:

  1. $value obviously, the value to be validated;
  2. $fieldSet the FieldSet instance, so that it is possible to get values of neighboring fields;
  3. $key in case the field expects an array value, then an integer or a string key of the array element is passed (defaults to NULL).

The callback should return boolean: TRUE if the validation passes, FALSE if it fails.

Dates

Date and datetime fields will transform the input value into a formatted date. So when you finally call the getFiltered() function, the field values will contain string dates formatted according to output format setting (Y-m-d by default for date and Y-m-d H:i:s for datetime fields).

You may alter this format setOutputFormat(). By default, Date and Datetime fields expect the input to strictly follow format set by setInputFormat(). And this input format also defaults to Y-m-d or Y-m-d H:i:s.

However, setInputFormat() accepts the second optional argument $strict. Setting it to false will cause the validator to try first the exact input format but then to try PHP's date_create().

Be careful with using non-strict date formats because it might be very confusing. For example, values like "+1year" or "16 years ago march 1st" will be valid dates.

Furthermore, values like "2010-02-31", which is obviously an invalid date, will pass validation! In the later case, the resulting value will be (surprise!) 2010-03-03. More on this in PHP manual read comment by thomas dot ribiere at allgoob dot com.

Validating Arrays

Any field can be set to expect an array value:

Field::int("subscriptions")->expectArray();

In this case, the field is expected to be array of elements. Each of those elements is validated against a given type (int in the example above). Any of the rules that you specify. The minimum and maximum array length can be controlled with:

  • setArrayMinLength(int)
  • setArrayMaxLength(int)

In case some of the array elements fails validation, it might be handy to see what particular elements failed. Consider the following example:

Field::int("subscriptions", "{label}: value #{key} is not an integer")
    ->setMin(1, "Subscriptions: value #{key} must be greater then or equal to {min}")
    ->expectArray()
    ->setArrayKeyOffset(1);

Let's say we have the following dataset:

{
  "subscriptions": [
    1,
    "not an integer",
    -1
  ]
}

Then we will have the following error messages:

  • subscriptions: value #2 is not an integer"
  • subscriptions: value #3 must be greater then or equal to {min}"

The message parser will replace "{key}" with the corresponding array key. Numeric arrays in PHP are zero-indexed, so by default, in case of an error on the first array element, you get an error message saying "value #0 is invalid".

While this might be a desired behavior, you also may want this to be displayed as "value #1 is invalid". This is achieved by calling Field::setArrayKeyOffset(1). Then all the keys are incremented by 1 in the error messages.

It is also often the case that integer IDs are used as keys for various data, so by default validity does not apply an offset to numeric keys.

Validating Compound Values

Assoc (Field::assoc()) is a compound field. So FieldSet will expect the value to be an array, which it will validate with a help of $innerFieldSet.

A real life example: set of three fields used to make up a date input: date[year], date[month], date[day].

Let's suppose that we expect $POST["date"] to be array(year: integer, month: integer, day: integer).

In this case, _$innerFieldSet will contain fields created with Field::int() and named year, month and day, each having proper range validation: setMax() and setMin()

The $message parameter to Field::assoc() will only be used in case when $POST["date"] is not an array.

If it is an array, then the _$innerFieldSet will take care of further validation. Should the $innerFieldSet report any errors, they will all be combined into a string and used as an error message for the date field in the outer FieldSet.

Tips and Tricks

Field::string("email")-> addCallbakRule( new EmailAddress(), "Email is invalid!" );
Field::string("email")->addCallbakRule( function($value) {
    return (new EmailValidator())->validate($value);
}, "Email is invalid!");

I have chosen using the email validation for these tips on purpose. The PHP Validity Checker also offers email validation, but (at least for now) it is a simple regexp check.

The Zend or Yii2 offer much more sophisticated ways to validate an email and you can simply use it while at the same time performing routines validity-style.

Labels

Every field must have a name. The name is the first and required parameter to all the named constructors. The name is the key of the associative array the FieldSet will validate.

In addition, field can also have a label. For example, field name is date_of_birth but the label is Date of birth. The label can be set with Field->setLabel(string $label). If it is not set, the field name is used as label.

Download the PHP Validity Checker Library or Install it with PHP Composer Tool

The PHP Validity Checker library can be downloaded by going to the package download page. It may also be installed using the PHP Composer tool by following the instructions in the installation page

Conclusion

Validity is one of many validation packages out there. I find it feature-reach enough to build on top of it, and I find it one the most convenient of its class. If you want to help this project: 1) find bugs, submit pull requests (preferable unit-tested) or 2) submit new translations - see validity\Language\En.php. Cheers!




You need to be a registered user or login to post a comment

Login Immediately with your account on:

FacebookGmail
HotmailStackOverflow
GitHubYahoo


Comments:

No comments were submitted yet.



  Post a comment Post a comment   See comments See comments (0)   Trackbacks (0)  
  All package blogs All package blogs   PHP Validity Checker PHP Validity Checker   Blog PHP Validity Checker package blog   RSS 1.0 feed RSS 2.0 feed   Blog How Can Validity PHP ...  

For more information send a message to info at phpclasses dot org.