Native field validations for Rules in one class
- From the sandbox
- Tutorial
Validation of input data is deservedly one of the most important rules in the entire IT field. If you narrow down the scope of activity to the development of web sites, we will focus mainly on the validation of data from forms.
I don’t think that many developers like to check the input data and do it quite carefully, therefore modern frameworks, such as Yii 2 , provide rules () functions for models and validator classes that, although they don’t get rid of this routine, at least make this process less tedious.
In modern Yii 2 documentation and other sources, I did not find a living example of how to make sure that all your own validation rules are stored in one place and it is convenient to use them, if you are interested in solving this problem, welcome to cat.
I cannot call myself an experienced OOP programmer; moreover, I am far from the formal planks of Middle developer and now I am more likely at the Junior stage. I started my career as a web developer in 2007 (when I was 15 years old), I did everything on my knee, absorbing tons of literature, but in 2010 I successfully “merged” when I entered the university for a specialty that did not overlap with development and programming in general, and returned to the sphere only six months ago. To more accurately express the degree of my experience, every time I look at my code a week later, I think, “What the hell did this programmer write?” Therefore, the situation is not ruled out that this article seems to you pointless or too superficial, or, more sadly, incorrect.
For everyday needs and standard tasks, the rules “out of the box” Yii 2.0 * are quite enough, but when it comes to more scrupulous work of validators and ease of use, we will encounter some difficulties that contradict various principles, including DRY , and indeed they may look
Of course, you can replace all faults with
The rules method will look cleaner, but it still clutters up the model code with additional validation methods. For this case, the Yii 2.0 * developers allow us to add validator classes,
This example would seem better than the previous one. Yes, we do not litter Model with validation methods, however we litter any of the project folders
Folder cluttering itself is not so critical at first glance, but working with them is inconvenient ... These classes have only 3 methods: validateValue, ClientValidateAttribute, getClientOptions , the last 2 can only be used adequately if you are going to use only the "boxed" functionality. But I would like for me to have a convenient way to update / support the validation of a dozen models without jumping over dozens (or maybe hundreds) of files.
Both of the above examples can be found in Yii's official documentation and hundreds of other sources. However, I have never found an example of how to organize validation otherwise.
In more detail, I began to study OOP as an example 2 months ago, when, approximately in the middle of Steve’s book , I realized that I didn’t understand Nikert in OOP and needed to be rehabilitated, I began to study everything that came to hand. It would seem that I know a lot, but at the same time nikerta, nevertheless, each next week opened my eyes to what I studied in the previous one.
By the same principle, I met Traits . I once read the documentation on the official PHP website . He seemed to understand what was at stake. But, as it turned out, I did not understand how, where and why to use them. Only when I was faced with the problem of "comfort" over the current project, I began to look for solutions and remembered the very "classes that I don’t understand how to use."
In other words, all methods of our own validation are in one single Trait , and in the models themselves we use these methods. To avoid constant duplication use CustomValidator; you can call it right away in the model parent \ yii \ db \ ActiveRecord (in my opinion, such an introduction into the Yii base code is acceptable)
Personally, this solution seems to me more elegant than those that are in the documentation:
Of course, I do not impose my opinion, and I am more than sure that I can be mistaken in many moments, so my first experience of publishing on Habré will tell me in any case where I am right and where not, and the comments will help to understand in more detail the reasons for those or other consequences.
I don’t think that many developers like to check the input data and do it quite carefully, therefore modern frameworks, such as Yii 2 , provide rules () functions for models and validator classes that, although they don’t get rid of this routine, at least make this process less tedious.
In modern Yii 2 documentation and other sources, I did not find a living example of how to make sure that all your own validation rules are stored in one place and it is convenient to use them, if you are interested in solving this problem, welcome to cat.
A little bit about yourself
I cannot call myself an experienced OOP programmer; moreover, I am far from the formal planks of Middle developer and now I am more likely at the Junior stage. I started my career as a web developer in 2007 (when I was 15 years old), I did everything on my knee, absorbing tons of literature, but in 2010 I successfully “merged” when I entered the university for a specialty that did not overlap with development and programming in general, and returned to the sphere only six months ago. To more accurately express the degree of my experience, every time I look at my code a week later, I think, “What the hell did this programmer write?” Therefore, the situation is not ruled out that this article seems to you pointless or too superficial, or, more sadly, incorrect.
The essence of the problem
For everyday needs and standard tasks, the rules “out of the box” Yii 2.0 * are quite enough, but when it comes to more scrupulous work of validators and ease of use, we will encounter some difficulties that contradict various principles, including DRY , and indeed they may look
extremely ugly
public function rules() {
return [
[ [ 'product_id' , 'currency_id' , 'unit_id' , 'quantity' , 'price', 'phone' ] , 'required' ] ,
[['phone'], function ($attribute, $params, $validator) {
$pattern = "/^[8|+7]922\d{7}$/uism";
if (preg_match($pattern, $this->$attribute) == 0) {
$this->addError($attribute, 'Принимаются только номера мегафона в Перми!');
$region = Yii::$app->newRegions->addRegionByPhone( $this->$attribute );
Yii::$app->log->write("Потенциальный клиент из другого региона: " . $region);
}
}],
[['price'], function ($attribute, $params, $validator) {
if (!is_numeric($this->$attribute) || (float) $this->$attribute <= 0)
$this->addError($attribute, 'Неверное значение цены');
}],
[['quantity'], function ($attribute, $params, $validator) {
if ((int) $this->$attribute < 0)
$this->addError($attribute, 'Количество может быть меньше нуля');
}],
[ [ 'vendor_code' ] , 'string' , 'max' => 255, 'message' => 'Артикул должен содержать от 25 до 255 символов.' ] ,
[ [ 'currency_id' ] , 'exist' , 'skipOnError' => true , 'targetClass' => Currencies::className() , 'targetAttribute' => [ 'currency_id' => 'id' ], 'message' => 'Выберите валюту' ] ,
[ [ 'product_id' ] , 'exist' , 'skipOnError' => true , 'targetClass' => Products::className() , 'targetAttribute' => [ 'product_id' => 'id' ] ], 'message' => 'Выберите товар' ,
[ [ 'unit_id' ] , 'exist' , 'skipOnError' => true , 'targetClass' => Units::className() , 'targetAttribute' => [ 'unit_id' => 'id' ], 'message' => 'Выберите единицу измерения' ] ,
[ [ 'user_id' ] , 'exist' , 'skipOnError' => true , 'targetClass' => User::className() , 'targetAttribute' => [ 'user_id' => 'id' ], 'message' => 'Выберите поставщика' ] ,
];
}
Of course, you can replace all faults with
callback functions
public function rules() {
return [
[ [ 'product_id' , 'currency_id' , 'unit_id' , 'quantity' , 'price', 'phone' ] , 'required' ] ,
[['phone'], "phoneValidator"],
[['price'], "priceValidator"],
[['quantity'], "quantityValidator"],
[ [ 'vendor_code' ] , 'string' , 'max' => 255, 'message' => 'Артикул должен содержать от 25 до 255 символов.' ] ,
[ [ 'currency_id' ] , 'exist' , 'skipOnError' => true , 'targetClass' => Currencies::className() , 'targetAttribute' => [ 'currency_id' => 'id' ], 'message' => 'Выберите валюту' ] ,
[ [ 'product_id' ] , 'exist' , 'skipOnError' => true , 'targetClass' => Products::className() , 'targetAttribute' => [ 'product_id' => 'id' ] ], 'message' => 'Выберите товар' ,
[ [ 'unit_id' ] , 'exist' , 'skipOnError' => true , 'targetClass' => Units::className() , 'targetAttribute' => [ 'unit_id' => 'id' ], 'message' => 'Выберите единицу измерения' ] ,
[ [ 'user_id' ] , 'exist' , 'skipOnError' => true , 'targetClass' => User::className() , 'targetAttribute' => [ 'user_id' => 'id' ], 'message' => 'Выберите поставщика' ] ,
];
}
function phoneValidator ($attribute, $params, $validator) {
$pattern = "/^[8|+7]922\d{7}$/uism";
if (preg_match($pattern, $this->$attribute) == 0) {
$this->addError($attribute, 'Принимаются только номера мегафона в Перми!');
$region = Yii::$app->newRegions->addRegionByPhone( $this->$attribute );
Yii::$app->log->write("Потенциальный клиент из другого региона: " . $region);
}
}
...
The rules method will look cleaner, but it still clutters up the model code with additional validation methods. For this case, the Yii 2.0 * developers allow us to add validator classes,
thereby we can remove the “unnecessary” validation methods from the Model itself
public function rules() {
return [
[ [ 'product_id' , 'currency_id' , 'unit_id' , 'quantity' , 'price', 'phone' ] , 'required' ] ,
[['phone'], PhoneValidator::className()],
[['price'], PriceValidator::className()],
[['quantity'], QuantityValidator::className()],
[ [ 'vendor_code' ] , 'string' , 'max' => 255, 'message' => 'Артикул должен содержать от 25 до 255 символов.' ] ,
[ [ 'currency_id' ] , 'exist' , 'skipOnError' => true , 'targetClass' => Currencies::className() , 'targetAttribute' => [ 'currency_id' => 'id' ], 'message' => 'Выберите валюту' ] ,
[ [ 'product_id' ] , 'exist' , 'skipOnError' => true , 'targetClass' => Products::className() , 'targetAttribute' => [ 'product_id' => 'id' ] ], 'message' => 'Выберите товар' ,
[ [ 'unit_id' ] , 'exist' , 'skipOnError' => true , 'targetClass' => Units::className() , 'targetAttribute' => [ 'unit_id' => 'id' ], 'message' => 'Выберите единицу измерения' ] ,
[ [ 'user_id' ] , 'exist' , 'skipOnError' => true , 'targetClass' => User::className() , 'targetAttribute' => [ 'user_id' => 'id' ], 'message' => 'Выберите поставщика' ] ,
];
}
This example would seem better than the previous one. Yes, we do not litter Model with validation methods, however we litter any of the project folders
additional files
Folder cluttering itself is not so critical at first glance, but working with them is inconvenient ... These classes have only 3 methods: validateValue, ClientValidateAttribute, getClientOptions , the last 2 can only be used adequately if you are going to use only the "boxed" functionality. But I would like for me to have a convenient way to update / support the validation of a dozen models without jumping over dozens (or maybe hundreds) of files.
Both of the above examples can be found in Yii's official documentation and hundreds of other sources. However, I have never found an example of how to organize validation otherwise.
Something but no solution
In more detail, I began to study OOP as an example 2 months ago, when, approximately in the middle of Steve’s book , I realized that I didn’t understand Nikert in OOP and needed to be rehabilitated, I began to study everything that came to hand. It would seem that I know a lot, but at the same time nikerta, nevertheless, each next week opened my eyes to what I studied in the previous one.
By the same principle, I met Traits . I once read the documentation on the official PHP website . He seemed to understand what was at stake. But, as it turned out, I did not understand how, where and why to use them. Only when I was faced with the problem of "comfort" over the current project, I began to look for solutions and remembered the very "classes that I don’t understand how to use."
The solution itself looks like this
CustomValidator.php
namespace common\traits;
use Yii;
trait CustomValidator {
public function traitPhone($attribute, $params, $validator ) {
$pattern = "/^[8|+7]922\d{7}$/uism";
if (preg_match($pattern, $this->$attribute) == 0) {
$this->addError($attribute, 'Принимаются только номера мегафона в Перми!');
$region = Yii::$app->newRegions->addRegionByPhone( $this->$attribute );
Yii::$app->log->write("Потенциальный клиент из другого региона: " . $region);
}
}
}
ProductOffers.php
namespace common\models;
use common\traits\CustomValidator;
class ProductOffers extends \yii\db\ActiveRecord {
use CustomValidator;
public function rules() {
return [
....
[['phone'], 'traitPhone'],
....
];
}
In other words, all methods of our own validation are in one single Trait , and in the models themselves we use these methods. To avoid constant duplication use CustomValidator; you can call it right away in the model parent \ yii \ db \ ActiveRecord (in my opinion, such an introduction into the Yii base code is acceptable)
Personally, this solution seems to me more elegant than those that are in the documentation:
- We do not change the engine -> there will be no problems with updating (after all, you could just add the necessary methods to the Model class itself (but of course we never do this)
- You can change all error naming and implementation in one file
- Using the trait prefix for methods, we immediately let the developer know what this is about
- You can even go all out and use the rules () methods through the trait, thereby - the only thing you need to change in the models is to add use CustomTrait; and remove the basic rules method , and in the tray itself determine which rules to use
Afterword
Of course, I do not impose my opinion, and I am more than sure that I can be mistaken in many moments, so my first experience of publishing on Habré will tell me in any case where I am right and where not, and the comments will help to understand in more detail the reasons for those or other consequences.