Skip to content

Commit 0099619

Browse files
author
Bizley
authored
Fix #19407: Fix yii\validators\UniqueValidator and yii\validators\ExistValidator to respect skipOnError option for target attributes
1 parent bba3806 commit 0099619

File tree

5 files changed

+74
-2
lines changed

5 files changed

+74
-2
lines changed

framework/CHANGELOG.md

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

framework/validators/ExistValidator.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,14 @@ private function checkTargetRelationExistence($model, $attribute)
153153
private function checkTargetAttributeExistence($model, $attribute)
154154
{
155155
$targetAttribute = $this->targetAttribute === null ? $attribute : $this->targetAttribute;
156+
if ($this->skipOnError) {
157+
foreach ((array)$targetAttribute as $k => $v) {
158+
if ($model->hasErrors(is_int($k) ? $v : $k)) {
159+
return;
160+
}
161+
}
162+
}
163+
156164
$params = $this->prepareConditions($targetAttribute, $model, $attribute);
157165
$conditions = [$this->targetAttributeJunction == 'or' ? 'or' : 'and'];
158166

framework/validators/UniqueValidator.php

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,9 +122,15 @@ public function init()
122122
*/
123123
public function validateAttribute($model, $attribute)
124124
{
125-
/* @var $targetClass ActiveRecordInterface */
126-
$targetClass = $this->getTargetClass($model);
127125
$targetAttribute = $this->targetAttribute === null ? $attribute : $this->targetAttribute;
126+
if ($this->skipOnError) {
127+
foreach ((array)$targetAttribute as $k => $v) {
128+
if ($model->hasErrors(is_int($k) ? $v : $k)) {
129+
return;
130+
}
131+
}
132+
}
133+
128134
$rawConditions = $this->prepareConditions($targetAttribute, $model, $attribute);
129135
$conditions = [$this->targetAttributeJunction === 'or' ? 'or' : 'and'];
130136

@@ -136,6 +142,8 @@ public function validateAttribute($model, $attribute)
136142
$conditions[] = [$key => $value];
137143
}
138144

145+
/* @var $targetClass ActiveRecordInterface */
146+
$targetClass = $this->getTargetClass($model);
139147
$db = $targetClass::getDb();
140148

141149
$modelExists = false;

tests/framework/validators/ExistValidatorTest.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use yii\base\Exception;
1111
use yii\validators\ExistValidator;
1212
use yiiunit\data\ar\ActiveRecord;
13+
use yiiunit\data\ar\Customer;
1314
use yiiunit\data\ar\Order;
1415
use yiiunit\data\ar\OrderItem;
1516
use yiiunit\data\validators\models\ValidatorTestMainModel;
@@ -268,4 +269,31 @@ public function testForceMaster()
268269

269270
ActiveRecord::$db = $this->getConnection();
270271
}
272+
273+
public function testSecondTargetAttributeWithError()
274+
{
275+
$validator = new ExistValidator(['targetAttribute' => ['email', 'name']]);
276+
$customer = new Customer();
277+
$customer->email = '[email protected]';
278+
$customer->name = 'user11111';
279+
280+
$validator->validateAttribute($customer, 'email');
281+
$this->assertTrue($customer->hasErrors('email'));
282+
283+
$customer->clearErrors();
284+
285+
$customer->addError('name', 'error');
286+
$validator->validateAttribute($customer, 'email');
287+
$this->assertFalse($customer->hasErrors('email')); // validator should be skipped
288+
289+
$validator = new ExistValidator([
290+
'targetAttribute' => ['email', 'name'],
291+
'skipOnError' => false,
292+
]);
293+
294+
$customer->clearErrors();
295+
$customer->addError('name', 'error');
296+
$validator->validateAttribute($customer, 'email');
297+
$this->assertTrue($customer->hasErrors('email')); // validator should not be skipped
298+
}
271299
}

tests/framework/validators/UniqueValidatorTest.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,33 @@ public function testForceMaster()
505505

506506
ActiveRecord::$db = $this->getConnection();
507507
}
508+
509+
public function testSecondTargetAttributeWithError()
510+
{
511+
$validator = new UniqueValidator(['targetAttribute' => ['email', 'name']]);
512+
$customer = new Customer();
513+
$customer->email = '[email protected]';
514+
$customer->name = 'user1';
515+
516+
$validator->validateAttribute($customer, 'email');
517+
$this->assertTrue($customer->hasErrors('email'));
518+
519+
$customer->clearErrors();
520+
521+
$customer->addError('name', 'error');
522+
$validator->validateAttribute($customer, 'email');
523+
$this->assertFalse($customer->hasErrors('email')); // validator should be skipped
524+
525+
$validator = new UniqueValidator([
526+
'targetAttribute' => ['email', 'name'],
527+
'skipOnError' => false,
528+
]);
529+
530+
$customer->clearErrors();
531+
$customer->addError('name', 'error');
532+
$validator->validateAttribute($customer, 'email');
533+
$this->assertTrue($customer->hasErrors('email')); // validator should not be skipped
534+
}
508535
}
509536

510537
class WithCustomer extends Customer {

0 commit comments

Comments
 (0)