To validate composite unique keys attach the ECompositeUniqueKeyValidateable behavior, declare unique keys and declare short validation method in a model class.
There are few reasons to use behavior and validation method instead of writing validation class
we can't attach a handler for CActiveRecord::onAfterFind() with only validator (this is needed for storing of old attributes of the model for proper validation when updating an existing record)
CValidator doesn't imply validation of several attributes
Requirements ¶
Tested on Yii 1.1 and php 5.3
Usage ¶
Attach the ECompositeUniqueKeyValidatable behavior and declare unique keys
public function behaviors() {
return array(
'ECompositeUniqueKeyValidatable' => array(
'class' => 'ECompositeUniqueKeyValidatable',
'uniqueKeys' => array(
'attributes' => 'login, applicationId',
'errorMessage' => 'Your login is already taken'
)
),
);
}
declare simple validation method in the model class
/**
* Validates composite unique keys
*
* Validates composite unique keys declared in the
* ECompositeUniqueKeyValidatable bahavior
*/
public function compositeUniqueKeysValidator() {
$this->validateCompositeUniqueKeys();
}
declare the validation rule
public function rules() {
return array(
// the first parameter doesn't matter, I use '*' (pretty ugly
// definition, but I don't know a better way)
array('*', 'compositeUniqueKeysValidator'),
);
}
Description of the options of unique keys ¶
attributes - unique key
errorMessage - error message
errorAttributes (optional) - attributes of the model which will contain the error message
skipOnErrorIn (optional) - if one of this attributes contains errors then validation will be skipped
Some examples ¶
declaring of two composite unique keys
public function behaviors() {
return array(
'ECompositeUniqueKeyValidatable' => array(
'class' => 'ECompositeUniqueKeyValidatable',
'uniqueKeys' => array(
array(
'attributes' => 'email, applicationId',
'errorAttributes' => 'email, email_confirmation',
'errorMessage' => 'This email is already registered',
'skipOnErrorIn' => 'email, applicationId'
),
array(
'attributes' => 'login, applicationId',
'errorAttributes' => 'login',
'errorMessage' => 'Your login is already taken',
'skipOnErrorIn' => 'login, applicationId'
),
)
),
// ...
compositeUniqueKeysValidator
maybe you could use onBeforeValidate or onAfterValidate events instead of making users to provide their own wrapper of your mechanizm (compositeUniqueKeysValidator)?
compositeUniqueKeysValidator
yes, but in this case users will not be able to set order in which validators should be performed, and undeclared validation in onAfterValidation() method looks messy to me, I prefer more explicit ways
compositeUniqueKeysValidator
then you could create switch "useValidationEvent" which will allow to choose between hidden usage of onBeforeValidation/onAfterValidation (default) and disable this feature to use explicit validation.
...as you can see I prefer solution "less code and modyfications is better in most cases, but let there be a switch to fully manual mode for advanced scenarios" ;-)
Where do you extract the file to?
Also, where does the behaviors method go?
> Where do you extract the file to?
anywhere you want, you can use path alias in behavior definition or rely on autoload
public function behaviors() { return array( 'ECompositeUniqueKeyValidatable' => array( 'class' => 'application.extension.ECompositeUniqueKeyValidatable',
Problem when update
I got unique error when I try to update a record without changing values of the record. I think its treat as a new record.
2 chennaiiq
Hi, make sure that there is no other records with the same unique key as the record you are updating (that can happen if you had created several records before you implemented validation).
Re: Problem when update (to ololo)
No records as you told. I checked. Got the same error even when I try to update (without any changes) which I create just now
Re: Problem when update
Hm, sorry, but I have no idea what can cause this problem. If you provide me some more information so that I can reproduce the problem, then I'll try to help.
Re: Problem when update (to ololo)
I got one thing
The problem occur only when I use dependent dropdown boxes. (Working fine in normal cause).
Fields are in Form 1) Road_id - dropdown box 2) Street - text box composite unique keys are ROAD_ID and STREET
In this cause, your extension works as expected. Its excellent
But
Fields are in Form 1) City_id - Dependent Dropdown 2) Area_id - Dependent Dropdown 3) Road_id - dependent dropdown box 4) Street - text box composite unique keys are ROAD_ID and STREET
In this cause, we are facing problem when update without any change
I will send you the code by mail If you want
Thank you very much
Undefined index: oldValue (on line 90)
Hi.
I', having this error while testing your extension.
Any idea?
Regards.
Re: Undefined index: oldValue (on line 90)
Hi, oldValue should be initialized for each of the unique keys in afterFind() event handler defined in the behavior
So I can assume that ECompositeUniqueKeyValidatable::afterFind() wasn't called, e.g. it can happen because you have overriden afterFind() event handler without calling parent::afterFind().
Anyway, first make sure ECompositeUniqueKeyValidatable::afterFind() is called (for example by putting die('smth') in the afterFind() method of the behavior)
Re: Undefined index: oldValue (on line 90)
Thank you ololo.
Yes, I do use another behavior which implements afterFind.
Should I put parent::afterFind in that behaviour implementation and in yours too?
Question (since I'm a newbie): Shouldn't everybody implementing an event call parent before they proceed?
Regards.
Re: Undefined index: oldValue (on line 90)
> Question (since I'm a newbie): Shouldn't everybody implementing
> an event call parent before they proceed?
hm, I thought no, but now I'm in doubt about it
I think you can only break something by overriding afterFind() in your model because only CActiveRecord::afterFind() raises an event (so it have to be called through the parent:: keyword if you want to raise an event), and when it's raised all subscribers (including behaviors) are getting notified and perform their logic independently. So I think it's not about another behavior, but I'm not sure.
Try to define ECompositeUniqueKeyValidateable prior to your another behavior and check if afterFind() handler of another behavior is called.
Re: Undefined index: oldValue (on line 90)
Hi.
Thank you for your support.
I'm using (http://www.yiiframework.com/extension/multimodelform "multimodel-form") and I'm trying to use your extension with the detail model. I think the problem is there.
I'll investigate and let you know my findings.
Regards.
PHP 5.3 problems
There are problems with PHP 5.3, when you use the call by reference in combination with for-loops. Following fixes make it work:
/** * Normalize unique keys */ private function _normalizeKeysData() { //... // convert comma separated lists to arrays foreach ($this->uniqueKeys as $key => $uk) { //CHANGED //... $this->uniqueKeys[$key] = $uk; //ADDED } //... }
and
private function _stringListToArray(&$list) { $list = explode(',', $list); foreach ($list as $key => $item) { //CHANGED $list[$key] = trim($item); //CHANGED } }
Please update this extension.
Beside this minor issue it works very nice in 5.3, thank you!!!
RE: PHP 5.3 problems
could you please describe this problem or provide a php.net link to the bug report or make some example reproducing this problem?
just curious, sometimes people are confused with by-reference loops and call expected behavior "buggy" e.g. https://bugs.php.net/bug.php?id=39307
@ololo
Sorry, I was not precise enough. You are doing a "Call-time pass-by-reference" (inside the for loops) which is deprecated since 5.3: http://php.net/manual/en/migration53.deprecated.php
So actually this is not a "bug", but a problem when your parser raises E_DEPRECATED.
RE: PHP 5.3 problems
sieppl, I have just figured out why I don't see "call-time pass-by-reference" warnings: allow_call_time_pass_reference php.ini option should be disabled
now with this option disabled I can see that everything is allright with the extension and I don't use "call-time pass-by-reference" so I reverted back the last commit and updated the extension again
let me clarify
"call-time pass-by-reference" is when we do this
<?php function do_something($var) { // anything } $blah = "blah"; do_something(&$blah);
and this is really dangerous to pass something by reference in a function whose argument is not supposed to be passed by reference
see http://php.net/manual/en/ini.core.php
// Passing arguments by reference at function call time was deprecated for code-cleanliness reasons. A function can modify its arguments in an undocumented way if it didn't declare that the argument shall be passed by reference. To prevent side-effects it's better to specify which arguments are passed by reference in the function declaration only.
but when we do something like this then it's OK and there is no warning:
<?php function do_something(&$var) { // anything } $blah = "blah"; do_something($blah);
Breaks in 5.4
the deprecated "Call-time pass-by-reference" will fail completely in php 5.4
Building on Sebastian K.'s work, I altered:
// convert comma separated lists to arrays foreach ($this->uniqueKeys as $key=>$uk) { $this->uniqueKeys[$key] = $uk; //ADDED isset($uk['attributes']) or $uk['attributes'] = array(); is_array($uk['attributes']) or $this->_stringListToArray($this->uniqueKeys[$key]['attributes']); // *nonexistent attribute* means that an error message will not be attached to a certain attribute isset($uk['errorAttributes']) or $uk['errorAttributes'] = array('*nonexistent attribute*'); is_array($uk['errorAttributes']) or $this->_stringListToArray($this->uniqueKeys[$key]['errorAttributes']); isset($uk['skipOnErrorIn']) or $uk['skipOnErrorIn'] = array(); is_array($uk['skipOnErrorIn']) or $this->_stringListToArray($this->uniqueKeys[$key]['skipOnErrorIn']); }
If you have any questions, please ask in the forum instead.
Signup or Login in order to comment.