Comrades! This article is not for high-high-highload systems. The speed of the presented solutions is definitely less than the simplest checks. At thousands of or very deep structures to apply the proposed approach is not recommended. In this topic, fast coding wins, not fast code.Without long
Let's not long introductions, but still with the background. Once in the framework of creating another very important component of a web service, we needed to check a lot of very different input parameters (in this case, received through $ _REQUEST). The component was very complex, the internal and external logic caused daily batthurts between all the participants, and there were few “selected” programmers who wrote, rewritten, sawed and filed again. When dozens of different variables, including arrays, fall into the system from the frontend, programmers do cross-cutting tasks (changing logic) and interfere with each other - the code grows very quickly, the number of if-chains begins to occupy more than one page. To return to such a code is more and more alien to the vulnerable soul. Tests no longer help, since every change in logic leads to a change in the same tests that still need to be remembered, understood and forgiven. It was then that the question arose of creating a convenient way to check the entire input stream in some pleasant way, but always and everywhere to receive feedback about errors in the same type. The emphasis here was originally on the convenience for developers, I strictly ask you to have in the future.
We will, in fact, to the point
The complete example
is on the githaba .
Suppose that we have a controller that has not yet passed the stage of refacking and rethinking until it disappears completely. First of all, he takes everything that came to him from the frontend ($ params = $ _REQUEST;). For a dirty experiment, let us imagine that there is no pre-filtering - what they sent, so we are happy. For example, the contents of the $ params array will be as follows:
')
$params = array( 'doc_id' => 133, 'subject_id' => '64', 'parent_id' => 32, 'title' => 'New document', 'data' => array( 'flag' => 'experiment', 'from_topic' => false, ), );
Now we set ourselves a task: you need to check that some keys necessarily exist, some are strictly typed, something must fall within the interval (greater than zero, etc). In addition it is impossible that there was anything in the array, except for the specified keys. In the meantime, we are looking for enlightenment - it would be nice to get sufficient debugging information. At first we will try to present it in the form of a chain of conditions with a release of exceptions.
Familiar? And so many, many times a day. For each “if” condition, a business process is supposed to write a unit test with different boundary values. Imagine how many lines of test you need only for this code. What about tomorrow? “Cut here, and here you need three documents at once so that you can accept and parent_id will now be an array.” And what to do with heterogeneous errors, of which for each situation is written separately? How to integrate with a new fashionable payment system, which needs to deliver errors in the form of codes? To write a mapping for such a vinaigrette is in itself popul.
Finally it shows AMatch
Well, now dessert is to check this array for many factors with the help of
AMatch :
require_once('class.AMatch.php'); $match = AMatch::runMatch($params) ->doc_id(0, '<')
Let's sort the given code. The start of validation (matching conditions) begins with passing the checked array to the AMatch :: runMatch () method. Then, by dereference, checks are called according to the scheme:
->_([___], [])->…->stopMatch()
The stopMatch () method returns the total result - true or false. Of course, the fact that “not working” was not always enough. Therefore, if the object reference is saved in the $ match variable before the stopMatch () call, then after stopMatch () you can get detailed comments about the result. I note that stopMatch () also contains checks and errors, so if you call up comments before this method, it may turn out that there will be incomplete information.
The first launch shows “Victory!”.
Chef, everything is gone
Let's ruin the incoming array to see the result:
$params_bad = array( 'doc_id' => -4, 'subject_id' => null, 'parent_id' => 30, 'data' => array( 'flag' => 'booom', 'from_topic' => array(), 'old_property' => true, ), 'wtf_param' => 'exploit', ); $params = $params_bad;
At the output we get:
array ( 'doc_id' => 'Condition is not valid', )
Why only one item? Because he was the first in the chain. It is not always necessary to go through the entire set of parameters. By default, AMatch interrupts checks as soon as the first outstanding condition is received. Before turning on the full check, let's see which condition was not passed. To do this, replace the error output with:
die( var_export($match->matchComments(), true) . PHP_EOL . var_export($match->matchCommentsConditions(), true) );
The result now shows the performed comparison operation, which returned false:
array ( 'doc_id' => 'Condition is not valid', ) array ( 'doc_id' =>
In essence, this is a replay of the original record:
->doc_id(0, '<')
All comments are stored in constants (AMatch :: KEY_CONDITION_NOT_VALID == 'Condition is not valid'). Obviously, you can now configure the standard mapping for issuing errors to the user, based on the error constants and the conditions of their occurrence.
Need more wood!
Let's return to other conditions. To command AMatch to check and check until the conditions are over, you need to use flags (bitmask). There are currently three flags.
- FLAG_STRICT_STRUCTURE — Verify that there are no keys in the array being checked that are not declared for matching.
- FLAG_DONT_STOP_MATCHING - Do not stop mapping, even if a mismatch condition is detected.
- FLAG_SHOW_GOOD_COMMENTS - Show comments not only to the problems, but also to the matching keys and conditions.
These flags can be especially useful in the debug mode of the application. Well, the flag "FLAG_DONT_STOP_MATCHING" is especially useful, for example, if you need to check the form data and return all problems in a heap. Let's add the code:
$flags = AMatch::FLAG_DONT_STOP_MATCHING; $match = AMatch::runMatch($params, $flags) …
Now the result reveals all the problems:
array ( 'doc_id' => 'Condition is not valid', 'subject_id' => 'Condition is not valid', 'parent_id' => 'Condition is not valid', 'data' => array ( 'flag' => 'Condition is not valid', ), 'title' => 'Expected parameter does not exist in the array of parameters', ) … ( )
I love tougher!
Pay attention to the special flag FLAG_STRICT_STRUCTURE. For example, it will be especially useful in a situation where you have created an API and support its versions. Clients will connect to the API and send various requests. It is very important to notice in time that the request being sent is outdated in format and not only invalid values ​​are sent, but also extra keys in general.
$flags = AMatch::FLAG_DONT_STOP_MATCHING | AMatch::FLAG_STRICT_STRUCTURE;
Execute the code with this flag. Two new lines will be added to the comments:
'stopMatch' => 'Unknown parameters in the input data',
'Unknown parameters:' => 'wtf_param',
// For any mapping, use the error value AMatch :: UNKNOWN_PARAMETERS_LIST and the key name AMatch :: _ UNKNOWN_PARAMETERS_LIST
In general, it is clear from this that a parameter was found for which there are no validation conditions.
Disinsection
The last flag is useful for debugging modes, when you need not only to know that there are invalid keys, but also to know that valid keys have been accurately verified. Let's add flags to debug and return the original source array:
define('DEBUG_MODE', true); if (DEBUG_MODE) { $flags |= AMatch::FLAG_SHOW_GOOD_COMMENTS; } … Victory: if (DEBUG_MODE) { $comments = $match->matchComments(); $comments_explanation = $match->matchCommentsConditions(); echo PHP_EOL; var_export($comments); echo PHP_EOL; var_export($comments_explanation); }
Now in the comments there will be a lot of books (they are all in AMatch constants):
Victory! array ( 'doc_id' => 'OK. Condition is valid', 'subject_id' => 'OK. Condition is valid', 'author_name' => 'Optional parameter, skipped bad condition result', 'parent_id' => 'OK. Expected parameter type is valid', 'data' => 'OK. Expected parameter type is valid', 'title' => 'OK. Expected parameter exist in the array of parameters', 'stopMatch' => 'The array does not contains unknown parameters', )
Separately, I note here that the rewriting of the execution result and comments is performed only under unsuccessful conditions. That is, if previously there was “success” in the conditions, then an erroneous condition would overwrite the comment with the key. If there was a failure earlier, the successful condition will not touch the comments.
I am not satisfied!
Add some candy. Before $ match-> stopMatch (), check the nested array structure:
function checkDocumentData($data) { $result = AMatch::runMatch($data) ->flag('experiment')
I hope everything is clear here.
After letter
Thanks for the ideas to Andrei Tereshchenko, Andrei Lugovoi and the Pravo.ru development team.
You can find more examples (see unittests) and download the source here:
https://github.com/KIVagant/AMatchI would be very happy if your ideas turn into useful commits. I do not like criticism, but oh well, burn in the comments. And alas, I am not strong in English, so Grammar-Nazi Well-Wolves.
UPD:
Next article:
new callback and work with errors .
UPD:
Converted to
Composer .