Multiscene or Want to share one interesting architectural reception
I never liked PHP frameworks to work correctly. And even the use of the word is not liked. Just to clarify — I'm not talking about fatal errors, not about error_reporting, I'm talking about what is called the validation errors. In the models, the forms — it really depends from the framework.
You just take a look. Here's an example Yii and Yii2, getting validation errors of the model:
the
Symfony form errors:
the
Actively marketed Pixie (quite a while about it did not exist):
the
What's wrong with it?
Yes, everything. It is not so. All this code really smells bad, it smells at times PHP4, spaghetti-architecture and wild confusion.
What do you do?
To begin to understand. From the very beginning.
the
1. Validity is the answer to the question "whether the value is valid, otherwise valid in this context". The context may be different, this field in the form, and the property of the object. Interestingly, the answer is "Yes" to the question of validity does not imply any additional information, but the "no" answer requires explanation. For example: the password is invalid BECAUSE its length is less than 6 characters.
2. Validation is the process of checking the validity. We have you a meaning and a context. Validator (the process performing the validation) that needs a simple answer, valid values in this context, and if not, why not.
3. "Why" from the previous paragraph just referred to as "validation error". validation Errors — detailed information about what exactly caused the false answer to the question of the validity of the data, that is the cause of a failed validation. In fact, it is not error in the sense of "chief, all is lost!", and just a validator's report, however, the word "error" has already taken root among developers.
4. validation Rule function that takes a context and a value and return an answer about validity. The answer should include true/false and the validation report, i.e. a set of errors, if any.
validation quite often (especially in some frameworks that still support PHP 5.2, we will not point a finger at them) confuse sanitize (or in Russian "cleaning") values. Do not confuse the concepts "validation" and "cleanup" (or reduction to a canonical form), are two completely different processes.
A good example that I like: the deployment of Russian phone numbers. For validation, it is sufficient (in the General case), so the string was 11 digits, and the first 7 of them, with arbitrary number and positions of other characters. If it's not, validation fails. The task of the General is to remove from it everything except numbers, so we can keep the database standardized msisdn.
Read to finally understand the difference: php.net/manual/ru/filter.filters.php
the
what a collection of validation errors is not an exception.
All of these wonderful
the
Try to put the problem. What would you like to see in the code? Well, let's say, something like this:
Somewhere in the active code is:
the
Somewhere in the template:
the
The essence of the proposed architectural pattern can be expressed very briefly: Multiscene. The exception, which is a collection of other exceptions.
How to achieve this? Fortunately, PHP allows us and not such stunts.
the
In the model validation rules are created. They are throwing exception whenever a value fails validation when you assign it to a field model. For example:
the
Create magic setter which will automatically call the validator for the field. And at the same time to convert an exception to another type, containing not just the error message validation, but also the name of the field:
the
Create a method fill($data), which tries to populate the model with data and gently gather into one all the validation errors for individual fields:
the
In fact, everything. It is possible to apply. A bunch of advantages:
the
the
Here's all of the small nuances, is a military code that I always use, and even teach their students to apply. Surprised that he has never seen before such a simple concept. If I first give you the ideas and code presented in this article are in the public domain. If not the first — sorry to the author of his mistake (s)
UPD on the results of the review
I thank all the commentators for their valuable thoughts and opinions.
The essence of the article is not validation. At all. Validation is just a bad calivornia example, I just failed to come up with the best.
The bottom line is very simple. PHP can be object that is and exception, and other exceptions at the same time. And this is useful.
Article based on information from habrahabr.ru
You just take a look. Here's an example Yii and Yii2, getting validation errors of the model:
the
$errors = $model->getErrors();
Symfony form errors:
the
$errors = $form->getErrors();
Actively marketed Pixie (quite a while about it did not exist):
the
$result = $validator- > validate($data);
$errors = $result->errors();
What's wrong with it?
Yes, everything. It is not so. All this code really smells bad, it smells at times PHP4, spaghetti-architecture and wild confusion.
What do you do?
To begin to understand. From the very beginning.
the
to Define important concepts.
1. Validity is the answer to the question "whether the value is valid, otherwise valid in this context". The context may be different, this field in the form, and the property of the object. Interestingly, the answer is "Yes" to the question of validity does not imply any additional information, but the "no" answer requires explanation. For example: the password is invalid BECAUSE its length is less than 6 characters.
2. Validation is the process of checking the validity. We have you a meaning and a context. Validator (the process performing the validation) that needs a simple answer, valid values in this context, and if not, why not.
3. "Why" from the previous paragraph just referred to as "validation error". validation Errors — detailed information about what exactly caused the false answer to the question of the validity of the data, that is the cause of a failed validation. In fact, it is not error in the sense of "chief, all is lost!", and just a validator's report, however, the word "error" has already taken root among developers.
4. validation Rule function that takes a context and a value and return an answer about validity. The answer should include true/false and the validation report, i.e. a set of errors, if any.
validation quite often (especially in some frameworks that still support PHP 5.2, we will not point a finger at them) confuse sanitize (or in Russian "cleaning") values. Do not confuse the concepts "validation" and "cleanup" (or reduction to a canonical form), are two completely different processes.
A good example that I like: the deployment of Russian phone numbers. For validation, it is sufficient (in the General case), so the string was 11 digits, and the first 7 of them, with arbitrary number and positions of other characters. If it's not, validation fails. The task of the General is to remove from it everything except numbers, so we can keep the database standardized msisdn.
Read to finally understand the difference: php.net/manual/ru/filter.filters.php
the
Well, what's wrong?
what a collection of validation errors is not an exception.
All of these wonderful
->getErrors()
is no exception. Consequently we are deprived of the many benefits:-
the
- Exceptions are typed. In frameworks, like the aforementioned, I can't create a hierarchy FormException --> FormFieldException --> FormPasswordFieldException --> FormPasswordFieldNotSameException. This is very important, especially with the release of PHP 7, which makes the type-hinting finally, the norm and the standard the
- Exception, encapsulating many useful. It's OOP! For example: on what page (URL) there is a validation error? Who is the user? What specific form field? What is the validation rule work? Finally, "let's give the translation of the message in Estonian". Could this all make a simple array of error messages? Of course not. (By the way, need to implement method __toString() and the exception template will act as a simple error message)
the
what to do?
Try to put the problem. What would you like to see in the code? Well, let's say, something like this:
Somewhere in the active code is:
the
try {
$user = new User;
$user- > fill($_POST);
$user->save();
redirect('hello.php');
catch (ValidationErrors as $e) {
$this->view->assign('errors', $e);
}
Somewhere in the template:
the
<?php foreach ($errors as $error): ?>
<div class="alert alert-danger"><?php echo $error->getMessage(); ?></div>
<?php endforeach; ?>
The essence of the proposed architectural pattern can be expressed very briefly: Multiscene. The exception, which is a collection of other exceptions.
How to achieve this? Fortunately, PHP allows us and not such stunts.
Convert the exception in a collection
All the fun is here!
Interface, which inherits all good for us interfaces to transform object to array:
the
A trait that implements this interface:
the
I personally would add another useful interface and its implementation trait, but it is, of course, quite unnecessary:
the
and finally, putting it all together:
the
the
interface IArrayAccess
extends \ArrayAccess, \Countable, \IteratorAggregate, \Serializable
{
}
A trait that implements this interface:
the
trait TArrayAccess
{
protected $storage = [];
protected function innerIsset($offset)
{
return array_key_exists($offset, $this->storage);
}
protected function innerGet($offset)
{
return isset($this- > storage[$offset]) ? $this- > storage[$offset] : null;
}
protected function innerSet($offset, $value)
{
if (" == $offset) {
if (empty($this- > storage)) {
$offset = 0;
} else {
$offset = max(array_keys($this->storage))+1;
}
}
$this- > storage[$offset] = $value;
}
protected function innerUnset($offset)
{
unset($this- > storage[$offset]);
}
public function offsetExists($offset)
{
return $this->innerIsset($offset);
}
public function offsetGet($offset)
{
return $this->innerGet($offset);
}
public function offsetSet($offset, $value)
{
$this->innerSet($offset, $value);
}
public function offsetUnset($offset)
{
$this->innerUnset($offset);
}
public function count()
{
return count($this->storage);
}
public function isEmpty()
{
return empty($this->storage);
}
}
// And so on. Carefully implement each interface of the composition IArrayAccess
// Here I allow myself only one liberty compared to vanilla arrays - note method innerIsset(), it returns true if a collection element exists but is null. IMHO, this is more correct behavior.
I personally would add another useful interface and its implementation trait, but it is, of course, quite unnecessary:
the
interface ICollection
{
public function add($value);
public function prepend($value);
public function append($value);
public function slice($offset, $length=null);
public function existsElement(array $attributes);
public function findAllByAttributes(array $attributes);
public function findByAttributes(array $attributes);
public function asort();
public function ksort();
public function uasort(callable $callback);
public function uksort(callable $callback);
public function natsort();
public function natcasesort();
public function sort(callable $callback);
public function map(callable $callback);
public function filter(callable $callback);
public function reduce($start, callable $callback);
public function collect($what);
public function group($by);
public function __call($method, array $params = []);
}
and finally, putting it all together:
the
class MultiException
extends \Exception
implements IArrayAccess
{
use TArrayAccess;
}
the
Simple example of application
Method populate the model data.
In the model validation rules are created. They are throwing exception whenever a value fails validation when you assign it to a field model. For example:
the
protected function validatePassword($value) {
if (strlen($value) < 3) {
throw new Exception('length of password');
}
...
return true;
}
Create magic setter which will automatically call the validator for the field. And at the same time to convert an exception to another type, containing not just the error message validation, but also the name of the field:
the
public function __set($key, $val) {
$validator = 'validate' . ucfirst($key);
if (method_exists($this, $validator)) {
try {
if ($this- > $validator($value)) {
parent::__set($key, $val);
}
} catch (Exception $e) {
throw new ModelColumnException($key, $e- > getMessage());
}
}
}
Create a method fill($data), which tries to populate the model with data and gently gather into one all the validation errors for individual fields:
the
public function fill($data) {
$errors = new Multiexception;
foreach ($data as $key => $val) {
try {
$this->$key = $val;
} catch (ModelColumnException $e) {
$errors[] = $e;
}
if (!$errors->isEmpty()) {
throw $errors;
}
}
In fact, everything. It is possible to apply. A bunch of advantages:
the
- Is an array of exceptions, so that we can at any time add a new exception or to remove already processed
- Is a class, so we build your class hierarchy the
- And finally, this is still the exception and so we have access to all of its standard properties and methods. Yes, and even getTrace()!
This exception, it means that you can catch in the right place the
This exception, so it after a certain phase of processing you can throw on
This object, so we can easily transfer anywhere the
the
in conclusion
Here's all of the small nuances, is a military code that I always use, and even teach their students to apply. Surprised that he has never seen before such a simple concept. If I first give you the ideas and code presented in this article are in the public domain. If not the first — sorry to the author of his mistake (s)
UPD on the results of the review
I thank all the commentators for their valuable thoughts and opinions.
The essence of the article is not validation. At all. Validation is just a bad calivornia example, I just failed to come up with the best.
The bottom line is very simple. PHP can be object that is and exception, and other exceptions at the same time. And this is useful.
Комментарии
Отправить комментарий