Browse Source

Fix #18051: Fix using `EachValidator` with custom validation function

tags/2.0.36
Bizley 4 years ago committed by GitHub
parent
commit
d62590807d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      docs/guide-pl/input-validation.md
  2. 4
      docs/guide/input-validation.md
  3. 1
      framework/CHANGELOG.md
  4. 38
      framework/UPGRADE.md
  5. 39
      framework/validators/EachValidator.php
  6. 18
      framework/validators/InlineValidator.php
  7. 1
      framework/validators/Validator.php
  8. 10
      tests/data/base/Speaker.php
  9. 4
      tests/data/validators/models/FakedValidationModel.php
  10. 7
      tests/framework/validators/EachValidatorTest.php
  11. 6
      tests/framework/validators/ValidatorTest.php

4
docs/guide-pl/input-validation.md

@ -319,8 +319,10 @@ Wbudowany walidator jest zdefiniowaną w modelu metodą lub funkcją anonimową.
* @param mixed $params wartość parametru podanego w zasadzie walidacji
* @param \yii\validators\InlineValidator $validator powiązana instancja InlineValidator
* Ten parametr jest dostępny od wersji 2.0.11.
* @param mixed $current aktualnie walidowana wartość atrybutu.
* Ten parametr jest dostępny od wersji 2.0.36.
*/
function ($attribute, $params, $validator)
function ($attribute, $params, $validator, $current)
```
Jeśli atrybut nie przejdzie walidacji, metoda/funkcja powinna wywołać metodę [[yii\base\Model::addError()|addError()]] do zapisania wiadomości o błędzie w modelu,

4
docs/guide/input-validation.md

@ -355,8 +355,10 @@ the method/function is:
* @param mixed $params the value of the "params" given in the rule
* @param \yii\validators\InlineValidator $validator related InlineValidator instance.
* This parameter is available since version 2.0.11.
* @param mixed $current the currently validated value of attribute.
* This parameter is available since version 2.0.36.
*/
function ($attribute, $params, $validator)
function ($attribute, $params, $validator, $current)
```
If an attribute fails the validation, the method/function should call [[yii\base\Model::addError()]] to save

1
framework/CHANGELOG.md

@ -31,6 +31,7 @@ Yii Framework 2 Change Log
- Enh #18151: Added `Mutex::isAcquired()` to check if lock is currently acquired (rhertogh)
- Bug #18094: Support for composite file extension validation (darkdef)
- Bug #18086: Fix accessing public properties of `ArrayAccess` via `ArrayHelper::getValue()` (samdark)
- Bug #18051: Fix using `EachValidator` with custom validation function (bizley)
2.0.35 May 02, 2020
-------------------

38
framework/UPGRADE.md

