yii: how to make a unique rule for two attributes
Solution 1
This can be done by Yii itself, you do not need an extension for it.
However an extension can help cleaning up the rules()
method as described here:
http://www.yiiframework.com/extension/unique-attributes-validator/
This is the code (copied from that site) which will work without using the extension:
public function rules() {
return array(
array('firstKey', 'unique', 'criteria'=>array(
'condition'=>'`secondKey`=:secondKey',
'params'=>array(
':secondKey'=>$this->secondKey
)
)),
);
}
In case the value of $this->secondKey
is not available inside rules()
-method you can add the validator in CActiveRecords beforeValidate()
-method like this:
public function beforeValidate()
{
if (parent::beforeValidate()) {
$validator = CValidator::createValidator('unique', $this, 'firstKey', array(
'criteria' => array(
'condition'=>'`secondKey`=:secondKey',
'params'=>array(
':secondKey'=>$this->secondKey
)
)
));
$this->getValidatorList()->insertAt(0, $validator);
return true;
}
return false;
}
Solution 2
You do not need complicated content of rules() method nor 3rd party extensions. Just create your own validation method. It's much easier to do it on your own.
public function rules()
{
return array(
array('firstField', 'myTestUniqueMethod'),
);
}
public function myTestUniqueMethod($attribute,$params)
{
//... and here your own pure SQL or ActiveRecord test ..
// usage:
// $this->firstField;
// $this->secondField;
// SELECT * FROM myTable WHERE firstField = $this->firstField AND secondField = $this->secondField ...
// If result not empty ... error
if (!$isUnique)
{
$this->addError('firstField', "Text of error");
$this->addError('secondField', "Text of error");
}
}
Solution 3
Yii1 :
http://www.yiiframework.com/extension/composite-unique-key-validatable/
Yii2 :
// 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 be unique together, and they both will receive error message
[['a1', 'a2'], 'unique', 'targetAttribute' => ['a1', 'a2']]
// a1 and a2 need to be 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']]
http://www.yiiframework.com/doc-2.0/yii-validators-uniquevalidator.html
Solution 4
In Yii2:
public function rules() {
return [
[['name'], 'unique', 'targetAttribute' => ['name', 'version']],
];
}
Solution 5
They've added support for unique composite keys in the next release candidate of Yii1.14rc, but here's (yet another) solution. BTW, this code uses the same 'attributeName' in the rules that the Yii framework will use in the next official release.
protected/models/Mymodel.php
public function rules()
{
return array(
array('name', 'uniqueValidator','attributeName'=>array(
'name', 'phone_number','email')
),
...
- 'name' in the beginning of the rule is the attribute the validation error will be attached to and later output on your form.
- 'attributeName' (array) contains an array of keys that you would like to validate together as a combined key.
protected/components/validators/uniqueValidator.php
class uniqueValidator extends CValidator
{
public $attributeName;
public $quiet = false; //future bool for quiet validation error -->not complete
/**
* Validates the attribute of the object.
* If there is any error, the error message is added to the object.
* @param CModel $object the object being validated
* @param string $attribute the attribute being validated
*/
protected function validateAttribute($object,$attribute)
{
// build criteria from attribute(s) using Yii CDbCriteria
$criteria=new CDbCriteria();
foreach ( $this->attributeName as $name )
$criteria->addSearchCondition( $name, $object->$name, false );
// use exists with $criteria to check if the supplied keys combined are unique
if ( $object->exists( $criteria ) ) {
$this->addError($object,$attribute, $object->label() .' ' .
$attribute .' "'. $object->$attribute . '" has already been taken.');
}
}
}
You can use as many attributes as you like and this will work for all of your CModels. The check is done with "exists".
protected/config/main.php
'application.components.validators.*',
You may have to add the above line to your config in the 'import' array so that uniqueValidator.php will be found by the Yii application.
Positive feedback and changes are very welcome!
Related videos on Youtube
li meirong
Updated on July 09, 2022Comments
-
li meirong almost 2 years
I have a table like this: (id, name, version, text). (name, version) is unique key, how can i make a rule to validate this.
-
cebe about 12 yearsThis will only validate that name is unique and version is unique alone. it does not validate the pair of columns
(name, version)
. -
Quicksilver almost 11 yearsI tried this method, but i am not getting the value
$this->secondKey
-
cebe almost 11 years
$this->secondKey
should be the second attribute that has to be unique. You have to adjust this to your attribute name. -
Quicksilver almost 11 yearsi know that. I tried with my attribute name. I'm not just getting the value of attribute inside
rules()
-
Quicksilver almost 11 yearsThanks. I will check it. BTW i ended up in writing a custom validator method.
-
Jan K. S. about 10 years+1 for addressing the case where secondKey is not available inside rules(). That solved my problem here.
-
Xaver Kapeller almost 10 yearsYour answer does not contain any information that is not already provided by at least one of the other answers.
-
ldg almost 10 yearsI'm still on 1.13 for a pretty big app that I haven't had time to upgrade. From a quick test, this works great, thanks! It also avoids having to either create a custom validator (not hard, granted but can get messy) and solves some of the issues with passing a dynamic value as the second attribute (can get really messy).
-
Nashi over 7 yearsUsually it's also possible to access secondKey in rules() direct from $_GET or $_POST - just need to use it carefully (f.e. because of possible SQL injections)
':secondKey'=>isset($_POST[get_class($this)]['secondKey']) ? $_POST[get_class($this)]['secondKey'] : 0,
-
cebe over 7 years@Nashi this is not a good idea. The model should never access request data directly. This makes it impossible to reuse it in other places and makes code really hard to understand and maintain.
-
Nashi over 7 years@cebe, of course it's not perfect idea - that's why I said "use it carefully", but... to be fair, I don't understand why you think, that it makes $_POST impossible to reuse in other places. Could you explain? Also, on the other side, maintaining validators written in two different places (rules() and beforeValidate()) isn't perfect solution too. What I'm trying to say is: both solutions are some kind of workarounds, yours is cleaner and safer, mine is only faster.
-
cebe over 7 years@Nashi, the point of clean code is not to be faster now, but so save your hours of debugging later. If everything is in one class you have that class to look at. If you use $_POST, you have your entire code base to search for bug.