Validation of input data is deservedly one of the most important rules in the entire IT field. If you narrow the scope of activities before the development of websites, 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 thoroughly, so modern frameworks such as
Yii 2 provide
rules () for models and Validator classes, which, although they don’t eliminate this routine, at a minimum, make this process less tedious.
')
In modern
Yii 2 documentation and other sources, I did not find a living example of how to make all of my own validation rules be kept in one place and it would be convenient to use them if you are interested in solving this problem, welcome under cat.
A little bit about yourself
I can’t call myself a sophisticated programmer in OOP; moreover, I’m far from the formal levels of Middle developer and now I’ll rather be at the Junior stage. I started my path as a web developer in 2007 (I was 15 at the time), 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 intersect with development and programming as a whole and returned to the field only six months ago. In order 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 excluded that you find this article meaningless or too superficial, or more sadly incorrect.
The essence of the problem
For everyday needs and standard tasks out-of-the-box,
Yii 2.0 * is enough, but when it comes to more scrupulous work of validators and usability, we will encounter some difficulties that contradict various principles, including
DRY , and in general they can look like
extremely uglypublic 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, all closures can be replaced by
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 the model code with additional validation methods. For this case, the developers of
Yii 2.0 * 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 the Model with validation methods, however we litter any of the project’s folders.
In itself, “cluttering up” folders 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 be adequately used only if you are going to use only the “boxed” functionality. But after all, I would like for me to have a convenient way to update / maintain the validation of a dozen models, without jumping on dozens (and maybe hundreds) of files.
Both of the above examples can be found in the official documentation of Yii and hundreds of other sources. However, I have nowhere found an example of how to organize validation differently.
Any, but still decision
In more detail, I began to study the OOP example 2 months ago, when around the middle of the book of
Steve, I realized that I don’t understand anything in the PLO and need to be rehabilitated, I began to study everything that comes handy. It would seem that I know a lot, but at the same time niche, nevertheless, each following week opened my eyes to what I had studied in the previous one.
By the same principle, I became acquainted with the
Traits . I once read the documentation on the
official PHP site . It seems to understand what is at stake. But, as it turned out, I did not understand how, where and why to use them. Only when I encountered the problem of “comfort” over the current project, I began to look for solutions and remembered those 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 the methods of our own validation are located in one single
Trait , and we use these methods in the models themselves. To avoid permanent duplication
use CustomValidator; it is possible to call it immediately in the parent of the models
\ yii \ db \ ActiveRecord (IMHO such an introduction to the Yii base code is valid)
Personally, I think this decision is more elegant than those 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 the methods, we immediately let the developer know what it is about.
- You can even go to all serious and use the rules () methods through the treyt, thus - the only thing that needs to be changed in the models is to add use CustomTrait; and remove the basic rules method, and in the third place, 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 points, so my first experience of publishing on Habré will tell me in any case where I am right and where it is not, and the comments will help to understand in more detail the reasons for those or other consequences.