There is a form ( Model ) that has more than 1 validator per field. In theory, the validate() method should check with all validators and enter all the errors found in the _errors property, and not stop after the first error. This assumption logically follows from the fact that the getErrors() documentation shows an example, where a two-dimensional array is returned and the username attribute in the example has 2 errors at once. To return only the first values, there is the getFirstErrors() method, which just returns a one-dimensional array of one error per attribute. But it is not clear when all the same, getErrors() returns more than 1 error per attribute and how to achieve this if necessary?

Here is a test case. Create a new project Yii2 on the advanced template with the command composer create-project --prefer-dist --stability=dev yiisoft/yii2-app-advanced testProject . Initialize with the init command. Add 2 files to it.

Test form in common/models/TestForm.php

 <?php namespace common\models; use yii\base\Model; class TestForm extends Model { public $fullName; /** * @inheritdoc */ public function rules() { return [ ['fullName', 'required'], // ПолноС имя Π½Π΅ ΠΌΠΎΠΆΠ΅Ρ‚ Π±Ρ‹Ρ‚ΡŒ пустым ['fullName', 'string', 'min' => 5, 'max' => 100], // ПолноС имя Π΄ΠΎΠ»ΠΆΠ½ΠΎ Π±Ρ‹Ρ‚ΡŒ строкой ΠΎΡ‚ 5 Π΄ΠΎ 100 символов ['fullName', 'match', 'pattern' => '/^\w+(?:\s+\w+)+$/u'], // ПолноС имя Π΄ΠΎΠ»ΠΆΠ½ΠΎ ΡΠΎΡΡ‚ΠΎΡΡ‚ΡŒ ΠΈΠ· 2+ слов ]; } } 

Test console controller to check in console/controllers/TestFormController.php

 <?php namespace console\controllers; use common\models\TestForm; use Yii; use yii\console\Controller; class TestFormController extends Controller { public function actionIndex($fullName) { $form = new TestForm(); $form->fullName = $fullName; if ($form->validate()) { echo "Form validated successful\n"; } else { echo "Form validated with errors:\n\n"; var_dump($form->getErrors()); } } } 

Perform a test in the console:

 yii test-form "asd" 

We get the result:

 Form validated with errors: array(1) { ["fullName"]=> array(1) { [0]=> string(47) "Full Name should contain at least 5 characters." } } 

Although the passed "asd" parameter does not satisfy 2 validators at once: "string" and "match". In the documentation, I did not find anything on the fly about it.

  • There the documentation says that you can pass the second parameter to the validate method, which is responsible for clearing the previous errors. Try this: $form->validate(null, false) . - Razzwan
  • @Razzwan, I tried it - it did not help. The result is the same, but I spent time studying the Yii code and figured out. The second parameter is responsible for clearing errors left over from previous calls to $form->validate() or $form->addError() . Now I will write the answer to the question. - gugglegum

1 answer 1

The problem lies in the fact that the validator does not validate attributes for which there are already accumulated errors. This is implemented in the yii\validators\Validator::validateAttributes() method:

 foreach ($attributes as $attribute) { $skip = $this->skipOnError && $model->hasErrors($attribute) || $this->skipOnEmpty && $this->isEmpty($model->$attribute); if (!$skip) { if ($this->when === null || call_user_func($this->when, $model, $attribute)) { $this->validateAttribute($model, $attribute); } } } 

As can be seen from the code, attribute verification is not performed if the $skip variable is true . And it is equal to true in 2 cases: 1) if the validator's skipOnError property is true and the current attribute has errors; 2) if the skipOnEmpty property is true and the current attribute is empty. We have the first condition. The skipOnError property is true by default. Therefore, when an error occurs on the first rule, the other rules are no longer checked. To solve this problem, add the parameter 'skipOnError' => false to the second validator rules in the second validator:

 public function rules() { return [ ['fullName', 'required'], // ПолноС имя Π½Π΅ ΠΌΠΎΠΆΠ΅Ρ‚ Π±Ρ‹Ρ‚ΡŒ пустым ['fullName', 'string', 'min' => 5, 'max' => 100], // ПолноС имя Π΄ΠΎΠ»ΠΆΠ½ΠΎ Π±Ρ‹Ρ‚ΡŒ строкой ΠΎΡ‚ 5 Π΄ΠΎ 100 символов ['fullName', 'match', 'pattern' => '/^\w+(?:\s+\w+)+$/u', 'skipOnError' => false], // ПолноС имя Π΄ΠΎΠ»ΠΆΠ½ΠΎ ΡΠΎΡΡ‚ΠΎΡΡ‚ΡŒ ΠΈΠ· 2+ слов ]; } 

Although, personally, it seems to me not entirely logical that this behavior is strictly defined in the model. This should probably be a validate() method option, as I may have several places to use this model. In one place I need only the first mistakes, in another place I need everything. What do I need to create two different models for this? This makes no sense. However, in Yii, such inconsistencies are common. Sad Html::beginTag('form') or bare HTML. Yes, and it’s not clear what the Request has to do with the form rendering, sorry for this lyrical digression.