@ -54,9 +54,47 @@ for both A and B.
Upgrade from Yii 2.0.35
-----------------------
* Inline validator signature has been updated with 4th parameter `current`:
```php
/**
* @param mixed $current the currently validated value of attribute
*/
function ($attribute, $params, $validator, $current)
```
* Behavior of inline validator used as a rule of `EachValidator` has been changed - `$attribute` now refers to original
model's attribute and not its temporary counterpart:
```php
public $array_attribute = ['first', 'second'];
public function rules()
{
return [
['array_attribute', 'each', 'rule' => ['customValidatingMethod']],
];
}
public function customValidatingMethod($attribute, $params, $validator, $current)
{
// $attribute === 'array_attribute' (as before)
// now: $this->$attribute === ['first', 'second'] (on every iteration)
// previously:
// $this->$attribute === 'first' (on first iteration)
// $this->$attribute === 'second' (on second iteration)
// use now $current instead
// $current === 'first' (on first iteration)
// $current === 'second' (on second iteration)
}
```
* If you have any controllers that override the `init()` method, make sure they are calling `parent::init()` at
the beginning, as demonstrated in the [component guide](https://www.yiiframework.com/doc/guide/2.0/en/concept-components).
Upgrade from Yii 2.0.32
-----------------------

39
framework/validators/EachValidator.php

@ -71,11 +71,6 @@ class EachValidator extends Validator
*/
public $stopOnFirstError = true;
/**
* @var Validator validator instance.
*/
private $_validator;
/**
* {@inheritdoc}
@ -89,36 +84,27 @@ class EachValidator extends Validator
}
/**
* Returns the validator declared in [[rule]].
* @param Model|null $model model in which context validator should be created.
* @return Validator the declared validator.
*/
private function getValidator($model = null)
{
if ($this->_validator === null) {
$this->_validator = $this->createEmbeddedValidator($model);
}
return $this->_validator;
}
/**
* Creates validator object based on the validation rule specified in [[rule]].
* @param Model|null $model model in which context validator should be created.
* @param mixed|null $current value being currently validated.
* @throws \yii\base\InvalidConfigException
* @return Validator validator instance
*/
private function createEmbeddedValidator($model)
private function createEmbeddedValidator($model = null, $current = null)
{
$rule = $this->rule;
if ($rule instanceof Validator) {
return $rule;
} elseif (is_array($rule) && isset($rule[0])) { // validator type
}
if (is_array($rule) && isset($rule[0])) { // validator type
if (!is_object($model)) {
$model = new Model(); // mock up context model
}
return Validator::createValidator($rule[0], $model, $this->attributes, array_slice($rule, 1));
$params = array_slice($rule, 1);
$params['current'] = $current;
return Validator::createValidator($rule[0], $model, $this->attributes, $params);
}
throw new InvalidConfigException('Invalid validation rule: a rule must be an array specifying validator type.');
@ -135,11 +121,10 @@ class EachValidator extends Validator
return;
}
$dynamicModel = new DynamicModel($model->getAttributes());
$dynamicModel->addRule($attribute, $this->getValidator($model));
$dynamicModel->setAttributeLabels($model->attributeLabels());
foreach ($arrayOfValues as $k => $v) {
$dynamicModel = new DynamicModel($model->getAttributes());
$dynamicModel->setAttributeLabels($model->attributeLabels());
$dynamicModel->addRule($attribute, $this->createEmbeddedValidator($model, $v));
$dynamicModel->defineAttribute($attribute, $v);
$dynamicModel->validate();
@ -173,7 +158,7 @@ class EachValidator extends Validator
return [$this->message, []];
}
$validator = $this->getValidator();
$validator = $this->createEmbeddedValidator();
foreach ($value as $v) {
if ($validator->skipOnEmpty && $validator->isEmpty($v)) {
continue;

18
framework/validators/InlineValidator.php

@ -58,6 +58,11 @@ class InlineValidator extends Validator
* Please refer to [[clientValidateAttribute()]] for details on how to return client validation code.
*/
public $clientValidate;
/**
* @var mixed the value of attribute being currently validated.
* @since 2.0.36
*/
public $current;
/**
@ -72,7 +77,11 @@ class InlineValidator extends Validator
$method = $this->method->bindTo($model);
}
call_user_func($method, $attribute, $this->params, $this);
$current = $this->current;
if ($current === null) {
$current = $model->$attribute;
}
$method($attribute, $this->params, $this, $current);
}
/**
@ -87,8 +96,11 @@ class InlineValidator extends Validator
} elseif ($method instanceof \Closure) {
$method = $this->method->bindTo($model);
}
return call_user_func($method, $attribute, $this->params, $this);
$current = $this->current;
if ($current === null) {
$current = $model->$attribute;
}
return $method($attribute, $this->params, $this, $current);
}
return null;

1
framework/validators/Validator.php

@ -217,6 +217,7 @@ class Validator extends Component
$params['class'] = __NAMESPACE__ . '\InlineValidator';
$params['method'] = [$model, $type];
} else {
unset($params['current']);
if (isset(static::$builtInValidators[$type])) {
$type = static::$builtInValidators[$type];
}

10
tests/data/base/Speaker.php

@ -50,8 +50,16 @@ class Speaker extends Model
];
}
public function customValidatingMethod($attribute, $params, $validator)
private $_checkedValues = [];
public function customValidatingMethod($attribute, $params, $validator, $current)
{
$this->_checkedValues[] = $current;
$this->addError($attribute, 'Custom method error');
}
public function getCheckedValues()
{
return $this->_checkedValues;
}
}

4
tests/data/validators/models/FakedValidationModel.php

@ -44,14 +44,14 @@ class FakedValidationModel extends Model
];
}
public function inlineVal($attribute, $params = [], $validator)
public function inlineVal($attribute, $params = [], $validator, $current)
{
$this->inlineValArgs = \func_get_args();
return true;
}
public function clientInlineVal($attribute, $params = [], $validator)
public function clientInlineVal($attribute, $params = [], $validator, $current)
{
return \func_get_args();
}

7
tests/framework/validators/EachValidatorTest.php

@ -240,6 +240,9 @@ class EachValidatorTest extends TestCase
$this->assertEquals('First Name is invalid.', $model->getFirstError('firstName'));
}
/**
* @see https://github.com/yiisoft/yii2/issues/18051
*/
public function testCustomMethod()
{
$model = new Speaker();
@ -249,6 +252,10 @@ class EachValidatorTest extends TestCase
$validator->validateAttribute($model, 'firstName');
$this->assertEquals('Custom method error', $model->getFirstError('firstName'));
// make sure each value of attribute array is checked separately
$this->assertEquals(['a', 'b'], $model->getCheckedValues());
// make sure original array is restored at the end
$this->assertEquals(['a', 'b'], $model->firstName);
}
public function testAnonymousMethod()

6
tests/framework/validators/ValidatorTest.php

@ -203,12 +203,14 @@ class ValidatorTest extends TestCase
// Access to validator in inline validation (https://github.com/yiisoft/yii2/issues/6242)
$model = new FakedValidationModel();
$model->val_attr_a = 'a';
$val = Validator::createValidator('inlineVal', $model, ['val_attr_a'], ['params' => ['foo' => 'bar']]);
$val->validateAttribute($model, 'val_attr_a');
$args = $model->getInlineValArgs();
$this->assertCount(3, $args);
$this->assertCount(4, $args);
$this->assertEquals('val_attr_a', $args[0]);
$this->assertEquals('a', $args[3]);
$this->assertEquals(['foo' => 'bar'], $args[1]);
$this->assertInstanceOf(InlineValidator::className(), $args[2]);
}
@ -227,7 +229,7 @@ class ValidatorTest extends TestCase
$val->clientValidate = 'clientInlineVal';
$args = $val->clientValidateAttribute($model, 'val_attr_a', null);
$this->assertCount(3, $args);
$this->assertCount(4, $args);
$this->assertEquals('val_attr_a', $args[0]);
$this->assertEquals(['foo' => 'bar'], $args[1]);
$this->assertInstanceOf(InlineValidator::className(), $args[2]);

Loading…
Cancel
Save