Fix #19407: Fix yii\validators\UniqueValidator and yii\validators\ExistValidator to respect skipOnError option for target attributes

This commit is contained in:
Bizley
2022-07-29 08:47:33 +02:00
committed by GitHub
parent bba3806961
commit 009961963c
5 changed files with 74 additions and 2 deletions

View File

@ -30,6 +30,7 @@ Yii Framework 2 Change Log
- Bug #19368: Fix PHP 8.1 error when `$fileMimeType` is `null` in `yii\validators\FileValidator::validateMimeType()` (bizley) - Bug #19368: Fix PHP 8.1 error when `$fileMimeType` is `null` in `yii\validators\FileValidator::validateMimeType()` (bizley)
- Enh #19384: Normalize `setBodyParams()` and `getBodyParam()` in `yii\web\Request` (WinterSilence, albertborsos) - Enh #19384: Normalize `setBodyParams()` and `getBodyParam()` in `yii\web\Request` (WinterSilence, albertborsos)
- Bug #19386: Fix recursive calling `yii\helpers\BaseArrayHelper::htmlDecode()` (WinterSilence) - Bug #19386: Fix recursive calling `yii\helpers\BaseArrayHelper::htmlDecode()` (WinterSilence)
- Bug #19407: Fix `yii\validators\UniqueValidator` and `yii\validators\ExistValidator` to respect `skipOnError` option for target attributes (bizley)
- Bug #19418: Fix `yii\filters\auth\CompositeAuth` ignoring `only` and `except` options (lesha724) - Bug #19418: Fix `yii\filters\auth\CompositeAuth` ignoring `only` and `except` options (lesha724)
- Enh #19401: Delay `exit(1)` in `yii\base\ErrorHandler::handleFatalError` (arrilot) - Enh #19401: Delay `exit(1)` in `yii\base\ErrorHandler::handleFatalError` (arrilot)
- Bug #19402: Add shutdown event and fix working directory in `yii\base\ErrorHandler` (WinterSilence) - Bug #19402: Add shutdown event and fix working directory in `yii\base\ErrorHandler` (WinterSilence)

View File

@ -153,6 +153,14 @@ class ExistValidator extends Validator
private function checkTargetAttributeExistence($model, $attribute) private function checkTargetAttributeExistence($model, $attribute)
{ {
$targetAttribute = $this->targetAttribute === null ? $attribute : $this->targetAttribute; $targetAttribute = $this->targetAttribute === null ? $attribute : $this->targetAttribute;
if ($this->skipOnError) {
foreach ((array)$targetAttribute as $k => $v) {
if ($model->hasErrors(is_int($k) ? $v : $k)) {
return;
}
}
}
$params = $this->prepareConditions($targetAttribute, $model, $attribute); $params = $this->prepareConditions($targetAttribute, $model, $attribute);
$conditions = [$this->targetAttributeJunction == 'or' ? 'or' : 'and']; $conditions = [$this->targetAttributeJunction == 'or' ? 'or' : 'and'];

View File

@ -122,9 +122,15 @@ class UniqueValidator extends Validator
*/ */
public function validateAttribute($model, $attribute) public function validateAttribute($model, $attribute)
{ {
/* @var $targetClass ActiveRecordInterface */
$targetClass = $this->getTargetClass($model);
$targetAttribute = $this->targetAttribute === null ? $attribute : $this->targetAttribute; $targetAttribute = $this->targetAttribute === null ? $attribute : $this->targetAttribute;
if ($this->skipOnError) {
foreach ((array)$targetAttribute as $k => $v) {
if ($model->hasErrors(is_int($k) ? $v : $k)) {
return;
}
}
}
$rawConditions = $this->prepareConditions($targetAttribute, $model, $attribute); $rawConditions = $this->prepareConditions($targetAttribute, $model, $attribute);
$conditions = [$this->targetAttributeJunction === 'or' ? 'or' : 'and']; $conditions = [$this->targetAttributeJunction === 'or' ? 'or' : 'and'];
@ -136,6 +142,8 @@ class UniqueValidator extends Validator
$conditions[] = [$key => $value]; $conditions[] = [$key => $value];
} }
/* @var $targetClass ActiveRecordInterface */
$targetClass = $this->getTargetClass($model);
$db = $targetClass::getDb(); $db = $targetClass::getDb();
$modelExists = false; $modelExists = false;

View File

@ -10,6 +10,7 @@ namespace yiiunit\framework\validators;
use yii\base\Exception; use yii\base\Exception;
use yii\validators\ExistValidator; use yii\validators\ExistValidator;
use yiiunit\data\ar\ActiveRecord; use yiiunit\data\ar\ActiveRecord;
use yiiunit\data\ar\Customer;
use yiiunit\data\ar\Order; use yiiunit\data\ar\Order;
use yiiunit\data\ar\OrderItem; use yiiunit\data\ar\OrderItem;
use yiiunit\data\validators\models\ValidatorTestMainModel; use yiiunit\data\validators\models\ValidatorTestMainModel;
@ -268,4 +269,31 @@ abstract class ExistValidatorTest extends DatabaseTestCase
ActiveRecord::$db = $this->getConnection(); ActiveRecord::$db = $this->getConnection();
} }
public function testSecondTargetAttributeWithError()
{
$validator = new ExistValidator(['targetAttribute' => ['email', 'name']]);
$customer = new Customer();
$customer->email = 'user11111@example.com';
$customer->name = 'user11111';
$validator->validateAttribute($customer, 'email');
$this->assertTrue($customer->hasErrors('email'));
$customer->clearErrors();
$customer->addError('name', 'error');
$validator->validateAttribute($customer, 'email');
$this->assertFalse($customer->hasErrors('email')); // validator should be skipped
$validator = new ExistValidator([
'targetAttribute' => ['email', 'name'],
'skipOnError' => false,
]);
$customer->clearErrors();
$customer->addError('name', 'error');
$validator->validateAttribute($customer, 'email');
$this->assertTrue($customer->hasErrors('email')); // validator should not be skipped
}
} }

View File

@ -505,6 +505,33 @@ abstract class UniqueValidatorTest extends DatabaseTestCase
ActiveRecord::$db = $this->getConnection(); ActiveRecord::$db = $this->getConnection();
} }
public function testSecondTargetAttributeWithError()
{
$validator = new UniqueValidator(['targetAttribute' => ['email', 'name']]);
$customer = new Customer();
$customer->email = 'user1@example.com';
$customer->name = 'user1';
$validator->validateAttribute($customer, 'email');
$this->assertTrue($customer->hasErrors('email'));
$customer->clearErrors();
$customer->addError('name', 'error');
$validator->validateAttribute($customer, 'email');
$this->assertFalse($customer->hasErrors('email')); // validator should be skipped
$validator = new UniqueValidator([
'targetAttribute' => ['email', 'name'],
'skipOnError' => false,
]);
$customer->clearErrors();
$customer->addError('name', 'error');
$validator->validateAttribute($customer, 'email');
$this->assertTrue($customer->hasErrors('email')); // validator should not be skipped
}
} }
class WithCustomer extends Customer { class WithCustomer extends Customer {