From 9649a6727ade3f065f3fa0b5f1fbd65e269b51c1 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Sun, 22 Dec 2013 16:40:51 -0500 Subject: [PATCH] Renamed `attributeName` and `className` to `targetAttribute` and `targetClass` for `UniqueValidator` and `ExistValidator`. Refactored UniqueValidator and ExistValidator. --- docs/guide/validation.md | 12 ++- framework/CHANGELOG.md | 1 + framework/yii/db/ActiveQuery.php | 5 + framework/yii/validators/ExistValidator.php | 118 ++++++++++----------- framework/yii/validators/UniqueValidator.php | 94 ++++++++-------- .../framework/validators/ExistValidatorTest.php | 24 ++--- .../framework/validators/UniqueValidatorTest.php | 22 ++-- 7 files changed, 139 insertions(+), 137 deletions(-) diff --git a/docs/guide/validation.md b/docs/guide/validation.md index 1b1e92e..5067cb6 100644 --- a/docs/guide/validation.md +++ b/docs/guide/validation.md @@ -67,9 +67,9 @@ Validates that the attribute value is a valid email address. Validates that the attribute value exists in a table. -- `className` the ActiveRecord class name or alias of the class that should be used to look for the attribute value being +- `targetClass` the ActiveRecord class name or alias of the class that should be used to look for the attribute value being validated. _(ActiveRecord class of the attribute being validated)_ -- `attributeName` the ActiveRecord attribute name that should be used to look for the attribute value being validated. +- `targetAttribute` the ActiveRecord attribute name that should be used to look for the attribute value being validated. _(name of the attribute being validated)_ ### `file`: [[FileValidator]] @@ -112,7 +112,9 @@ Validates that the attribute value is among a list of values. ### `inline`: [[InlineValidator]] -Uses a custom function to validate the attribute. You need to define a public method in your model class which will evaluate the validity of the attribute. For example, if an attribute needs to be divisible by 10. In the rules you would define: `['attributeName', 'myValidationMethod'],`. +Uses a custom function to validate the attribute. You need to define a public method in your +model class which will evaluate the validity of the attribute. For example, if an attribute +needs to be divisible by 10. In the rules you would define: `['attributeName', 'myValidationMethod'],`. Then, your own method could look like this: ```php @@ -161,9 +163,9 @@ Validates that the attribute value is of certain length. Validates that the attribute value is unique in the corresponding database table. -- `className` the ActiveRecord class name or alias of the class that should be used to look for the attribute value being +- `targetClass` the ActiveRecord class name or alias of the class that should be used to look for the attribute value being validated. _(ActiveRecord class of the attribute being validated)_ -- `attributeName` the ActiveRecord attribute name that should be used to look for the attribute value being validated. +- `targetAttribute` the ActiveRecord attribute name that should be used to look for the attribute value being validated. _(name of the attribute being validated)_ ### `url`: [[UrlValidator]] diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 6c81dd9..41d8f75 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -32,6 +32,7 @@ Yii Framework 2 Change Log - Enh: Sort and Paginiation can now create absolute URLs (cebe) - Chg: Renamed `yii\jui\Widget::clientEventsMap` to `clientEventMap` (qiangxue) - Chg: Renamed `ActiveRecord::getPopulatedRelations()` to `getRelatedRecords()` (qiangxue) +- Chg: Renamed `attributeName` and `className` to `targetAttribute` and `targetClass` for `UniqueValidator` and `ExistValidator` (qiangxue) - Chg: Added `yii\widgets\InputWidget::options` (qiangxue) - New #1438: [MongoDB integration](https://github.com/yiisoft/yii2-mongodb) ActiveRecord and Query (klimov-paul) - New #1393: [Codeception testing framework integration](https://github.com/yiisoft/yii2-codeception) (Ragazzo) diff --git a/framework/yii/db/ActiveQuery.php b/framework/yii/db/ActiveQuery.php index e93e9be..e0e14cf 100644 --- a/framework/yii/db/ActiveQuery.php +++ b/framework/yii/db/ActiveQuery.php @@ -143,4 +143,9 @@ class ActiveQuery extends Query implements ActiveQueryInterface } return $db->createCommand($sql, $params); } + + public function joinWith($name) + { + + } } diff --git a/framework/yii/validators/ExistValidator.php b/framework/yii/validators/ExistValidator.php index c205655..323172a 100644 --- a/framework/yii/validators/ExistValidator.php +++ b/framework/yii/validators/ExistValidator.php @@ -13,44 +13,47 @@ use yii\base\InvalidConfigException; /** * ExistValidator validates that the attribute value exists in a table. * + * ExistValidator checks if the value being validated can be found in the table column specified by + * the ActiveRecord class [[targetClass]] and the attribute [[targetAttribute]]. + * * This validator is often used to verify that a foreign key contains a value * that can be found in the foreign table. * + * The followings are examples of validation rules using this validator: + * + * ```php + * // a1 needs to exist + * ['a1', 'exist'] + * // a1 needs to exist, but its value will use a2 to check for the existence + * ['a1', 'exist', 'targetAttribute' => 'a2'] + * // a1 and a2 need to exist together, and they both will receive error message + * ['a1, a2', 'exist', 'targetAttribute' => ['a1', 'a2']] + * // a1 and a2 need to exist together, only a1 will receive error message + * ['a1', 'exist', 'targetAttribute' => ['a1', 'a2']] + * // a1 needs to be unique by checking the existence of both a2 and a3 (using a1 value) + * ['a1', 'unique', 'targetAttribute' => ['a2', 'a1' => 'a3']] + * ``` + * * @author Qiang Xue * @since 2.0 */ class ExistValidator extends Validator { /** - * @var string the ActiveRecord class name or alias of the class - * that should be used to look for the attribute value being validated. - * Defaults to null, meaning using the ActiveRecord class of - * the attribute being validated. - * @see attributeName + * @var string the name of the ActiveRecord class that should be used to validate the existence + * of the current attribute value. It not set, it will use the ActiveRecord class of the attribute being validated. + * @see targetAttribute */ - public $className; + public $targetClass; /** - * @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. 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)) - * ``` + * @var string|array the name of the ActiveRecord attribute that should be used to + * validate the existence of the current attribute value. If not set, it will use the name + * of the attribute currently being validated. You may use an array to validate the existence + * of multiple columns at the same time. The array values are the attributes that will be + * used to validate the existence, while the array keys are the attributes whose values are to be validated. + * If the key and the value are the same, you can just specify the value. */ - public $attributeName; + public $targetAttribute; /** @@ -69,17 +72,28 @@ class ExistValidator extends Validator */ public function validateAttribute($object, $attribute) { - $value = $object->$attribute; + /** @var \yii\db\ActiveRecordInterface $targetClass */ + $targetClass = $this->targetClass === null ? get_class($object) : $this->targetClass; + $targetAttribute = $this->targetAttribute === null ? $attribute : $this->targetAttribute; - if (is_array($value)) { - $this->addError($object, $attribute, $this->message); - return; + if (is_array($targetAttribute)) { + $params = []; + foreach ($targetAttribute as $k => $v) { + $params[$v] = is_integer($k) ? $object->$v : $object->$k; + } + } else { + $params = [$targetAttribute => $object->$attribute]; + } + + foreach ($params as $value) { + if (is_array($value)) { + $this->addError($object, $attribute, Yii::t('yii', '{attribute} is invalid.')); + return; + } } /** @var \yii\db\ActiveRecordInterface $className */ - $className = $this->className === null ? get_class($object) : $this->className; - $attributeName = $this->attributeName === null ? $attribute : $this->attributeName; - if (!$this->exists($className, $attributeName, $object, $value)) { + if (!$targetClass::find()->where($params)->exists()) { $this->addError($object, $attribute, $this->message); } } @@ -92,39 +106,17 @@ class ExistValidator extends Validator if (is_array($value)) { return [$this->message, []]; } - if ($this->className === null) { + if ($this->targetClass === null) { throw new InvalidConfigException('The "className" property must be set.'); } - if ($this->attributeName === null) { - throw new InvalidConfigException('The "attributeName" property must be set.'); + if (!is_string($this->targetAttribute)) { + throw new InvalidConfigException('The "attributeName" property must be configured as a string.'); } - 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 */ - $query = $className::find(); - 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(); + /** @var \yii\db\ActiveRecordInterface $targetClass */ + $targetClass = $this->targetClass; + $query = $targetClass::find(); + $query->where([$this->targetAttribute => $value]); + return $query->exists() ? null : [$this->message, []]; } } diff --git a/framework/yii/validators/UniqueValidator.php b/framework/yii/validators/UniqueValidator.php index a497ead..1136f02 100644 --- a/framework/yii/validators/UniqueValidator.php +++ b/framework/yii/validators/UniqueValidator.php @@ -11,7 +11,25 @@ use Yii; use yii\db\ActiveRecordInterface; /** - * UniqueValidator validates that the attribute value is unique in the corresponding database table. + * UniqueValidator validates that the attribute value is unique in the specified database table. + * + * UniqueValidator checks if the value being validated is unique in the table column specified by + * the ActiveRecord class [[targetClass]] and the attribute [[targetAttribute]]. + * + * The followings are examples of validation rules using this validator: + * + * ```php + * // a1 needs to be unique + * ['a1', 'unique'] + * // a1 needs to be unique, but column a2 will be used to check the uniqueness of the a1 value + * ['a1', 'unique', 'targetAttribute' => 'a2'] + * // a1 and a2 need to unique together, and they both will receive error message + * ['a1, a2', 'unique', 'targetAttribute' => ['a1', 'a2']] + * // a1 and a2 need to unique together, only a1 will receive error message + * ['a1', 'unique', 'targetAttribute' => ['a1', 'a2']] + * // a1 needs to be unique by checking the uniqueness of both a2 and a3 (using a1 value) + * ['a1', 'unique', 'targetAttribute' => ['a2', 'a1' => 'a3']] + * ``` * * @author Qiang Xue * @since 2.0 @@ -19,34 +37,20 @@ use yii\db\ActiveRecordInterface; class UniqueValidator extends Validator { /** - * @var string the ActiveRecord class name or alias of the class - * that should be used to look for the attribute value being validated. - * Defaults to null, meaning using the ActiveRecord class of the attribute being validated. - * @see attributeName + * @var string the name of the ActiveRecord class that should be used to validate the uniqueness + * of the current attribute value. It not set, it will use the ActiveRecord class of the attribute being validated. + * @see targetAttribute */ - public $className; + public $targetClass; /** - * @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. 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)) - * ``` + * @var string|array the name of the ActiveRecord attribute that should be used to + * validate the uniqueness of the current attribute value. If not set, it will use the name + * of the attribute currently being validated. You may use an array to validate the uniqueness + * of multiple columns at the same time. The array values are the attributes that will be + * used to validate the uniqueness, while the array keys are the attributes whose values are to be validated. + * If the key and the value are the same, you can just specify the value. */ - public $attributeName; + public $targetAttribute; /** * @inheritdoc @@ -64,31 +68,27 @@ class UniqueValidator extends Validator */ public function validateAttribute($object, $attribute) { - $value = $object->$attribute; - - if (is_array($value)) { - $this->addError($object, $attribute, Yii::t('yii', '{attribute} is invalid.')); - return; - } - - /** @var ActiveRecordInterface $className */ - $className = $this->className === null ? get_class($object) : $this->className; - $attributeName = $this->attributeName === null ? $attribute : $this->attributeName; - - $query = $className::find(); + /** @var ActiveRecordInterface $targetClass */ + $targetClass = $this->targetClass === null ? get_class($object) : $this->targetClass; + $targetAttribute = $this->targetAttribute === null ? $attribute : $this->targetAttribute; - if (is_array($attributeName)) { + if (is_array($targetAttribute)) { $params = []; - foreach ($attributeName as $k => $v) { - if (is_integer($k)) { - $params[$v] = $this->className === null ? $object->$v : $value; - } else { - $params[$k] = $v; - } + foreach ($targetAttribute as $k => $v) { + $params[$v] = is_integer($k) ? $object->$v : $object->$k; } } else { - $params = [$attributeName => $value]; + $params = [$targetAttribute => $object->$attribute]; + } + + foreach ($params as $value) { + if (is_array($value)) { + $this->addError($object, $attribute, Yii::t('yii', '{attribute} is invalid.')); + return; + } } + + $query = $targetClass::find(); $query->where($params); if (!$object instanceof ActiveRecordInterface || $object->getIsNewRecord()) { @@ -101,7 +101,7 @@ class UniqueValidator extends Validator $n = count($objects); if ($n === 1) { $keys = array_keys($params); - $pks = $className::primaryKey(); + $pks = $targetClass::primaryKey(); sort($keys); sort($pks); if ($keys === $pks) { diff --git a/tests/unit/framework/validators/ExistValidatorTest.php b/tests/unit/framework/validators/ExistValidatorTest.php index 03332ad..8f1a054 100644 --- a/tests/unit/framework/validators/ExistValidatorTest.php +++ b/tests/unit/framework/validators/ExistValidatorTest.php @@ -36,18 +36,18 @@ class ExistValidatorTest extends DatabaseTestCase } // combine to save the time creating a new db-fixture set (likely ~5 sec) try { - $val = new ExistValidator(['className' => ValidatorTestMainModel::className()]); + $val = new ExistValidator(['targetClass' => ValidatorTestMainModel::className()]); $val->validate('ref'); $this->fail('Exception should have been thrown at this time'); } catch (Exception $e) { $this->assertInstanceOf('yii\base\InvalidConfigException', $e); - $this->assertEquals('The "attributeName" property must be set.', $e->getMessage()); + $this->assertEquals('The "attributeName" property must be configured as a string.', $e->getMessage()); } } public function testValidateValue() { - $val = new ExistValidator(['className' => ValidatorTestRefModel::className(), 'attributeName' => 'id']); + $val = new ExistValidator(['targetClass' => ValidatorTestRefModel::className(), 'targetAttribute' => 'id']); $this->assertTrue($val->validate(2)); $this->assertTrue($val->validate(5)); $this->assertFalse($val->validate(99)); @@ -57,22 +57,22 @@ class ExistValidatorTest extends DatabaseTestCase public function testValidateAttribute() { // existing value on different table - $val = new ExistValidator(['className' => ValidatorTestMainModel::className(), 'attributeName' => 'id']); + $val = new ExistValidator(['targetClass' => ValidatorTestMainModel::className(), 'targetAttribute' => 'id']); $m = ValidatorTestRefModel::find(['id' => 1]); $val->validateAttribute($m, 'ref'); $this->assertFalse($m->hasErrors()); // non-existing value on different table - $val = new ExistValidator(['className' => ValidatorTestMainModel::className(), 'attributeName' => 'id']); + $val = new ExistValidator(['targetClass' => ValidatorTestMainModel::className(), 'targetAttribute' => 'id']); $m = ValidatorTestRefModel::find(['id' => 6]); $val->validateAttribute($m, 'ref'); $this->assertTrue($m->hasErrors('ref')); // existing value on same table - $val = new ExistValidator(['attributeName' => 'ref']); + $val = new ExistValidator(['targetAttribute' => 'ref']); $m = ValidatorTestRefModel::find(['id' => 2]); $val->validateAttribute($m, 'test_val'); $this->assertFalse($m->hasErrors()); // non-existing value on same table - $val = new ExistValidator(['attributeName' => 'ref']); + $val = new ExistValidator(['targetAttribute' => 'ref']); $m = ValidatorTestRefModel::find(['id' => 5]); $val->validateAttribute($m, 'test_val_fail'); $this->assertTrue($m->hasErrors('test_val_fail')); @@ -88,7 +88,7 @@ class ExistValidatorTest extends DatabaseTestCase $val->validateAttribute($m, 'a_field'); $this->assertTrue($m->hasErrors('a_field')); // check array - $val = new ExistValidator(['attributeName' => 'ref']); + $val = new ExistValidator(['targetAttribute' => 'ref']); $m = ValidatorTestRefModel::find(['id' => 2]); $m->test_val = [1,2,3]; $val->validateAttribute($m, 'test_val'); @@ -98,8 +98,8 @@ class ExistValidatorTest extends DatabaseTestCase public function testValidateCompositeKeys() { $val = new ExistValidator([ - 'className' => OrderItem::className(), - 'attributeName' => ['order_id', 'item_id'], + 'targetClass' => OrderItem::className(), + 'targetAttribute' => ['order_id', 'item_id'], ]); // validate old record $m = OrderItem::find(['order_id' => 1, 'item_id' => 2]); @@ -115,8 +115,8 @@ class ExistValidatorTest extends DatabaseTestCase $this->assertTrue($m->hasErrors('order_id')); $val = new ExistValidator([ - 'className' => OrderItem::className(), - 'attributeName' => ['order_id', 'item_id' => 2], + 'targetClass' => OrderItem::className(), + 'targetAttribute' => ['id' => 'order_id'], ]); // validate old record $m = Order::find(1); diff --git a/tests/unit/framework/validators/UniqueValidatorTest.php b/tests/unit/framework/validators/UniqueValidatorTest.php index 1631243..4af3d29 100644 --- a/tests/unit/framework/validators/UniqueValidatorTest.php +++ b/tests/unit/framework/validators/UniqueValidatorTest.php @@ -60,7 +60,7 @@ class UniqueValidatorTest extends DatabaseTestCase public function testValidateAttributeOfNonARModel() { - $val = new UniqueValidator(['className' => ValidatorTestRefModel::className(), 'attributeName' => 'ref']); + $val = new UniqueValidator(['targetClass' => ValidatorTestRefModel::className(), 'targetAttribute' => 'ref']); $m = FakedValidationModel::createWithAttributes(['attr_1' => 5, 'attr_2' => 1313]); $val->validateAttribute($m, 'attr_1'); $this->assertTrue($m->hasErrors('attr_1')); @@ -70,7 +70,7 @@ class UniqueValidatorTest extends DatabaseTestCase public function testValidateNonDatabaseAttribute() { - $val = new UniqueValidator(['className' => ValidatorTestRefModel::className(), 'attributeName' => 'ref']); + $val = new UniqueValidator(['targetClass' => ValidatorTestRefModel::className(), 'targetAttribute' => 'ref']); $m = ValidatorTestMainModel::find(1); $val->validateAttribute($m, 'testMainVal'); $this->assertFalse($m->hasErrors('testMainVal')); @@ -91,8 +91,8 @@ class UniqueValidatorTest extends DatabaseTestCase public function testValidateCompositeKeys() { $val = new UniqueValidator([ - 'className' => OrderItem::className(), - 'attributeName' => ['order_id', 'item_id'], + 'targetClass' => OrderItem::className(), + 'targetAttribute' => ['order_id', 'item_id'], ]); // validate old record $m = OrderItem::find(['order_id' => 1, 'item_id' => 2]); @@ -111,19 +111,21 @@ class UniqueValidatorTest extends DatabaseTestCase $this->assertFalse($m->hasErrors('order_id')); $val = new UniqueValidator([ - 'className' => OrderItem::className(), - 'attributeName' => ['order_id', 'item_id' => 2], + 'targetClass' => OrderItem::className(), + 'targetAttribute' => ['id' => 'order_id'], ]); // validate old record $m = Order::find(1); $val->validateAttribute($m, 'id'); - $this->assertFalse($m->hasErrors('id')); + $this->assertTrue($m->hasErrors('id')); + $m = Order::find(1); $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 = Order::find(1); + $m->id = 10; + $val->validateAttribute($m, 'id'); + $this->assertFalse($m->hasErrors('id')); $m = new Order(['id' => 1]); $val->validateAttribute($m, 'id');