-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathECompositeUniqueKeyValidatable.php
More file actions
executable file
·164 lines (140 loc) · 5.43 KB
/
Copy pathECompositeUniqueKeyValidatable.php
File metadata and controls
executable file
·164 lines (140 loc) · 5.43 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
<?php
/**
* ECompositeUniqueKeyValidatable behavior attaches methods to validate
* uniqueness of composite keys
*
* @link http://www.yiiframework.com/extension/composite-unique-key-validatable/
* @link https://github.com/sergey-s/yii-composite-unique-key-validatable
* @author Sergey Sytsevich <laa513@gmail.com>
*/
class ECompositeUniqueKeyValidatable extends CActiveRecordBehavior {
/**
* @var mixed composite unique keys of a model
*
* In normalized form this is an array with the next structure
* array(
* // array representing a composite unique key
* array(
* // attributes of a composite key
* 'attributes' => array('email', 'applicationId'),
*
* // error message
* 'errorMessage' => 'This email is already registered',
*
* // (optional) list of attributes of the model which will be populated with an error message
* 'errorAttributes' => array('email', 'email_confirmation'),
*
* // (optional) if one of this attributes contains errors then validation will be skipped
* 'skipOnErrorIn' => array('email', 'applicationId')
* ),
*
* // other keys...
* )
*/
public $uniqueKeys;
/**
* @var bool Whether the $uniqueKeys property is already normalized
*/
private $_normalized = false;
/**
* Saves old values of unique keys
*
* We need old values to validate existing model on update scenario
*
* @param CEvent $event
*/
public function afterFind($event) {
$this->_normalizeKeysData();
foreach ($this->uniqueKeys as &$uk) {
$uk['oldValue'] = $this->_getUkValue($uk['attributes']);
}
}
/**
* Validates composite unique keys
*/
public function validateCompositeUniqueKeys() {
$this->_normalizeKeysData();
$object = $this->getOwner();
foreach ($this->uniqueKeys as $uk) {
// check whether validation of the current key should be skipped
foreach ($uk['skipOnErrorIn'] as $skipAttr) {
if ($object->getError($skipAttr)) {
continue 2;
}
}
$criteria = new CDbCriteria();
foreach ($uk['attributes'] as $attr) {
if ($object->$attr === null) {
$criteria->addCondition(Yii::app()->db->quoteColumnName($attr) . ' is null');
} else {
$criteria->compare(Yii::app()->db->quoteColumnName($attr), $object->$attr);
}
}
/*
* if the model is a new record or if it's an old record with modified unique key value
* then the composite key should be unique ($criteriaLimit = 0)
*
* if we are updating an existing record without changes of unique key attributes
* then we should allow one existing record satisfying the criteria
*/
$ukIsChanged = !$object->isNewRecord
&& ($uk['oldValue'] != $this->_getUkValue($uk['attributes']));
$criteriaLimit = ($object->isNewRecord || $ukIsChanged) ? 0 : 1;
if (CActiveRecord::model(get_class($object))->count($criteria) > $criteriaLimit) {
foreach ($uk['errorAttributes'] as $attr) {
$object->addError($attr, strtr($uk['errorMessage'], array('{value}'=>$object->$attr)));
}
}
}
}
/**
* [column => value] map representing composite key
*
* @return array
*/
public function _getUkValue($attributes) {
$ukValue = array();
foreach ($attributes as $attr) {
$ukValue[$attr] = $this->getOwner()->$attr;
}
return $ukValue;
}
/**
* Normalize unique keys
*/
private function _normalizeKeysData() {
if ($this->_normalized) {
return;
}
if (!is_array($this->uniqueKeys)) {
throw new CException('Wrong unique keys format in the '
. get_class($this->getOwner()) . ' model');
}
// when only one unique key is declared
if (!is_array(current($this->uniqueKeys))) {
$this->uniqueKeys = array($this->uniqueKeys);
}
// convert comma separated lists to arrays
foreach ($this->uniqueKeys as &$uk) {
isset($uk['attributes']) or $uk['attributes'] = array();
is_array($uk['attributes']) or $this->_stringListToArray($uk['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($uk['errorAttributes']);
isset($uk['skipOnErrorIn']) or $uk['skipOnErrorIn'] = array();
is_array($uk['skipOnErrorIn']) or $this->_stringListToArray($uk['skipOnErrorIn']);
}
$this->_normalized = true;
}
/**
* Convert comma separated list to array
*
* @param string $list
*/
private function _stringListToArray(&$list) {
$list = explode(',', $list);
foreach ($list as &$item) {
$item = trim($item);
}
}
}