1: <?php
2: /**
3: * CModel class file.
4: *
5: * @author Qiang Xue <qiang.xue@gmail.com>
6: * @link http://www.yiiframework.com/
7: * @copyright 2008-2013 Yii Software LLC
8: * @license http://www.yiiframework.com/license/
9: */
10:
11:
12: /**
13: * CModel is the base class providing the common features needed by data model objects.
14: *
15: * CModel defines the basic framework for data models that need to be validated.
16: *
17: * @property CList $validatorList All the validators declared in the model.
18: * @property array $validators The validators applicable to the current {@link scenario}.
19: * @property array $errors Errors for all attributes or the specified attribute. Empty array is returned if no error.
20: * @property array $attributes Attribute values (name=>value).
21: * @property string $scenario The scenario that this model is in.
22: * @property array $safeAttributeNames Safe attribute names.
23: * @property CMapIterator $iterator An iterator for traversing the items in the list.
24: *
25: * @author Qiang Xue <qiang.xue@gmail.com>
26: * @package system.base
27: * @since 1.0
28: */
29: abstract class CModel extends CComponent implements IteratorAggregate, ArrayAccess
30: {
31: private $_errors=array(); // attribute name => array of errors
32: private $_validators; // validators
33: private $_scenario=''; // scenario
34:
35: /**
36: * Returns the list of attribute names of the model.
37: * @return array list of attribute names.
38: */
39: abstract public function attributeNames();
40:
41: /**
42: * Returns the validation rules for attributes.
43: *
44: * This method should be overridden to declare validation rules.
45: * Each rule is an array with the following structure:
46: * <pre>
47: * array('attribute list', 'validator name', 'on'=>'scenario name', ...validation parameters...)
48: * </pre>
49: * where
50: * <ul>
51: * <li>attribute list: specifies the attributes (separated by commas) to be validated;</li>
52: * <li>validator name: specifies the validator to be used. It can be the name of a model class
53: * method, the name of a built-in validator, or a validator class (or its path alias).
54: * A validation method must have the following signature:
55: * <pre>
56: * // $params refers to validation parameters given in the rule
57: * function validatorName($attribute,$params)
58: * </pre>
59: * A built-in validator refers to one of the validators declared in {@link CValidator::builtInValidators}.
60: * And a validator class is a class extending {@link CValidator}.</li>
61: * <li>on: this specifies the scenarios when the validation rule should be performed.
62: * Separate different scenarios with commas. If this option is not set, the rule
63: * will be applied in any scenario that is not listed in "except". Please see {@link scenario} for more details about this option.</li>
64: * <li>except: this specifies the scenarios when the validation rule should not be performed.
65: * Separate different scenarios with commas. Please see {@link scenario} for more details about this option.</li>
66: * <li>additional parameters are used to initialize the corresponding validator properties.
67: * Please refer to individual validator class API for possible properties.</li>
68: * </ul>
69: *
70: * The following are some examples:
71: * <pre>
72: * array(
73: * array('username', 'required'),
74: * array('username', 'length', 'min'=>3, 'max'=>12),
75: * array('password', 'compare', 'compareAttribute'=>'password2', 'on'=>'register'),
76: * array('password', 'authenticate', 'on'=>'login'),
77: * );
78: * </pre>
79: *
80: * Note, in order to inherit rules defined in the parent class, a child class needs to
81: * merge the parent rules with child rules using functions like array_merge().
82: *
83: * @return array validation rules to be applied when {@link validate()} is called.
84: * @see scenario
85: */
86: public function rules()
87: {
88: return array();
89: }
90:
91: /**
92: * Returns a list of behaviors that this model should behave as.
93: * The return value should be an array of behavior configurations indexed by
94: * behavior names. Each behavior configuration can be either a string specifying
95: * the behavior class or an array of the following structure:
96: * <pre>
97: * 'behaviorName'=>array(
98: * 'class'=>'path.to.BehaviorClass',
99: * 'property1'=>'value1',
100: * 'property2'=>'value2',
101: * )
102: * </pre>
103: *
104: * Note, the behavior classes must implement {@link IBehavior} or extend from
105: * {@link CBehavior}. Behaviors declared in this method will be attached
106: * to the model when it is instantiated.
107: *
108: * For more details about behaviors, see {@link CComponent}.
109: * @return array the behavior configurations (behavior name=>behavior configuration)
110: */
111: public function behaviors()
112: {
113: return array();
114: }
115:
116: /**
117: * Returns the attribute labels.
118: * Attribute labels are mainly used in error messages of validation.
119: * By default an attribute label is generated using {@link generateAttributeLabel}.
120: * This method allows you to explicitly specify attribute labels.
121: *
122: * Note, in order to inherit labels defined in the parent class, a child class needs to
123: * merge the parent labels with child labels using functions like array_merge().
124: *
125: * @return array attribute labels (name=>label)
126: * @see generateAttributeLabel
127: */
128: public function attributeLabels()
129: {
130: return array();
131: }
132:
133: /**
134: * Performs the validation.
135: *
136: * This method executes the validation rules as declared in {@link rules}.
137: * Only the rules applicable to the current {@link scenario} will be executed.
138: * A rule is considered applicable to a scenario if its 'on' option is not set
139: * or contains the scenario.
140: *
141: * Errors found during the validation can be retrieved via {@link getErrors}.
142: *
143: * @param array $attributes list of attributes that should be validated. Defaults to null,
144: * meaning any attribute listed in the applicable validation rules should be
145: * validated. If this parameter is given as a list of attributes, only
146: * the listed attributes will be validated.
147: * @param boolean $clearErrors whether to call {@link clearErrors} before performing validation
148: * @return boolean whether the validation is successful without any error.
149: * @see beforeValidate
150: * @see afterValidate
151: */
152: public function validate($attributes=null, $clearErrors=true)
153: {
154: if($clearErrors)
155: $this->clearErrors();
156: if($this->beforeValidate())
157: {
158: foreach($this->getValidators() as $validator)
159: $validator->validate($this,$attributes);
160: $this->afterValidate();
161: return !$this->hasErrors();
162: }
163: else
164: return false;
165: }
166:
167: /**
168: * This method is invoked after a model instance is created by new operator.
169: * The default implementation raises the {@link onAfterConstruct} event.
170: * You may override this method to do postprocessing after model creation.
171: * Make sure you call the parent implementation so that the event is raised properly.
172: */
173: protected function afterConstruct()
174: {
175: if($this->hasEventHandler('onAfterConstruct'))
176: $this->onAfterConstruct(new CEvent($this));
177: }
178:
179: /**
180: * This method is invoked before validation starts.
181: * The default implementation calls {@link onBeforeValidate} to raise an event.
182: * You may override this method to do preliminary checks before validation.
183: * Make sure the parent implementation is invoked so that the event can be raised.
184: * @return boolean whether validation should be executed. Defaults to true.
185: * If false is returned, the validation will stop and the model is considered invalid.
186: */
187: protected function beforeValidate()
188: {
189: $event=new CModelEvent($this);
190: $this->onBeforeValidate($event);
191: return $event->isValid;
192: }
193:
194: /**
195: * This method is invoked after validation ends.
196: * The default implementation calls {@link onAfterValidate} to raise an event.
197: * You may override this method to do postprocessing after validation.
198: * Make sure the parent implementation is invoked so that the event can be raised.
199: */
200: protected function afterValidate()
201: {
202: $this->onAfterValidate(new CEvent($this));
203: }
204:
205: /**
206: * This event is raised after the model instance is created by new operator.
207: * @param CEvent $event the event parameter
208: */
209: public function onAfterConstruct($event)
210: {
211: $this->raiseEvent('onAfterConstruct',$event);
212: }
213:
214: /**
215: * This event is raised before the validation is performed.
216: * @param CModelEvent $event the event parameter
217: */
218: public function onBeforeValidate($event)
219: {
220: $this->raiseEvent('onBeforeValidate',$event);
221: }
222:
223: /**
224: * This event is raised after the validation is performed.
225: * @param CEvent $event the event parameter
226: */
227: public function onAfterValidate($event)
228: {
229: $this->raiseEvent('onAfterValidate',$event);
230: }
231:
232: /**
233: * Returns all the validators declared in the model.
234: * This method differs from {@link getValidators} in that the latter
235: * would only return the validators applicable to the current {@link scenario}.
236: * Also, since this method return a {@link CList} object, you may
237: * manipulate it by inserting or removing validators (useful in behaviors).
238: * For example, <code>$model->validatorList->add($newValidator)</code>.
239: * The change made to the {@link CList} object will persist and reflect
240: * in the result of the next call of {@link getValidators}.
241: * @return CList all the validators declared in the model.
242: * @since 1.1.2
243: */
244: public function getValidatorList()
245: {
246: if($this->_validators===null)
247: $this->_validators=$this->createValidators();
248: return $this->_validators;
249: }
250:
251: /**
252: * Returns the validators applicable to the current {@link scenario}.
253: * @param string $attribute the name of the attribute whose validators should be returned.
254: * If this is null, the validators for ALL attributes in the model will be returned.
255: * @return array the validators applicable to the current {@link scenario}.
256: */
257: public function getValidators($attribute=null)
258: {
259: if($this->_validators===null)
260: $this->_validators=$this->createValidators();
261:
262: $validators=array();
263: $scenario=$this->getScenario();
264: foreach($this->_validators as $validator)
265: {
266: if($validator->applyTo($scenario))
267: {
268: if($attribute===null || in_array($attribute,$validator->attributes,true))
269: $validators[]=$validator;
270: }
271: }
272: return $validators;
273: }
274:
275: /**
276: * Creates validator objects based on the specification in {@link rules}.
277: * This method is mainly used internally.
278: * @throws CException if current class has an invalid validation rule
279: * @return CList validators built based on {@link rules()}.
280: */
281: public function createValidators()
282: {
283: $validators=new CList;
284: foreach($this->rules() as $rule)
285: {
286: if(isset($rule[0],$rule[1])) // attributes, validator name
287: $validators->add(CValidator::createValidator($rule[1],$this,$rule[0],array_slice($rule,2)));
288: else
289: throw new CException(Yii::t('yii','{class} has an invalid validation rule. The rule must specify attributes to be validated and the validator name.',
290: array('{class}'=>get_class($this))));
291: }
292: return $validators;
293: }
294:
295: /**
296: * Returns a value indicating whether the attribute is required.
297: * This is determined by checking if the attribute is associated with a
298: * {@link CRequiredValidator} validation rule in the current {@link scenario}.
299: * @param string $attribute attribute name
300: * @return boolean whether the attribute is required
301: */
302: public function isAttributeRequired($attribute)
303: {
304: foreach($this->getValidators($attribute) as $validator)
305: {
306: if($validator instanceof CRequiredValidator)
307: return true;
308: }
309: return false;
310: }
311:
312: /**
313: * Returns a value indicating whether the attribute is safe for massive assignments.
314: * @param string $attribute attribute name
315: * @return boolean whether the attribute is safe for massive assignments
316: * @since 1.1
317: */
318: public function isAttributeSafe($attribute)
319: {
320: $attributes=$this->getSafeAttributeNames();
321: return in_array($attribute,$attributes);
322: }
323:
324: /**
325: * Returns the text label for the specified attribute.
326: * @param string $attribute the attribute name
327: * @return string the attribute label
328: * @see generateAttributeLabel
329: * @see attributeLabels
330: */
331: public function getAttributeLabel($attribute)
332: {
333: $labels=$this->attributeLabels();
334: if(isset($labels[$attribute]))
335: return $labels[$attribute];
336: else
337: return $this->generateAttributeLabel($attribute);
338: }
339:
340: /**
341: * Returns a value indicating whether there is any validation error.
342: * @param string $attribute attribute name. Use null to check all attributes.
343: * @return boolean whether there is any error.
344: */
345: public function hasErrors($attribute=null)
346: {
347: if($attribute===null)
348: return $this->_errors!==array();
349: else
350: return isset($this->_errors[$attribute]);
351: }
352:
353: /**
354: * Returns the errors for all attribute or a single attribute.
355: * @param string $attribute attribute name. Use null to retrieve errors for all attributes.
356: * @return array errors for all attributes or the specified attribute. Empty array is returned if no error.
357: */
358: public function getErrors($attribute=null)
359: {
360: if($attribute===null)
361: return $this->_errors;
362: else
363: return isset($this->_errors[$attribute]) ? $this->_errors[$attribute] : array();
364: }
365:
366: /**
367: * Returns the first error of the specified attribute.
368: * @param string $attribute attribute name.
369: * @return string the error message. Null is returned if no error.
370: */
371: public function getError($attribute)
372: {
373: return isset($this->_errors[$attribute]) ? reset($this->_errors[$attribute]) : null;
374: }
375:
376: /**
377: * Adds a new error to the specified attribute.
378: * @param string $attribute attribute name
379: * @param string $error new error message
380: */
381: public function addError($attribute,$error)
382: {
383: $this->_errors[$attribute][]=$error;
384: }
385:
386: /**
387: * Adds a list of errors.
388: * @param array $errors a list of errors. The array keys must be attribute names.
389: * The array values should be error messages. If an attribute has multiple errors,
390: * these errors must be given in terms of an array.
391: * You may use the result of {@link getErrors} as the value for this parameter.
392: */
393: public function addErrors($errors)
394: {
395: foreach($errors as $attribute=>$error)
396: {
397: if(is_array($error))
398: {
399: foreach($error as $e)
400: $this->addError($attribute, $e);
401: }
402: else
403: $this->addError($attribute, $error);
404: }
405: }
406:
407: /**
408: * Removes errors for all attributes or a single attribute.
409: * @param string $attribute attribute name. Use null to remove errors for all attribute.
410: */
411: public function clearErrors($attribute=null)
412: {
413: if($attribute===null)
414: $this->_errors=array();
415: else
416: unset($this->_errors[$attribute]);
417: }
418:
419: /**
420: * Generates a user friendly attribute label.
421: * This is done by replacing underscores or dashes with blanks and
422: * changing the first letter of each word to upper case.
423: * For example, 'department_name' or 'DepartmentName' becomes 'Department Name'.
424: * @param string $name the column name
425: * @return string the attribute label
426: */
427: public function generateAttributeLabel($name)
428: {
429: return ucwords(trim(strtolower(str_replace(array('-','_','.'),' ',preg_replace('/(?<![A-Z])[A-Z]/', ' \0', $name)))));
430: }
431:
432: /**
433: * Returns all attribute values.
434: * @param array $names list of attributes whose value needs to be returned.
435: * Defaults to null, meaning all attributes as listed in {@link attributeNames} will be returned.
436: * If it is an array, only the attributes in the array will be returned.
437: * @return array attribute values (name=>value).
438: */
439: public function getAttributes($names=null)
440: {
441: $values=array();
442: foreach($this->attributeNames() as $name)
443: $values[$name]=$this->$name;
444:
445: if(is_array($names))
446: {
447: $values2=array();
448: foreach($names as $name)
449: $values2[$name]=isset($values[$name]) ? $values[$name] : null;
450: return $values2;
451: }
452: else
453: return $values;
454: }
455:
456: /**
457: * Sets the attribute values in a massive way.
458: * @param array $values attribute values (name=>value) to be set.
459: * @param boolean $safeOnly whether the assignments should only be done to the safe attributes.
460: * A safe attribute is one that is associated with a validation rule in the current {@link scenario}.
461: * @see getSafeAttributeNames
462: * @see attributeNames
463: */
464: public function setAttributes($values,$safeOnly=true)
465: {
466: if(!is_array($values))
467: return;
468: $attributes=array_flip($safeOnly ? $this->getSafeAttributeNames() : $this->attributeNames());
469: foreach($values as $name=>$value)
470: {
471: if(isset($attributes[$name]))
472: $this->$name=$value;
473: elseif($safeOnly)
474: $this->onUnsafeAttribute($name,$value);
475: }
476: }
477:
478: /**
479: * Sets the attributes to be null.
480: * @param array $names list of attributes to be set null. If this parameter is not given,
481: * all attributes as specified by {@link attributeNames} will have their values unset.
482: * @since 1.1.3
483: */
484: public function unsetAttributes($names=null)
485: {
486: if($names===null)
487: $names=$this->attributeNames();
488: foreach($names as $name)
489: $this->$name=null;
490: }
491:
492: /**
493: * This method is invoked when an unsafe attribute is being massively assigned.
494: * The default implementation will log a warning message if YII_DEBUG is on.
495: * It does nothing otherwise.
496: * @param string $name the unsafe attribute name
497: * @param mixed $value the attribute value
498: * @since 1.1.1
499: */
500: public function onUnsafeAttribute($name,$value)
501: {
502: if(YII_DEBUG)
503: Yii::log(Yii::t('yii','Failed to set unsafe attribute "{attribute}" of "{class}".',array('{attribute}'=>$name, '{class}'=>get_class($this))),CLogger::LEVEL_WARNING);
504: }
505:
506: /**
507: * Returns the scenario that this model is used in.
508: *
509: * Scenario affects how validation is performed and which attributes can
510: * be massively assigned.
511: *
512: * A validation rule will be performed when calling {@link validate()}
513: * if its 'except' value does not contain current scenario value while
514: * 'on' option is not set or contains the current scenario value.
515: *
516: * And an attribute can be massively assigned if it is associated with
517: * a validation rule for the current scenario. Note that an exception is
518: * the {@link CUnsafeValidator unsafe} validator which marks the associated
519: * attributes as unsafe and not allowed to be massively assigned.
520: *
521: * @return string the scenario that this model is in.
522: */
523: public function getScenario()
524: {
525: return $this->_scenario;
526: }
527:
528: /**
529: * Sets the scenario for the model.
530: * @param string $value the scenario that this model is in.
531: * @see getScenario
532: */
533: public function setScenario($value)
534: {
535: $this->_scenario=$value;
536: }
537:
538: /**
539: * Returns the attribute names that are safe to be massively assigned.
540: * A safe attribute is one that is associated with a validation rule in the current {@link scenario}.
541: * @return array safe attribute names
542: */
543: public function getSafeAttributeNames()
544: {
545: $attributes=array();
546: $unsafe=array();
547: foreach($this->getValidators() as $validator)
548: {
549: if(!$validator->safe)
550: {
551: foreach($validator->attributes as $name)
552: $unsafe[]=$name;
553: }
554: else
555: {
556: foreach($validator->attributes as $name)
557: $attributes[$name]=true;
558: }
559: }
560:
561: foreach($unsafe as $name)
562: unset($attributes[$name]);
563: return array_keys($attributes);
564: }
565:
566: /**
567: * Returns an iterator for traversing the attributes in the model.
568: * This method is required by the interface IteratorAggregate.
569: * @return CMapIterator an iterator for traversing the items in the list.
570: */
571: public function getIterator()
572: {
573: $attributes=$this->getAttributes();
574: return new CMapIterator($attributes);
575: }
576:
577: /**
578: * Returns whether there is an element at the specified offset.
579: * This method is required by the interface ArrayAccess.
580: * @param mixed $offset the offset to check on
581: * @return boolean
582: */
583: public function offsetExists($offset)
584: {
585: return property_exists($this,$offset);
586: }
587:
588: /**
589: * Returns the element at the specified offset.
590: * This method is required by the interface ArrayAccess.
591: * @param integer $offset the offset to retrieve element.
592: * @return mixed the element at the offset, null if no element is found at the offset
593: */
594: public function offsetGet($offset)
595: {
596: return $this->$offset;
597: }
598:
599: /**
600: * Sets the element at the specified offset.
601: * This method is required by the interface ArrayAccess.
602: * @param integer $offset the offset to set element
603: * @param mixed $item the element value
604: */
605: public function offsetSet($offset,$item)
606: {
607: $this->$offset=$item;
608: }
609:
610: /**
611: * Unsets the element at the specified offset.
612: * This method is required by the interface ArrayAccess.
613: * @param mixed $offset the offset to unset element
614: */
615: public function offsetUnset($offset)
616: {
617: unset($this->$offset);
618: }
619: }
620: