mirror of
https://github.com/yiisoft/yii2.git
synced 2025-11-29 13:57:50 +08:00
Fixes #797: Added support for validating multiple columns by UniqueValidator and ExistValidator
This commit is contained in:
@@ -16,6 +16,7 @@ Yii Framework 2 Change Log
|
||||
- Bug: Fixed incorrect event name for `yii\jui\Spinner` (samdark)
|
||||
- Bug: Json::encode() did not handle objects that implement JsonSerializable interface correctly (cebe)
|
||||
- Bug: Fixed issue with tabular input on ActiveField::radio() and ActiveField::checkbox() (jom)
|
||||
- Enh #797: Added support for validating multiple columns by `UniqueValidator` and `ExistValidator` (qiangxue)
|
||||
- Enh #1293: Replaced Console::showProgress() with a better approach. See Console::startProgress() for details (cebe)
|
||||
- Enh #1406: DB Schema support for Oracle Database (p0larbeer, qiangxue)
|
||||
- Enh #1437: Added ListView::viewParams (qiangxue)
|
||||
|
||||
@@ -30,10 +30,25 @@ class ExistValidator extends Validator
|
||||
*/
|
||||
public $className;
|
||||
/**
|
||||
* @var string the yii\db\ActiveRecord class attribute name that should be
|
||||
* @var string|array the ActiveRecord class attribute name that should be
|
||||
* used to look for the attribute value being validated. Defaults to null,
|
||||
* meaning using the name of the attribute being validated.
|
||||
* @see className
|
||||
* meaning using the name of the attribute being validated. Use a string
|
||||
* to specify the attribute that is different from the attribute being validated
|
||||
* (often used together with [[className]]). Use an array to validate the existence about
|
||||
* multiple columns. For example,
|
||||
*
|
||||
* ```php
|
||||
* // a1 needs to exist
|
||||
* array('a1', 'exist')
|
||||
* // a1 needs to exist, but its value will use a2 to check for the existence
|
||||
* array('a1', 'exist', 'attributeName' => 'a2')
|
||||
* // a1 and a2 need to exist together, and they both will receive error message
|
||||
* array('a1, a2', 'exist', 'attributeName' => array('a1', 'a2'))
|
||||
* // a1 and a2 need to exist together, only a1 will receive error message
|
||||
* array('a1', 'exist', 'attributeName' => array('a1', 'a2'))
|
||||
* // a1 and a2 need to exist together, a2 will take value 10, only a1 will receive error message
|
||||
* array('a1', 'exist', 'attributeName' => array('a1', 'a2' => 10))
|
||||
* ```
|
||||
*/
|
||||
public $attributeName;
|
||||
|
||||
@@ -64,9 +79,7 @@ class ExistValidator extends Validator
|
||||
/** @var \yii\db\ActiveRecordInterface $className */
|
||||
$className = $this->className === null ? get_class($object) : $this->className;
|
||||
$attributeName = $this->attributeName === null ? $attribute : $this->attributeName;
|
||||
$query = $className::find();
|
||||
$query->where([$attributeName => $value]);
|
||||
if (!$query->exists()) {
|
||||
if (!$this->exists($className, $attributeName, $object, $value)) {
|
||||
$this->addError($object, $attribute, $this->message);
|
||||
}
|
||||
}
|
||||
@@ -85,10 +98,33 @@ class ExistValidator extends Validator
|
||||
if ($this->attributeName === null) {
|
||||
throw new InvalidConfigException('The "attributeName" property must be set.');
|
||||
}
|
||||
return $this->exists($this->className, $this->attributeName, null, $value) ? null : [$this->message, []];
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs existence check.
|
||||
* @param string $className the AR class name to be checked against
|
||||
* @param string|array $attributeName the attribute(s) to be checked
|
||||
* @param \yii\db\ActiveRecordInterface $object the object whose value is being validated
|
||||
* @param mixed $value the attribute value currently being validated
|
||||
* @return boolean whether the data being validated exists in the database already
|
||||
*/
|
||||
protected function exists($className, $attributeName, $object, $value)
|
||||
{
|
||||
/** @var \yii\db\ActiveRecordInterface $className */
|
||||
$className = $this->className;
|
||||
$query = $className::find();
|
||||
$query->where([$this->attributeName => $value]);
|
||||
return $query->exists() ? null : [$this->message, []];
|
||||
if (is_array($attributeName)) {
|
||||
$params = [];
|
||||
foreach ($attributeName as $k => $v) {
|
||||
if (is_integer($k)) {
|
||||
$params[$v] = $this->className === null && $object !== null ? $object->$v : $value;
|
||||
} else {
|
||||
$params[$k] = $v;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$params = [$attributeName => $value];
|
||||
}
|
||||
return $query->where($params)->exists();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,9 +26,25 @@ class UniqueValidator extends Validator
|
||||
*/
|
||||
public $className;
|
||||
/**
|
||||
* @var string the ActiveRecord class attribute name that should be
|
||||
* @var string|array the ActiveRecord class attribute name that should be
|
||||
* used to look for the attribute value being validated. Defaults to null,
|
||||
* meaning using the name of the attribute being validated.
|
||||
* meaning using the name of the attribute being validated. Use a string
|
||||
* to specify the attribute that is different from the attribute being validated
|
||||
* (often used together with [[className]]). Use an array to validate uniqueness about
|
||||
* multiple columns. For example,
|
||||
*
|
||||
* ```php
|
||||
* // a1 needs to be unique
|
||||
* array('a1', 'unique')
|
||||
* // a1 needs to be unique, but its value will use a2 to check for the uniqueness
|
||||
* array('a1', 'unique', 'attributeName' => 'a2')
|
||||
* // a1 and a2 need to unique together, and they both will receive error message
|
||||
* array('a1, a2', 'unique', 'attributeName' => array('a1', 'a2'))
|
||||
* // a1 and a2 need to unique together, only a1 will receive error message
|
||||
* array('a1', 'unique', 'attributeName' => array('a1', 'a2'))
|
||||
* // a1 and a2 need to unique together, a2 will take value 10, only a1 will receive error message
|
||||
* array('a1', 'unique', 'attributeName' => array('a1', 'a2' => 10))
|
||||
* ```
|
||||
*/
|
||||
public $attributeName;
|
||||
|
||||
@@ -60,7 +76,20 @@ class UniqueValidator extends Validator
|
||||
$attributeName = $this->attributeName === null ? $attribute : $this->attributeName;
|
||||
|
||||
$query = $className::find();
|
||||
$query->where([$attributeName => $value]);
|
||||
|
||||
if (is_array($attributeName)) {
|
||||
$params = [];
|
||||
foreach ($attributeName as $k => $v) {
|
||||
if (is_integer($k)) {
|
||||
$params[$v] = $this->className === null ? $object->$v : $value;
|
||||
} else {
|
||||
$params[$k] = $v;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$params = [$attributeName => $value];
|
||||
}
|
||||
$query->where($params);
|
||||
|
||||
if (!$object instanceof ActiveRecordInterface || $object->getIsNewRecord()) {
|
||||
// if current $object isn't in the database yet then it's OK just to call exists()
|
||||
@@ -71,7 +100,11 @@ class UniqueValidator extends Validator
|
||||
$objects = $query->limit(2)->all();
|
||||
$n = count($objects);
|
||||
if ($n === 1) {
|
||||
if (in_array($attributeName, $className::primaryKey())) {
|
||||
$keys = array_keys($params);
|
||||
$pks = $className::primaryKey();
|
||||
sort($keys);
|
||||
sort($pks);
|
||||
if ($keys === $pks) {
|
||||
// primary key is modified and not unique
|
||||
$exists = $object->getOldPrimaryKey() != $object->getPrimaryKey();
|
||||
} else {
|
||||
|
||||
@@ -7,6 +7,8 @@ use Yii;
|
||||
use yii\base\Exception;
|
||||
use yii\validators\ExistValidator;
|
||||
use yiiunit\data\ar\ActiveRecord;
|
||||
use yiiunit\data\ar\Order;
|
||||
use yiiunit\data\ar\OrderItem;
|
||||
use yiiunit\data\validators\models\ValidatorTestMainModel;
|
||||
use yiiunit\data\validators\models\ValidatorTestRefModel;
|
||||
use yiiunit\framework\db\DatabaseTestCase;
|
||||
@@ -92,4 +94,44 @@ class ExistValidatorTest extends DatabaseTestCase
|
||||
$val->validateAttribute($m, 'test_val');
|
||||
$this->assertTrue($m->hasErrors('test_val'));
|
||||
}
|
||||
|
||||
public function testValidateCompositeKeys()
|
||||
{
|
||||
$val = new ExistValidator([
|
||||
'className' => OrderItem::className(),
|
||||
'attributeName' => ['order_id', 'item_id'],
|
||||
]);
|
||||
// validate old record
|
||||
$m = OrderItem::find(['order_id' => 1, 'item_id' => 2]);
|
||||
$val->validateAttribute($m, 'order_id');
|
||||
$this->assertFalse($m->hasErrors('order_id'));
|
||||
|
||||
// validate new record
|
||||
$m = new OrderItem(['order_id' => 1, 'item_id' => 2]);
|
||||
$val->validateAttribute($m, 'order_id');
|
||||
$this->assertFalse($m->hasErrors('order_id'));
|
||||
$m = new OrderItem(['order_id' => 10, 'item_id' => 2]);
|
||||
$val->validateAttribute($m, 'order_id');
|
||||
$this->assertTrue($m->hasErrors('order_id'));
|
||||
|
||||
$val = new ExistValidator([
|
||||
'className' => OrderItem::className(),
|
||||
'attributeName' => ['order_id', 'item_id' => 2],
|
||||
]);
|
||||
// validate old record
|
||||
$m = Order::find(1);
|
||||
$val->validateAttribute($m, 'id');
|
||||
$this->assertFalse($m->hasErrors('id'));
|
||||
$m = Order::find(1);
|
||||
$m->id = 10;
|
||||
$val->validateAttribute($m, 'id');
|
||||
$this->assertTrue($m->hasErrors('id'));
|
||||
|
||||
$m = new Order(['id' => 1]);
|
||||
$val->validateAttribute($m, 'id');
|
||||
$this->assertFalse($m->hasErrors('id'));
|
||||
$m = new Order(['id' => 10]);
|
||||
$val->validateAttribute($m, 'id');
|
||||
$this->assertTrue($m->hasErrors('id'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ namespace yiiunit\framework\validators;
|
||||
use yii\validators\UniqueValidator;
|
||||
use Yii;
|
||||
use yiiunit\data\ar\ActiveRecord;
|
||||
use yiiunit\data\ar\Order;
|
||||
use yiiunit\data\ar\OrderItem;
|
||||
use yiiunit\data\validators\models\FakedValidationModel;
|
||||
use yiiunit\data\validators\models\ValidatorTestMainModel;
|
||||
use yiiunit\data\validators\models\ValidatorTestRefModel;
|
||||
@@ -85,4 +87,49 @@ class UniqueValidatorTest extends DatabaseTestCase
|
||||
$m = new ValidatorTestMainModel();
|
||||
$val->validateAttribute($m, 'testMainVal');
|
||||
}
|
||||
|
||||
public function testValidateCompositeKeys()
|
||||
{
|
||||
$val = new UniqueValidator([
|
||||
'className' => OrderItem::className(),
|
||||
'attributeName' => ['order_id', 'item_id'],
|
||||
]);
|
||||
// validate old record
|
||||
$m = OrderItem::find(['order_id' => 1, 'item_id' => 2]);
|
||||
$val->validateAttribute($m, 'order_id');
|
||||
$this->assertFalse($m->hasErrors('order_id'));
|
||||
$m->item_id = 1;
|
||||
$val->validateAttribute($m, 'order_id');
|
||||
$this->assertTrue($m->hasErrors('order_id'));
|
||||
|
||||
// validate new record
|
||||
$m = new OrderItem(['order_id' => 1, 'item_id' => 2]);
|
||||
$val->validateAttribute($m, 'order_id');
|
||||
$this->assertTrue($m->hasErrors('order_id'));
|
||||
$m = new OrderItem(['order_id' => 10, 'item_id' => 2]);
|
||||
$val->validateAttribute($m, 'order_id');
|
||||
$this->assertFalse($m->hasErrors('order_id'));
|
||||
|
||||
$val = new UniqueValidator([
|
||||
'className' => OrderItem::className(),
|
||||
'attributeName' => ['order_id', 'item_id' => 2],
|
||||
]);
|
||||
// validate old record
|
||||
$m = Order::find(1);
|
||||
$val->validateAttribute($m, 'id');
|
||||
$this->assertFalse($m->hasErrors('id'));
|
||||
$m->id = 2;
|
||||
$val->validateAttribute($m, 'id');
|
||||
$this->assertFalse($m->hasErrors('id'));
|
||||
$m->id = 3;
|
||||
$val->validateAttribute($m, 'id');
|
||||
$this->assertTrue($m->hasErrors('id'));
|
||||
|
||||
$m = new Order(['id' => 1]);
|
||||
$val->validateAttribute($m, 'id');
|
||||
$this->assertTrue($m->hasErrors('id'));
|
||||
$m = new Order(['id' => 10]);
|
||||
$val->validateAttribute($m, 'id');
|
||||
$this->assertFalse($m->hasErrors('id'));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user