forked from sdlins/Yii-Conditional-Validator
-
Notifications
You must be signed in to change notification settings - Fork 0
/
YiiConditionalValidator.php
199 lines (172 loc) · 7.51 KB
/
YiiConditionalValidator.php
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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
<?php
/**
* Validates multiple attributes using any Yii Core Validator
* depending on some another attribute's condition (validation) is true;
*
* Example of rule in a hipothetic invoice checking if id_payment_method is Money
* or Card, if so, then dueDate is required *and* must be numerical:
*
* array('dueDate', 'ext.YiiConditionalValidator',
* 'if'=>array(
* array('id_payment_method', 'in', 'range'=>array(PaymentMethod::MONEY, PaymentMethod::CARD), 'allowEmpty'=>false)
* ),
* 'then'=>array(
* array('dueDate', 'required', [other params]),
* array('dueDate', 'numerical', [other params]),
* ),
* ),
*
* @author Sidney Lins <[email protected]>
* @copyright Copyright © 2011 Sidney Lins
* @version 1.0.0
* @license New BSD Licence
*/
class YiiConditionalValidator extends CValidator
{
public $if = array();
public $then = array();
public function __construct()
{
$this->analyseSupliedRules();
}
protected function validateAttribute($object, $attribute)
{
$noErrorsOnIfRules = $this->runValidators($this->prepareValidatorsData($object, $this->if), true, true);
if ($noErrorsOnIfRules) {
$this->runValidators($this->prepareValidatorsData($object, $this->then));
}
}
/**
* Create a data array with params prepared for use into runValidators() method.
*
* @param CModel $object The original object that is being conditionally validated.
* @param array $rules The set of rules defined in "if" or "then" rules.
* @return array A dimensional array containing prepared data for each validator
* to be created and runned.
*/
protected function prepareValidatorsData($object, array $rules)
{
$preparedValidatorsData = array();
foreach ($rules as $ruleKey => $rule) {
$validatorData = array();
/**
* @todo Implement way of define "if" rules using or not the attribute
* name. When not using, use original attributeName list.
*/
$fetchedObjectAndAttribute = $this->fetchRealObjectAndAttribute($object, array_shift($rule));
$validatorData['object'] = $fetchedObjectAndAttribute['object'];
$validatorData['attribute'] = $fetchedObjectAndAttribute['attribute'];
$validatorData['validation'] = array_shift($rule);
foreach ($rule as $key => $ruleParams) {
$validatorData['params'][$key] = $ruleParams;
}
$validatorData['params'] = isset($validatorData['params']) ? $validatorData['params'] : array();
$preparedValidatorsData[$ruleKey] = $validatorData;
}
return $preparedValidatorsData;
}
/**
* Fetches the original or related object defined in a validation rule. If this
* attribute name uses dot notation it will try to get the related object, otherwise
* the original object is used.
*
* Ex:
*
* If original object is an instance of Post and the attribute name is 'author.name',
* this will fetch the object $author (related to Post) and its attribute 'name'.
*
* @param CActiveRecord|CFormModel $originalObject Original object
* @param string $attributeName Attribute name defined in validation (may use dot notation)
* @return array An array with original or related object and attribute name in format of
* ['object' => $instanceOfFetchedObject, 'attribute' => $attributeName].
* @throws CException If a relation not exists or if related object is null.
*/
protected function fetchRealObjectAndAttribute($originalObject, $attributeName)
{
$realObjectAndAttribute = array();
$realObjectAndAttribute['object'] = $originalObject;
$realObjectAndAttribute['attribute'] = $attributeName;
if (strpos($attributeName, '.') !== false) {
$relations = explode('.', $attributeName);
$realObject = $originalObject;
$realAttributeName = array_pop($relations);
foreach ($relations as $relation) {
if ($realObject instanceof CActiveRecord) {
if ($realObject->hasRelated($relation)) {
$realObject = $realObject->getRelated($relation);
} else {
throw new CException(
get_class($realObject) . " has not a relation named \"$relation\". Check the YiiConditionalValidator rule that is using the attribute name \"$attributeName\"."
);
}
}
}
if (is_null($realObject)) {
throw new CException(
"The relation \"$relation\" of " . get_class($realObject) . " is returning a null value. Check the YiiConditionalValidator rule that is using the attribute name \"$attributeName\"."
);
}
$realObjectAndAttribute['object'] = $realObject;
$realObjectAndAttribute['attribute'] = $realAttributeName;
}
return $realObjectAndAttribute;
}
/**
* Analyses and validates the rules suplied.
*
* @throws CException On invalid sintaxe or error.
*/
protected function analyseSupliedRules()
{
if (!is_array($this->if)) {
throw new CException('Invalid argument "validations" for YiiConditionalValidator. Please, suply an array().');
}
if (!is_array($this->then)) {
throw new CException('Invalid argument "dependentValidations" for YiiConditionalValidator. Please, suply an array().');
}
}
/**
* Creates and executes each validator based on $validatorsData.
*
* @param array $validatorsData Must contain 'validation', 'object', 'attribute'
* and 'params' for each validator.
* @param boolean $discardErrorsAfterCheck Useful to allow discard validation
* errors in "if" rules but not in "then" rules.
* @param boolean $returnFalseOnError Useful to allow return false on error
* when validating "if" rules but continue checking on error when validating
* "then" rules.
*
* @return boolean If $returnFalseOnError is true, returns false on error, otherwise
* returns ever true;
*/
protected function runValidators(array $validatorsData, $discardErrorsAfterCheck = false, $returnFalseOnError = false)
{
foreach ($validatorsData as $preparedData) {
$validation = $preparedData['validation'];
$object = $preparedData['object'];
$attribute = $preparedData['attribute'];
$params = $preparedData['params'];
$errorsBackup = $object->getErrors();
$object->clearErrors();
$validator = CValidator::createValidator($validation, $object, $attribute, $params);
$validator->validate($object, $attribute);
if ($object->hasErrors($attribute)) {
$newErrors = $object->getErrors();
$object->clearErrors();
if (!$discardErrorsAfterCheck) {
$object->addErrors($newErrors);
}
if ($returnFalseOnError) {
$object->addErrors($errorsBackup);
$errorsBackup = null;
return false;
}
} else {
$object->addErrors($errorsBackup);
$errorsBackup = null;
}
}
return true;
}
}
?>