EachValidator.php 6.3 KB

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