📜 ⬆️ ⬇️

Strain It! - Validation and data casting in PHP

Introduction


Perhaps any programmer sooner or later faces the problem of data validation. Having spent a lot of energy on writing validation algorithms of the same type and I decided to look for more convenient methods. It was a long time ago, and since then I happened to use a large number of ready-made solutions and develop several of my own, which I safely used until recently.

But after I switched to PHP 5.3, I was drawn to write a new validator based on anonymous functions. And at the same time once again practice in their use. I set the following requirements for the future class:

Already after the start of work, I had the idea that anonymous functions in this class can be used not only for validation, but also to bring data to the desired form (for example, to a certain type). Having added one more (“Universality”) to the list of requirements, I boldly renamed the class Strain.

What eventually turned out ... read below. Here I will not claim the originality of the decision and other nonsense. If you like this approach, use it in your projects, and if not - then not. For desperate lovers of perversions, I will publish the source code at the end of the article.

Class strain


As I wrote above, the functionality of the class is double. Let's take a look at both types of anonymous functions used in it and their application. Of course, nothing prevents you if you want to combine their functionality in one function.
')
I hope I do not scare you too much hastily invented names and definitions.

Function-cast (OP)

FP for data conversion (in our case, some variable). In essence, it can contain any logic for transforming one data into another. For example, create a function that will translate everything into INTEGER.
Strain :: add ( 'integer' , function ( & $value , $options = null ) { <br/>
$value = ( int ) $value ; <br/>
} ) ;

This is just a simple example. In real use, I would add here another limit on the number of bits in the number.

Now let's look at the operation of this function:
$var = '56' ; // ! . <br/>
Strain :: it ( $var , 'integer' ) ; <br/>
var_dump ( $var ) ; <br/>
<br/>
// : int(56)

Function Validation (FV)

The FV checks the data for compliance with a specific condition and, in case of an error, throws out the report. If the function returns nothing (i.e., returns NULL), then the data is correct. Example:
Strain :: add ( 'must_be_integer' , function ( & $value , $options = null ) { <br/>
if ( ! is_int ( $value ) ) return true ; <br/>
} ) ;

Comments are superfluous. Let's look at the work function:

$var = '56' ; <br/>
var_dump ( Strain :: it ( $var , 'must_be_integer' ) ) ; <br/>
<br/>
// : TRUE

TRUE indicates an error. In more detail, the returned value can be found in Strain :: $ result (in this example, it coincides with TRUE)

Challenging example

Now, after we have dealt with the basics, it is time to move to a more complex application of Strain - validation of objects.

To begin, create the object itself with the data. And let it be, for example, data about the new user that we want to add.
$user = ( object ) array ( <br/>
'email' => 'user@site.com' , <br/>
'name' => 'User' , <br/>
'address' => ( object ) array ( <br/>
'city' => 'Default City' , <br/>
'street' => 'Street' <br/>
) <br/>
) ;

And so ... we have a certain object that must contain correct data and have the structure we need, and we must make sure of this before throwing it into the database, and we must know exactly about all the validation errors. Fortunately, Strain can completely solve this problem.
Create an object with which validation will occur. In the future, I will call such objects a “data filtering circuit” (SF).
$valid = ( object ) array ( <br/>
'email' => array ( 'email' , 'UserExists' ) , <br/>
'name' => array ( 'string' , 'regexp' => '/^[A-Za-z0-9 _-]{3,20}$/' ) , <br/>
'address' => ( object ) array ( <br/>
'city' => 'string' , <br/>
'street' => 'string' <br/>
) <br/>
) ;

Let's take a closer look at the Federation Council. It is immediately noticeable that the object completely repeats the structure of the data being checked, only here the values ​​of the properties are somewhat “strange”. I am sure many of you have already guessed exactly how this object was compiled.

Each value of the object contains either the name of the function or another FS. For example, in the form of a list of function names - an array of mixed type, with the possibility of passing parameters to a function. Remember that incomprehensible $ options variable in the function assignment? this is it!

Now we proceed to the test. There is already a familiar challenge to us.
Strain :: it ( $user , $valid ) ; // FALSE

Do not forget to see what remains in Strain :: $ result
object ( stdClass ) #7 (3) {<br/>
[ "email" ] => <br/>
NULL <br/>
[ "name" ] => <br/>
NULL <br/>
[ "address" ] => <br/>
object ( stdClass ) #9 (2) {<br/>
[ "city" ] => <br/>
NULL <br/>
[ "street" ] => <br/>
NULL <br/>
} <br/>
}

As we see the object has been filtered without leaving any errors.

Additional flexibility of the method lies in the fact that using the Strain :: add () method, you can add not only the anonymous functions themselves, but also the invoices that you saw above. Recursions are also possible when one of the functions starts its own check. (see below)

A couple of comments

Before you start messing with this class, you should learn a couple more tricks.

If the data is checked by a list of functions, then the result from the first function that returned is returned to $ result. BUT if the function returns FALSE, then the result will go NULL, and the execution of the chain will stop. This is done in order to have a simple way to stop the check without returning an error, for example, if the value can be NULL, or it can be specified, and the check needs to be performed. Example:
$valid = array ( 'null' , 'string' , 'length' => array ( 2 , 10 ) ) ;

In the 'null' function, you need to write so that it returns FALSE, if it receives a NULL value, then no further checks will be performed and no error will appear.

Implementation of checking the array with the same type of data.
$valid = array ( 'array_of' => array ( 'string' , 'length' => array ( 2 , 10 ) ) ) ;

An example of how the function is easily transferred to the SF, which it can use at its discretion. In this case, they check every element of the array.

Another interesting example. Realization conditions OR.
$valid = array ( 'mixed' => array ( 'null' , array ( 'string' , 'length' => array ( 2 , 10 ) ) ) ) ;

It does the same thing as the example with NULL above in the text, only without the mandatory return of FALSE by this function. Similarly, you can come up with functions that implement various XOR, NOR and BRRR!

In conclusion, I want to tell you about the third parameter of the Strain :: it () method. It defines the behavior applied to the data. Parameter values:

0: Does not change the data structure;
1: Adds the properties specified in the filter structure to the data structure;
2: Removes from the data structure properties not specified in the filter structure;
3: 1 and 2 together. (By default. Only with this value it is guaranteed that the object after filtering will have a structure similar to the invoice, even if instead of the object you stick a NULL )

Those. the class is able to remove unnecessary properties of objects, add new ones and replace them with something that does not meet the requirements of the Federation Council. Created properties are NULL by default and are filtered.

Conclusion


Unfortunately, I did not include anonymous functions in the sources, except for 'array_of' and 'mixed'. In any case, writing them is quite easy, and everyone will still want to make them in their own way.

For ease of use and clarity of the code, I recommend to separate the spelling of the names of the OP and FV. For example, all FPs should be given names containing at the beginning (or end) the character `!`. Or come up with a parameter that will explicitly point to logic.

Important! At the moment the class is still being tested and you can use it at product only at your own peril and risk.

As promised, the link to the source:
Strain sources on github

PS Does anyone need something like that, just for JS?

Update: If someone is very confused that the it () method returns TRUE, when there is an error and FALSE, when there is no error, and not vice versa, then they can correct its code. This will not affect the work of the class.
Update 2: A check () method has been added to the class, which is similar to the it () method, but returns TRUE if there are no errors and FALSE if there is one.
Update 3: Added the ability to specify functions directly in the schema, as well as the ability to insert schema objects into the schema arrays, which probably draws on another article, because you cannot explain it in a few words. One of the examples I posted in the comments.

Source: https://habr.com/ru/post/115082/


All Articles