EachValidator.php 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  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\validators;
  8. use Yii;
  9. use yii\base\DynamicModel;
  10. use yii\base\InvalidConfigException;
  11. use yii\base\Model;
  12. /**
  13. * EachValidator validates an array by checking each of its elements against an embedded validation rule.
  14. *
  15. * ```php
  16. * class MyModel extends Model
  17. * {
  18. * public $categoryIDs = [];
  19. *
  20. * public function rules()
  21. * {
  22. * return [
  23. * // checks if every category ID is an integer
  24. * ['categoryIDs', 'each', 'rule' => ['integer']],
  25. * ]
  26. * }
  27. * }
  28. * ```
  29. *
  30. * > Note: This validator will not work with inline validation rules in case of usage outside the model scope,
  31. * e.g. via [[validate()]] method.
  32. *
  33. * > Note: EachValidator is meant to be used only in basic cases, you should consider usage of tabular input,
  34. * using several models for the more complex case.
  35. *
  36. * @author Paul Klimov <klimov.paul@gmail.com>
  37. * @since 2.0.4
  38. */
  39. class EachValidator extends Validator
  40. {
  41. /**
  42. * @var array|Validator definition of the validation rule, which should be used on array values.
  43. * It should be specified in the same format as at [[\yii\base\Model::rules()]], except it should not
  44. * contain attribute list as the first element.
  45. * For example:
  46. *
  47. * ```php
  48. * ['integer']
  49. * ['match', 'pattern' => '/[a-z]/is']
  50. * ```
  51. *
  52. * Please refer to [[\yii\base\Model::rules()]] for more details.
  53. */
  54. public $rule;
  55. /**
  56. * @var bool whether to use error message composed by validator declared via [[rule]] if its validation fails.
  57. * If enabled, error message specified for this validator itself will appear only if attribute value is not an array.
  58. * If disabled, own error message value will be used always.
  59. */
  60. public $allowMessageFromRule = true;
  61. /**
  62. * @var bool whether to stop validation once first error among attribute value elements is detected.
  63. * When enabled validation will produce single error message on attribute, when disabled - multiple
  64. * error messages mya appear: one per each invalid value.
  65. * Note that this option will affect only [[validateAttribute()]] value, while [[validateValue()]] will
  66. * not be affected.
  67. * @since 2.0.11
  68. */
  69. public $stopOnFirstError = true;
  70. /**
  71. * @var Validator validator instance.
  72. */
  73. private $_validator;
  74. /**
  75. * {@inheritdoc}
  76. */
  77. public function init()
  78. {
  79. parent::init();
  80. if ($this->message === null) {
  81. $this->message = Yii::t('yii', '{attribute} is invalid.');
  82. }
  83. }
  84. /**
  85. * Returns the validator declared in [[rule]].
  86. * @param Model|null $model model in which context validator should be created.
  87. * @return Validator the declared validator.
  88. */
  89. private function getValidator($model = null)
  90. {
  91. if ($this->_validator === null) {
  92. $this->_validator = $this->createEmbeddedValidator($model);
  93. }
  94. return $this->_validator;
  95. }
  96. /**
  97. * Creates validator object based on the validation rule specified in [[rule]].
  98. * @param Model|null $model model in which context validator should be created.
  99. * @throws \yii\base\InvalidConfigException
  100. * @return Validator validator instance
  101. */
  102. private function createEmbeddedValidator($model)
  103. {
  104. $rule = $this->rule;
  105. if ($rule instanceof Validator) {
  106. return $rule;
  107. } elseif (is_array($rule) && isset($rule[0])) { // validator type
  108. if (!is_object($model)) {
  109. $model = new Model(); // mock up context model
  110. }
  111. return Validator::createValidator($rule[0], $model, $this->attributes, array_slice($rule, 1));
  112. }
  113. throw new InvalidConfigException('Invalid validation rule: a rule must be an array specifying validator type.');
  114. }
  115. /**
  116. * {@inheritdoc}
  117. */
  118. public function validateAttribute($model, $attribute)
  119. {
  120. $arrayOfValues = $model->$attribute;
  121. if (!is_array($arrayOfValues) && !$arrayOfValues instanceof \ArrayAccess) {
  122. $this->addError($model, $attribute, $this->message, []);
  123. return;
  124. }
  125. $dynamicModel = new DynamicModel($model->getAttributes());
  126. $dynamicModel->addRule($attribute, $this->getValidator($model));
  127. $dynamicModel->setAttributeLabels($model->attributeLabels());
  128. foreach ($arrayOfValues as $k => $v) {
  129. $dynamicModel->defineAttribute($attribute, $v);
  130. $dynamicModel->validate();
  131. $arrayOfValues[$k] = $dynamicModel->$attribute; // filtered values like 'trim'
  132. if (!$dynamicModel->hasErrors($attribute)) {
  133. continue;
  134. }
  135. if ($this->allowMessageFromRule) {
  136. $validationErrors = $dynamicModel->getErrors($attribute);
  137. $model->addErrors([$attribute => $validationErrors]);
  138. } else {
  139. $this->addError($model, $attribute, $this->message, ['value' => $v]);
  140. }
  141. if ($this->stopOnFirstError) {
  142. break;
  143. }
  144. }
  145. $model->$attribute = $arrayOfValues;
  146. }
  147. /**
  148. * {@inheritdoc}
  149. */
  150. protected function validateValue($value)
  151. {
  152. if (!is_array($value) && !$value instanceof \ArrayAccess) {
  153. return [$this->message, []];
  154. }
  155. $validator = $this->getValidator();
  156. foreach ($value as $v) {
  157. if ($validator->skipOnEmpty && $validator->isEmpty($v)) {
  158. continue;
  159. }
  160. $result = $validator->validateValue($v);
  161. if ($result !== null) {
  162. if ($this->allowMessageFromRule) {
  163. $result[1]['value'] = $v;
  164. return $result;
  165. }
  166. return [$this->message, ['value' => $v]];
  167. }
  168. }
  169. return null;
  170. }
  171. }