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: Fixed incorrect event name for `yii\jui\Spinner` (samdark)
|
||||||
- Bug: Json::encode() did not handle objects that implement JsonSerializable interface correctly (cebe)
|
- 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)
|
- 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 #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 #1406: DB Schema support for Oracle Database (p0larbeer, qiangxue)
|
||||||
- Enh #1437: Added ListView::viewParams (qiangxue)
|
- Enh #1437: Added ListView::viewParams (qiangxue)
|
||||||
|
|||||||
@@ -30,10 +30,25 @@ class ExistValidator extends Validator
|
|||||||
*/
|
*/
|
||||||
public $className;
|
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,
|
* 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
|
||||||
* @see className
|
* 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;
|
public $attributeName;
|
||||||
|
|
||||||
@@ -64,9 +79,7 @@ class ExistValidator extends Validator
|
|||||||
/** @var \yii\db\ActiveRecordInterface $className */
|
/** @var \yii\db\ActiveRecordInterface $className */
|
||||||
$className = $this->className === null ? get_class($object) : $this->className;
|
$className = $this->className === null ? get_class($object) : $this->className;
|
||||||
$attributeName = $this->attributeName === null ? $attribute : $this->attributeName;
|
$attributeName = $this->attributeName === null ? $attribute : $this->attributeName;
|
||||||
$query = $className::find();
|
if (!$this->exists($className, $attributeName, $object, $value)) {
|
||||||
$query->where([$attributeName => $value]);
|
|
||||||
if (!$query->exists()) {
|
|
||||||
$this->addError($object, $attribute, $this->message);
|
$this->addError($object, $attribute, $this->message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -85,10 +98,33 @@ class ExistValidator extends Validator
|
|||||||
if ($this->attributeName === null) {
|
if ($this->attributeName === null) {
|
||||||
throw new InvalidConfigException('The "attributeName" property must be set.');
|
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 */
|
/** @var \yii\db\ActiveRecordInterface $className */
|
||||||
$className = $this->className;
|
|
||||||
$query = $className::find();
|
$query = $className::find();
|
||||||
$query->where([$this->attributeName => $value]);
|
if (is_array($attributeName)) {
|
||||||
return $query->exists() ? null : [$this->message, []];
|
$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;
|
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,
|
* 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;
|
public $attributeName;
|
||||||
|
|
||||||
@@ -60,7 +76,20 @@ class UniqueValidator extends Validator
|
|||||||
$attributeName = $this->attributeName === null ? $attribute : $this->attributeName;
|
$attributeName = $this->attributeName === null ? $attribute : $this->attributeName;
|
||||||
|
|
||||||
$query = $className::find();
|
$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 (!$object instanceof ActiveRecordInterface || $object->getIsNewRecord()) {
|
||||||
// if current $object isn't in the database yet then it's OK just to call exists()
|
// 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();
|
$objects = $query->limit(2)->all();
|
||||||
$n = count($objects);
|
$n = count($objects);
|
||||||
if ($n === 1) {
|
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
|
// primary key is modified and not unique
|
||||||
$exists = $object->getOldPrimaryKey() != $object->getPrimaryKey();
|
$exists = $object->getOldPrimaryKey() != $object->getPrimaryKey();
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ use Yii;
|
|||||||
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\Order;
|
||||||
|
use yiiunit\data\ar\OrderItem;
|
||||||
use yiiunit\data\validators\models\ValidatorTestMainModel;
|
use yiiunit\data\validators\models\ValidatorTestMainModel;
|
||||||
use yiiunit\data\validators\models\ValidatorTestRefModel;
|
use yiiunit\data\validators\models\ValidatorTestRefModel;
|
||||||
use yiiunit\framework\db\DatabaseTestCase;
|
use yiiunit\framework\db\DatabaseTestCase;
|
||||||
@@ -92,4 +94,44 @@ class ExistValidatorTest extends DatabaseTestCase
|
|||||||
$val->validateAttribute($m, 'test_val');
|
$val->validateAttribute($m, 'test_val');
|
||||||
$this->assertTrue($m->hasErrors('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\validators\UniqueValidator;
|
||||||
use Yii;
|
use Yii;
|
||||||
use yiiunit\data\ar\ActiveRecord;
|
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\FakedValidationModel;
|
||||||
use yiiunit\data\validators\models\ValidatorTestMainModel;
|
use yiiunit\data\validators\models\ValidatorTestMainModel;
|
||||||
use yiiunit\data\validators\models\ValidatorTestRefModel;
|
use yiiunit\data\validators\models\ValidatorTestRefModel;
|
||||||
@@ -85,4 +87,49 @@ class UniqueValidatorTest extends DatabaseTestCase
|
|||||||
$m = new ValidatorTestMainModel();
|
$m = new ValidatorTestMainModel();
|
||||||
$val->validateAttribute($m, 'testMainVal');
|
$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