Model.php 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858
  1. <?php
  2. /**
  3. * @link http://www.yiiframework.com/
  4. * @copyright Copyright (c) 2008 Yii Software LLC
  5. * @license http://www.yiiframework.com/license/
  6. */
  7. namespace yii\base;
  8. use Yii;
  9. use ArrayAccess;
  10. use ArrayObject;
  11. use ArrayIterator;
  12. use ReflectionClass;
  13. use IteratorAggregate;
  14. use yii\helpers\Inflector;
  15. use yii\validators\RequiredValidator;
  16. use yii\validators\Validator;
  17. /**
  18. * Model is the base class for data models.
  19. *
  20. * Model implements the following commonly used features:
  21. *
  22. * - attribute declaration: by default, every public class member is considered as
  23. * a model attribute
  24. * - attribute labels: each attribute may be associated with a label for display purpose
  25. * - massive attribute assignment
  26. * - scenario-based validation
  27. *
  28. * Model also raises the following events when performing data validation:
  29. *
  30. * - [[EVENT_BEFORE_VALIDATE]]: an event raised at the beginning of [[validate()]]
  31. * - [[EVENT_AFTER_VALIDATE]]: an event raised at the end of [[validate()]]
  32. *
  33. * You may directly use Model to store model data, or extend it with customization.
  34. * You may also customize Model by attaching [[ModelBehavior|model behaviors]].
  35. *
  36. * @property \yii\validators\Validator[] $activeValidators The validators applicable to the current
  37. * [[scenario]]. This property is read-only.
  38. * @property array $attributes Attribute values (name => value).
  39. * @property array $errors An array of errors for all attributes. Empty array is returned if no error. The
  40. * result is a two-dimensional array. See [[getErrors()]] for detailed description. This property is read-only.
  41. * @property array $firstErrors The first errors. An empty array will be returned if there is no error. This
  42. * property is read-only.
  43. * @property ArrayIterator $iterator An iterator for traversing the items in the list. This property is
  44. * read-only.
  45. * @property string $scenario The scenario that this model is in. Defaults to [[DEFAULT_SCENARIO]].
  46. * @property ArrayObject|\yii\validators\Validator[] $validators All the validators declared in the model.
  47. * This property is read-only.
  48. *
  49. * @author Qiang Xue <[email protected]>
  50. * @since 2.0
  51. */
  52. class Model extends Component implements IteratorAggregate, ArrayAccess
  53. {
  54. /**
  55. * The name of the default scenario.
  56. */
  57. const DEFAULT_SCENARIO = 'default';
  58. /**
  59. * @event ModelEvent an event raised at the beginning of [[validate()]]. You may set
  60. * [[ModelEvent::isValid]] to be false to stop the validation.
  61. */
  62. const EVENT_BEFORE_VALIDATE = 'beforeValidate';
  63. /**
  64. * @event Event an event raised at the end of [[validate()]]
  65. */
  66. const EVENT_AFTER_VALIDATE = 'afterValidate';
  67. /**
  68. * @var array validation errors (attribute name => array of errors)
  69. */
  70. private $_errors;
  71. /**
  72. * @var ArrayObject list of validators
  73. */
  74. private $_validators;
  75. /**
  76. * @var string current scenario
  77. */
  78. private $_scenario = self::DEFAULT_SCENARIO;
  79. /**
  80. * Returns the validation rules for attributes.
  81. *
  82. * Validation rules are used by [[validate()]] to check if attribute values are valid.
  83. * Child classes may override this method to declare different validation rules.
  84. *
  85. * Each rule is an array with the following structure:
  86. *
  87. * ~~~
  88. * [
  89. * ['attribute1', 'attribute2'],
  90. * 'validator type',
  91. * 'on' => ['scenario1', 'scenario2'],
  92. * ...other parameters...
  93. * ]
  94. * ~~~
  95. *
  96. * where
  97. *
  98. * - attribute list: required, specifies the attributes array to be validated, for single attribute you can pass string;
  99. * - validator type: required, specifies the validator to be used. It can be the name of a model
  100. * class method, the name of a built-in validator, or a validator class name (or its path alias).
  101. * - on: optional, specifies the [[scenario|scenarios]] array when the validation
  102. * rule can be applied. If this option is not set, the rule will apply to all scenarios.
  103. * - additional name-value pairs can be specified to initialize the corresponding validator properties.
  104. * Please refer to individual validator class API for possible properties.
  105. *
  106. * A validator can be either an object of a class extending [[Validator]], or a model class method
  107. * (called *inline validator*) that has the following signature:
  108. *
  109. * ~~~
  110. * // $params refers to validation parameters given in the rule
  111. * function validatorName($attribute, $params)
  112. * ~~~
  113. *
  114. * In the above `$attribute` refers to currently validated attribute name while `$params` contains an array of
  115. * validator configuration options such as `max` in case of `string` validator. Currently validate attribute value
  116. * can be accessed as `$this->[$attribute]`.
  117. *
  118. * Yii also provides a set of [[Validator::builtInValidators|built-in validators]].
  119. * They each has an alias name which can be used when specifying a validation rule.
  120. *
  121. * Below are some examples:
  122. *
  123. * ~~~
  124. * [
  125. * // built-in "required" validator
  126. * [['username', 'password'], 'required'],
  127. * // built-in "string" validator customized with "min" and "max" properties
  128. * ['username', 'string', 'min' => 3, 'max' => 12],
  129. * // built-in "compare" validator that is used in "register" scenario only
  130. * ['password', 'compare', 'compareAttribute' => 'password2', 'on' => 'register'],
  131. * // an inline validator defined via the "authenticate()" method in the model class
  132. * ['password', 'authenticate', 'on' => 'login'],
  133. * // a validator of class "DateRangeValidator"
  134. * ['dateRange', 'DateRangeValidator'],
  135. * ];
  136. * ~~~
  137. *
  138. * Note, in order to inherit rules defined in the parent class, a child class needs to
  139. * merge the parent rules with child rules using functions such as `array_merge()`.
  140. *
  141. * @return array validation rules
  142. * @see scenarios()
  143. */
  144. public function rules()
  145. {
  146. return [];
  147. }
  148. /**
  149. * Returns a list of scenarios and the corresponding active attributes.
  150. * An active attribute is one that is subject to validation in the current scenario.
  151. * The returned array should be in the following format:
  152. *
  153. * ~~~
  154. * [
  155. * 'scenario1' => ['attribute11', 'attribute12', ...],
  156. * 'scenario2' => ['attribute21', 'attribute22', ...],
  157. * ...
  158. * ]
  159. * ~~~
  160. *
  161. * By default, an active attribute is considered safe and can be massively assigned.
  162. * If an attribute should NOT be massively assigned (thus considered unsafe),
  163. * please prefix the attribute with an exclamation character (e.g. '!rank').
  164. *
  165. * The default implementation of this method will return all scenarios found in the [[rules()]]
  166. * declaration. A special scenario named [[DEFAULT_SCENARIO]] will contain all attributes
  167. * found in the [[rules()]]. Each scenario will be associated with the attributes that
  168. * are being validated by the validation rules that apply to the scenario.
  169. *
  170. * @return array a list of scenarios and the corresponding active attributes.
  171. */
  172. public function scenarios()
  173. {
  174. $scenarios = [self::DEFAULT_SCENARIO => []];
  175. foreach ($this->getValidators() as $validator) {
  176. foreach ($validator->on as $scenario) {
  177. $scenarios[$scenario] = [];
  178. }
  179. foreach ($validator->except as $scenario) {
  180. $scenarios[$scenario] = [];
  181. }
  182. }
  183. $names = array_keys($scenarios);
  184. foreach ($this->getValidators() as $validator) {
  185. if (empty($validator->on) && empty($validator->except)) {
  186. foreach ($names as $name) {
  187. foreach ($validator->attributes as $attribute) {
  188. $scenarios[$name][$attribute] = true;
  189. }
  190. }
  191. } elseif (empty($validator->on)) {
  192. foreach ($names as $name) {
  193. if (!in_array($name, $validator->except, true)) {
  194. foreach ($validator->attributes as $attribute) {
  195. $scenarios[$name][$attribute] = true;
  196. }
  197. }
  198. }
  199. } else {
  200. foreach ($validator->on as $name) {
  201. foreach ($validator->attributes as $attribute) {
  202. $scenarios[$name][$attribute] = true;
  203. }
  204. }
  205. }
  206. }
  207. foreach ($scenarios as $scenario => $attributes) {
  208. if (empty($attributes) && $scenario !== self::DEFAULT_SCENARIO) {
  209. unset($scenarios[$scenario]);
  210. } else {
  211. $scenarios[$scenario] = array_keys($attributes);
  212. }
  213. }
  214. return $scenarios;
  215. }
  216. /**
  217. * Returns the form name that this model class should use.
  218. *
  219. * The form name is mainly used by [[\yii\web\ActiveForm]] to determine how to name
  220. * the input fields for the attributes in a model. If the form name is "A" and an attribute
  221. * name is "b", then the corresponding input name would be "A[b]". If the form name is
  222. * an empty string, then the input name would be "b".
  223. *
  224. * By default, this method returns the model class name (without the namespace part)
  225. * as the form name. You may override it when the model is used in different forms.
  226. *
  227. * @return string the form name of this model class.
  228. */
  229. public function formName()
  230. {
  231. $reflector = new ReflectionClass($this);
  232. return $reflector->getShortName();
  233. }
  234. /**
  235. * Returns the list of attribute names.
  236. * By default, this method returns all public non-static properties of the class.
  237. * You may override this method to change the default behavior.
  238. * @return array list of attribute names.
  239. */
  240. public function attributes()
  241. {
  242. $class = new ReflectionClass($this);
  243. $names = [];
  244. foreach ($class->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) {
  245. if (!$property->isStatic()) {
  246. $names[] = $property->getName();
  247. }
  248. }
  249. return $names;
  250. }
  251. /**
  252. * Returns the attribute labels.
  253. *
  254. * Attribute labels are mainly used for display purpose. For example, given an attribute
  255. * `firstName`, we can declare a label `First Name` which is more user-friendly and can
  256. * be displayed to end users.
  257. *
  258. * By default an attribute label is generated using [[generateAttributeLabel()]].
  259. * This method allows you to explicitly specify attribute labels.
  260. *
  261. * Note, in order to inherit labels defined in the parent class, a child class needs to
  262. * merge the parent labels with child labels using functions such as `array_merge()`.
  263. *
  264. * @return array attribute labels (name => label)
  265. * @see generateAttributeLabel()
  266. */
  267. public function attributeLabels()
  268. {
  269. return [];
  270. }
  271. /**
  272. * Performs the data validation.
  273. *
  274. * This method executes the validation rules applicable to the current [[scenario]].
  275. * The following criteria are used to determine whether a rule is currently applicable:
  276. *
  277. * - the rule must be associated with the attributes relevant to the current scenario;
  278. * - the rules must be effective for the current scenario.
  279. *
  280. * This method will call [[beforeValidate()]] and [[afterValidate()]] before and
  281. * after the actual validation, respectively. If [[beforeValidate()]] returns false,
  282. * the validation will be cancelled and [[afterValidate()]] will not be called.
  283. *
  284. * Errors found during the validation can be retrieved via [[getErrors()]],
  285. * [[getFirstErrors()]] and [[getFirstError()]].
  286. *
  287. * @param array $attributes list of attributes that should be validated.
  288. * If this parameter is empty, it means any attribute listed in the applicable
  289. * validation rules should be validated.
  290. * @param boolean $clearErrors whether to call [[clearErrors()]] before performing validation
  291. * @return boolean whether the validation is successful without any error.
  292. * @throws InvalidParamException if the current scenario is unknown.
  293. */
  294. public function validate($attributes = null, $clearErrors = true)
  295. {
  296. $scenarios = $this->scenarios();
  297. $scenario = $this->getScenario();
  298. if (!isset($scenarios[$scenario])) {
  299. throw new InvalidParamException("Unknown scenario: $scenario");
  300. }
  301. if ($clearErrors) {
  302. $this->clearErrors();
  303. }
  304. if ($attributes === null) {
  305. $attributes = $this->activeAttributes();
  306. }
  307. if ($this->beforeValidate()) {
  308. foreach ($this->getActiveValidators() as $validator) {
  309. $validator->validateAttributes($this, $attributes);
  310. }
  311. $this->afterValidate();
  312. return !$this->hasErrors();
  313. }
  314. return false;
  315. }
  316. /**
  317. * This method is invoked before validation starts.
  318. * The default implementation raises a `beforeValidate` event.
  319. * You may override this method to do preliminary checks before validation.
  320. * Make sure the parent implementation is invoked so that the event can be raised.
  321. * @return boolean whether the validation should be executed. Defaults to true.
  322. * If false is returned, the validation will stop and the model is considered invalid.
  323. */
  324. public function beforeValidate()
  325. {
  326. $event = new ModelEvent;
  327. $this->trigger(self::EVENT_BEFORE_VALIDATE, $event);
  328. return $event->isValid;
  329. }
  330. /**
  331. * This method is invoked after validation ends.
  332. * The default implementation raises an `afterValidate` event.
  333. * You may override this method to do postprocessing after validation.
  334. * Make sure the parent implementation is invoked so that the event can be raised.
  335. */
  336. public function afterValidate()
  337. {
  338. $this->trigger(self::EVENT_AFTER_VALIDATE);
  339. }
  340. /**
  341. * Returns all the validators declared in [[rules()]].
  342. *
  343. * This method differs from [[getActiveValidators()]] in that the latter
  344. * only returns the validators applicable to the current [[scenario]].
  345. *
  346. * Because this method returns an ArrayObject object, you may
  347. * manipulate it by inserting or removing validators (useful in model behaviors).
  348. * For example,
  349. *
  350. * ~~~
  351. * $model->validators[] = $newValidator;
  352. * ~~~
  353. *
  354. * @return ArrayObject|\yii\validators\Validator[] all the validators declared in the model.
  355. */
  356. public function getValidators()
  357. {
  358. if ($this->_validators === null) {
  359. $this->_validators = $this->createValidators();
  360. }
  361. return $this->_validators;
  362. }
  363. /**
  364. * Returns the validators applicable to the current [[scenario]].
  365. * @param string $attribute the name of the attribute whose applicable validators should be returned.
  366. * If this is null, the validators for ALL attributes in the model will be returned.
  367. * @return \yii\validators\Validator[] the validators applicable to the current [[scenario]].
  368. */
  369. public function getActiveValidators($attribute = null)
  370. {
  371. $validators = [];
  372. $scenario = $this->getScenario();
  373. foreach ($this->getValidators() as $validator) {
  374. if ($validator->isActive($scenario) && ($attribute === null || in_array($attribute, $validator->attributes, true))) {
  375. $validators[] = $validator;
  376. }
  377. }
  378. return $validators;
  379. }
  380. /**
  381. * Creates validator objects based on the validation rules specified in [[rules()]].
  382. * Unlike [[getValidators()]], each time this method is called, a new list of validators will be returned.
  383. * @return ArrayObject validators
  384. * @throws InvalidConfigException if any validation rule configuration is invalid
  385. */
  386. public function createValidators()
  387. {
  388. $validators = new ArrayObject;
  389. foreach ($this->rules() as $rule) {
  390. if ($rule instanceof Validator) {
  391. $validators->append($rule);
  392. } elseif (is_array($rule) && isset($rule[0], $rule[1])) { // attributes, validator type
  393. $validator = Validator::createValidator($rule[1], $this, (array) $rule[0], array_slice($rule, 2));
  394. $validators->append($validator);
  395. } else {
  396. throw new InvalidConfigException('Invalid validation rule: a rule must specify both attribute names and validator type.');
  397. }
  398. }
  399. return $validators;
  400. }
  401. /**
  402. * Returns a value indicating whether the attribute is required.
  403. * This is determined by checking if the attribute is associated with a
  404. * [[\yii\validators\RequiredValidator|required]] validation rule in the
  405. * current [[scenario]].
  406. * @param string $attribute attribute name
  407. * @return boolean whether the attribute is required
  408. */
  409. public function isAttributeRequired($attribute)
  410. {
  411. foreach ($this->getActiveValidators($attribute) as $validator) {
  412. if ($validator instanceof RequiredValidator) {
  413. return true;
  414. }
  415. }
  416. return false;
  417. }
  418. /**
  419. * Returns a value indicating whether the attribute is safe for massive assignments.
  420. * @param string $attribute attribute name
  421. * @return boolean whether the attribute is safe for massive assignments
  422. * @see safeAttributes()
  423. */
  424. public function isAttributeSafe($attribute)
  425. {
  426. return in_array($attribute, $this->safeAttributes(), true);
  427. }
  428. /**
  429. * Returns a value indicating whether the attribute is active in the current scenario.
  430. * @param string $attribute attribute name
  431. * @return boolean whether the attribute is active in the current scenario
  432. * @see activeAttributes()
  433. */
  434. public function isAttributeActive($attribute)
  435. {
  436. return in_array($attribute, $this->activeAttributes(), true);
  437. }
  438. /**
  439. * Returns the text label for the specified attribute.
  440. * @param string $attribute the attribute name
  441. * @return string the attribute label
  442. * @see generateAttributeLabel()
  443. * @see attributeLabels()
  444. */
  445. public function getAttributeLabel($attribute)
  446. {
  447. $labels = $this->attributeLabels();
  448. return isset($labels[$attribute]) ? $labels[$attribute] : $this->generateAttributeLabel($attribute);
  449. }
  450. /**
  451. * Returns a value indicating whether there is any validation error.
  452. * @param string|null $attribute attribute name. Use null to check all attributes.
  453. * @return boolean whether there is any error.
  454. */
  455. public function hasErrors($attribute = null)
  456. {
  457. return $attribute === null ? !empty($this->_errors) : isset($this->_errors[$attribute]);
  458. }
  459. /**
  460. * Returns the errors for all attribute or a single attribute.
  461. * @param string $attribute attribute name. Use null to retrieve errors for all attributes.
  462. * @property array An array of errors for all attributes. Empty array is returned if no error.
  463. * The result is a two-dimensional array. See [[getErrors()]] for detailed description.
  464. * @return array errors for all attributes or the specified attribute. Empty array is returned if no error.
  465. * Note that when returning errors for all attributes, the result is a two-dimensional array, like the following:
  466. *
  467. * ~~~
  468. * [
  469. * 'username' => [
  470. * 'Username is required.',
  471. * 'Username must contain only word characters.',
  472. * ],
  473. * 'email' => [
  474. * 'Email address is invalid.',
  475. * ]
  476. * ]
  477. * ~~~
  478. *
  479. * @see getFirstErrors()
  480. * @see getFirstError()
  481. */
  482. public function getErrors($attribute = null)
  483. {
  484. if ($attribute === null) {
  485. return $this->_errors === null ? [] : $this->_errors;
  486. } else {
  487. return isset($this->_errors[$attribute]) ? $this->_errors[$attribute] : [];
  488. }
  489. }
  490. /**
  491. * Returns the first error of every attribute in the model.
  492. * @return array the first errors. An empty array will be returned if there is no error.
  493. * @see getErrors()
  494. * @see getFirstError()
  495. */
  496. public function getFirstErrors()
  497. {
  498. if (empty($this->_errors)) {
  499. return [];
  500. } else {
  501. $errors = [];
  502. foreach ($this->_errors as $attributeErrors) {
  503. if (isset($attributeErrors[0])) {
  504. $errors[] = $attributeErrors[0];
  505. }
  506. }
  507. }
  508. return $errors;
  509. }
  510. /**
  511. * Returns the first error of the specified attribute.
  512. * @param string $attribute attribute name.
  513. * @return string the error message. Null is returned if no error.
  514. * @see getErrors()
  515. * @see getFirstErrors()
  516. */
  517. public function getFirstError($attribute)
  518. {
  519. return isset($this->_errors[$attribute]) ? reset($this->_errors[$attribute]) : null;
  520. }
  521. /**
  522. * Adds a new error to the specified attribute.
  523. * @param string $attribute attribute name
  524. * @param string $error new error message
  525. */
  526. public function addError($attribute, $error = '')
  527. {
  528. $this->_errors[$attribute][] = $error;
  529. }
  530. /**
  531. * Removes errors for all attributes or a single attribute.
  532. * @param string $attribute attribute name. Use null to remove errors for all attribute.
  533. */
  534. public function clearErrors($attribute = null)
  535. {
  536. if ($attribute === null) {
  537. $this->_errors = [];
  538. } else {
  539. unset($this->_errors[$attribute]);
  540. }
  541. }
  542. /**
  543. * Generates a user friendly attribute label based on the give attribute name.
  544. * This is done by replacing underscores, dashes and dots with blanks and
  545. * changing the first letter of each word to upper case.
  546. * For example, 'department_name' or 'DepartmentName' will generate 'Department Name'.
  547. * @param string $name the column name
  548. * @return string the attribute label
  549. */
  550. public function generateAttributeLabel($name)
  551. {
  552. return Inflector::camel2words($name, true);
  553. }
  554. /**
  555. * Returns attribute values.
  556. * @param array $names list of attributes whose value needs to be returned.
  557. * Defaults to null, meaning all attributes listed in [[attributes()]] will be returned.
  558. * If it is an array, only the attributes in the array will be returned.
  559. * @param array $except list of attributes whose value should NOT be returned.
  560. * @return array attribute values (name => value).
  561. */
  562. public function getAttributes($names = null, $except = [])
  563. {
  564. $values = [];
  565. if ($names === null) {
  566. $names = $this->attributes();
  567. }
  568. foreach ($names as $name) {
  569. $values[$name] = $this->$name;
  570. }
  571. foreach ($except as $name) {
  572. unset($values[$name]);
  573. }
  574. return $values;
  575. }
  576. /**
  577. * Sets the attribute values in a massive way.
  578. * @param array $values attribute values (name => value) to be assigned to the model.
  579. * @param boolean $safeOnly whether the assignments should only be done to the safe attributes.
  580. * A safe attribute is one that is associated with a validation rule in the current [[scenario]].
  581. * @see safeAttributes()
  582. * @see attributes()
  583. */
  584. public function setAttributes($values, $safeOnly = true)
  585. {
  586. if (is_array($values)) {
  587. $attributes = array_flip($safeOnly ? $this->safeAttributes() : $this->attributes());
  588. foreach ($values as $name => $value) {
  589. if (isset($attributes[$name])) {
  590. $this->$name = $value;
  591. } elseif ($safeOnly) {
  592. $this->onUnsafeAttribute($name, $value);
  593. }
  594. }
  595. }
  596. }
  597. /**
  598. * This method is invoked when an unsafe attribute is being massively assigned.
  599. * The default implementation will log a warning message if YII_DEBUG is on.
  600. * It does nothing otherwise.
  601. * @param string $name the unsafe attribute name
  602. * @param mixed $value the attribute value
  603. */
  604. public function onUnsafeAttribute($name, $value)
  605. {
  606. if (YII_DEBUG) {
  607. Yii::trace("Failed to set unsafe attribute '$name' in '" . get_class($this) . "'.", __METHOD__);
  608. }
  609. }
  610. /**
  611. * Returns the scenario that this model is used in.
  612. *
  613. * Scenario affects how validation is performed and which attributes can
  614. * be massively assigned.
  615. *
  616. * @return string the scenario that this model is in. Defaults to [[DEFAULT_SCENARIO]].
  617. */
  618. public function getScenario()
  619. {
  620. return $this->_scenario;
  621. }
  622. /**
  623. * Sets the scenario for the model.
  624. * Note that this method does not check if the scenario exists or not.
  625. * The method [[validate()]] will perform this check.
  626. * @param string $value the scenario that this model is in.
  627. */
  628. public function setScenario($value)
  629. {
  630. $this->_scenario = $value;
  631. }
  632. /**
  633. * Returns the attribute names that are safe to be massively assigned in the current scenario.
  634. * @return string[] safe attribute names
  635. */
  636. public function safeAttributes()
  637. {
  638. $scenario = $this->getScenario();
  639. $scenarios = $this->scenarios();
  640. if (!isset($scenarios[$scenario])) {
  641. return [];
  642. }
  643. $attributes = [];
  644. foreach ($scenarios[$scenario] as $attribute) {
  645. if ($attribute[0] !== '!') {
  646. $attributes[] = $attribute;
  647. }
  648. }
  649. return $attributes;
  650. }
  651. /**
  652. * Returns the attribute names that are subject to validation in the current scenario.
  653. * @return string[] safe attribute names
  654. */
  655. public function activeAttributes()
  656. {
  657. $scenario = $this->getScenario();
  658. $scenarios = $this->scenarios();
  659. if (!isset($scenarios[$scenario])) {
  660. return [];
  661. }
  662. $attributes = $scenarios[$scenario];
  663. foreach ($attributes as $i => $attribute) {
  664. if ($attribute[0] === '!') {
  665. $attributes[$i] = substr($attribute, 1);
  666. }
  667. }
  668. return $attributes;
  669. }
  670. /**
  671. * Populates the model with the data from end user.
  672. * The data to be loaded is `$data[formName]`, where `formName` refers to the value of [[formName()]].
  673. * If [[formName()]] is empty, the whole `$data` array will be used to populate the model.
  674. * The data being populated is subject to the safety check by [[setAttributes()]].
  675. * @param array $data the data array. This is usually `$_POST` or `$_GET`, but can also be any valid array
  676. * supplied by end user.
  677. * @param string $formName the form name to be used for loading the data into the model.
  678. * If not set, [[formName()]] will be used.
  679. * @return boolean whether the model is successfully populated with some data.
  680. */
  681. public function load($data, $formName = null)
  682. {
  683. $scope = $formName === null ? $this->formName() : $formName;
  684. if ($scope == '') {
  685. $this->setAttributes($data);
  686. return true;
  687. } elseif (isset($data[$scope])) {
  688. $this->setAttributes($data[$scope]);
  689. return true;
  690. } else {
  691. return false;
  692. }
  693. }
  694. /**
  695. * Populates a set of models with the data from end user.
  696. * This method is mainly used to collect tabular data input.
  697. * The data to be loaded for each model is `$data[formName][index]`, where `formName`
  698. * refers to the value of [[formName()]], and `index` the index of the model in the `$models` array.
  699. * If [[formName()]] is empty, `$data[index]` will be used to populate each model.
  700. * The data being populated to each model is subject to the safety check by [[setAttributes()]].
  701. * @param array $models the models to be populated. Note that all models should have the same class.
  702. * @param array $data the data array. This is usually `$_POST` or `$_GET`, but can also be any valid array
  703. * supplied by end user.
  704. * @return boolean whether the model is successfully populated with some data.
  705. */
  706. public static function loadMultiple($models, $data)
  707. {
  708. /** @var Model $model */
  709. $model = reset($models);
  710. if ($model === false) {
  711. return false;
  712. }
  713. $success = false;
  714. $scope = $model->formName();
  715. foreach ($models as $i => $model) {
  716. if ($scope == '') {
  717. if (isset($data[$i])) {
  718. $model->setAttributes($data[$i]);
  719. $success = true;
  720. }
  721. } elseif (isset($data[$scope][$i])) {
  722. $model->setAttributes($data[$scope][$i]);
  723. $success = true;
  724. }
  725. }
  726. return $success;
  727. }
  728. /**
  729. * Validates multiple models.
  730. * This method will validate every model. The models being validated may
  731. * be of the same or different types.
  732. * @param array $models the models to be validated
  733. * @param array $attributes list of attributes that should be validated.
  734. * If this parameter is empty, it means any attribute listed in the applicable
  735. * validation rules should be validated.
  736. * @return boolean whether all models are valid. False will be returned if one
  737. * or multiple models have validation error.
  738. */
  739. public static function validateMultiple($models, $attributes = null)
  740. {
  741. $valid = true;
  742. /** @var Model $model */
  743. foreach ($models as $model) {
  744. $valid = $model->validate($attributes) && $valid;
  745. }
  746. return $valid;
  747. }
  748. /**
  749. * Converts the object into an array.
  750. * The default implementation will return [[attributes]].
  751. * @return array the array representation of the object
  752. */
  753. public function toArray()
  754. {
  755. return $this->getAttributes();
  756. }
  757. /**
  758. * Returns an iterator for traversing the attributes in the model.
  759. * This method is required by the interface IteratorAggregate.
  760. * @return ArrayIterator an iterator for traversing the items in the list.
  761. */
  762. public function getIterator()
  763. {
  764. $attributes = $this->getAttributes();
  765. return new ArrayIterator($attributes);
  766. }
  767. /**
  768. * Returns whether there is an element at the specified offset.
  769. * This method is required by the SPL interface `ArrayAccess`.
  770. * It is implicitly called when you use something like `isset($model[$offset])`.
  771. * @param mixed $offset the offset to check on
  772. * @return boolean
  773. */
  774. public function offsetExists($offset)
  775. {
  776. return $this->$offset !== null;
  777. }
  778. /**
  779. * Returns the element at the specified offset.
  780. * This method is required by the SPL interface `ArrayAccess`.
  781. * It is implicitly called when you use something like `$value = $model[$offset];`.
  782. * @param mixed $offset the offset to retrieve element.
  783. * @return mixed the element at the offset, null if no element is found at the offset
  784. */
  785. public function offsetGet($offset)
  786. {
  787. return $this->$offset;
  788. }
  789. /**
  790. * Sets the element at the specified offset.
  791. * This method is required by the SPL interface `ArrayAccess`.
  792. * It is implicitly called when you use something like `$model[$offset] = $item;`.
  793. * @param integer $offset the offset to set element
  794. * @param mixed $item the element value
  795. */
  796. public function offsetSet($offset, $item)
  797. {
  798. $this->$offset = $item;
  799. }
  800. /**
  801. * Sets the element value at the specified offset to null.
  802. * This method is required by the SPL interface `ArrayAccess`.
  803. * It is implicitly called when you use something like `unset($model[$offset])`.
  804. * @param mixed $offset the offset to unset element
  805. */
  806. public function offsetUnset($offset)
  807. {
  808. $this->$offset = null;
  809. }
  810. }