Merge branch '3376-each-validator'

Conflicts:
	framework/CHANGELOG.md
This commit is contained in:
Paul Klimov
2015-04-11 14:13:55 +03:00
4 changed files with 228 additions and 1 deletions

View File

@ -21,6 +21,7 @@ Yii Framework 2 Change Log
- Bug #8012: Fixed fetching multiple relations between two tables for pgsql (nineinchnick) - Bug #8012: Fixed fetching multiple relations between two tables for pgsql (nineinchnick)
- Bug #8014: Fixed setting incorrect form "action" property after submitting a form using a link with "data-method" and containing "action" among "data-params" (samdark) - Bug #8014: Fixed setting incorrect form "action" property after submitting a form using a link with "data-method" and containing "action" among "data-params" (samdark)
- Bug #8032: `yii\rbac\PhpManager::updateItem()` was unable to rename item updated (ChristopheBrun, samdark) - Bug #8032: `yii\rbac\PhpManager::updateItem()` was unable to rename item updated (ChristopheBrun, samdark)
- Enh #3376: Added `yii\validators\EachValidator`, which allows validation of the array attributes (klimov-paul)
- Enh #6895: Added `ignoreCategories` config option for message command to ignore categories specified (samdark) - Enh #6895: Added `ignoreCategories` config option for message command to ignore categories specified (samdark)
- Enh #6975: Pressing arrows while focused in inputs of Active Form with `validateOnType` enabled no longer triggers validation (slinstj) - Enh #6975: Pressing arrows while focused in inputs of Active Form with `validateOnType` enabled no longer triggers validation (slinstj)
- Enh #7409: Allow `yii\filters\auth\CompositeAuth::authMethods` to take authentication objects (fernandezekiel, qiangxue) - Enh #7409: Allow `yii\filters\auth\CompositeAuth::authMethods` to take authentication objects (fernandezekiel, qiangxue)
@ -36,7 +37,6 @@ Yii Framework 2 Change Log
- Enh #7850: Added `yii\filters\PageCache::cacheCookies` and `cacheHeaders` to allow selectively caching cookies and HTTP headers (qiangxue) - Enh #7850: Added `yii\filters\PageCache::cacheCookies` and `cacheHeaders` to allow selectively caching cookies and HTTP headers (qiangxue)
- Enh #7867: Implemented findUniqueIndexes for oci and mssql (nineinchnick) - Enh #7867: Implemented findUniqueIndexes for oci and mssql (nineinchnick)
- Enh #7912: Added `aria-label` to ActionColumn buttons (LAV45, samdark) - Enh #7912: Added `aria-label` to ActionColumn buttons (LAV45, samdark)
- Enh #7918: `yii\widgets\Pjax` got ability to avoid registering link/form handler via setting `false` to `$linkSelector`/`$formSelector` (usualdesigner, Alex-Code, samdark)
- Enh #7973: Added `Schema::getSchemaNames` method (nineinchnick) - Enh #7973: Added `Schema::getSchemaNames` method (nineinchnick)
- Enh: Added `yii\helper\Console::wrapText()` method to wrap indented text by console window width and used it in `yii help` command (cebe) - Enh: Added `yii\helper\Console::wrapText()` method to wrap indented text by console window width and used it in `yii help` command (cebe)
- Enh: Implement batchInsert for oci (nineinchnick) - Enh: Implement batchInsert for oci (nineinchnick)

View File

@ -0,0 +1,150 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\validators;
use yii\base\InvalidConfigException;
use Yii;
use yii\base\Model;
/**
* EachValidator serves validation of the array attributes.
* It perform validation of each array element using any other validator specified by [[rule]].
*
* ~~~php
* class MyModel extends Model
* {
* public $arrayAttribute = [];
*
* public function rules()
* {
* return [
* ['arrayAttribute', 'each', 'rule' => ['trim']],
* ['arrayAttribute', 'each', 'rule' => ['integer']],
* ]
* }
* }
* ~~~
*
* Note: this validator will not work with validation declared via model inline method. If you declare inline
* validation rule for attribute, you should avoid usage of this validator and iterate over array attribute
* values manually inside your code.
*
* @property Validator $validator related validator instance. This property is read only.
*
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.0.4
*/
class EachValidator extends Validator
{
/**
* @var array|Validator definition of the validation rule, which should be used on array values.
* It should be specified in the same format as at [[yii\base\Model::rules()]], except it should not
* contain attribute list as the first element.
* For example:
*
* ~~~
* ['integer']
* ['match', 'pattern' => '/[a-z]/is']
* ~~~
*
* Please refer to [[yii\base\Model::rules()]] for more details.
*/
public $rule;
/**
* @var boolean whether to use error message composed by validator declared via [[rule]] if its validation fails.
* If enabled, error message specified for this validator itself will appear only if attribute value is not an array.
* If disabled, own error message value will be used always.
*/
public $allowMessageFromRule = true;
/**
* @var Validator validator instance.
*/
private $_validator;
/**
* @inheritdoc
*/
public function init()
{
parent::init();
if ($this->message === null) {
$this->message = Yii::t('yii', '{attribute} is invalid.');
}
}
/**
* Returns the validator declared in [[rule]].
* @return Validator the declared validator.
*/
public function getValidator()
{
if ($this->_validator === null) {
$this->_validator = $this->createValidators();
}
return $this->_validator;
}
/**
* Creates validator object based on the validation rule specified in [[rule]].
* @return Validator validator instance
* @throws InvalidConfigException if any validation rule configuration is invalid
*/
private function createValidators()
{
$rule = $this->rule;
if ($rule instanceof Validator) {
return $rule;
} elseif (is_array($rule) && isset($rule[0])) { // validator type
return Validator::createValidator($rule[0], new Model(), $this->attributes, array_slice($rule, 1));
} else {
throw new InvalidConfigException('Invalid validation rule: a rule must be an array specifying validator type.');
}
}
/**
* @inheritdoc
*/
public function validateAttribute($model, $attribute)
{
$value = $model->$attribute;
$validator = $this->getValidator();
if ($validator instanceof FilterValidator && is_array($value)) {
$filteredValue = [];
foreach ($value as $k => $v) {
if (!$validator->skipOnArray || !is_array($v)) {
$filteredValue[$k] = call_user_func($validator->filter, $v);
}
}
$model->$attribute = $filteredValue;
} else {
parent::validateAttribute($model, $attribute);
}
}
/**
* @inheritdoc
*/
protected function validateValue($value)
{
if (!is_array($value)) {
return [$this->message, []];
}
$validator = $this->getValidator();
foreach ($value as $v) {
$result = $validator->validateValue($v);
if ($result !== null) {
return $this->allowMessageFromRule ? $result : [$this->message, []];
}
}
return null;
}
}

View File

@ -27,6 +27,7 @@ use yii\base\NotSupportedException;
* - `date`: [[DateValidator]] * - `date`: [[DateValidator]]
* - `default`: [[DefaultValueValidator]] * - `default`: [[DefaultValueValidator]]
* - `double`: [[NumberValidator]] * - `double`: [[NumberValidator]]
* - `each`: [[EachValidator]]
* - `email`: [[EmailValidator]] * - `email`: [[EmailValidator]]
* - `exist`: [[ExistValidator]] * - `exist`: [[ExistValidator]]
* - `file`: [[FileValidator]] * - `file`: [[FileValidator]]
@ -57,6 +58,7 @@ class Validator extends Component
'date' => 'yii\validators\DateValidator', 'date' => 'yii\validators\DateValidator',
'default' => 'yii\validators\DefaultValueValidator', 'default' => 'yii\validators\DefaultValueValidator',
'double' => 'yii\validators\NumberValidator', 'double' => 'yii\validators\NumberValidator',
'each' => 'yii\validators\EachValidator',
'email' => 'yii\validators\EmailValidator', 'email' => 'yii\validators\EmailValidator',
'exist' => 'yii\validators\ExistValidator', 'exist' => 'yii\validators\ExistValidator',
'file' => 'yii\validators\FileValidator', 'file' => 'yii\validators\FileValidator',

View File

@ -0,0 +1,75 @@
<?php
namespace yiiunit\framework\validators;
use yii\validators\EachValidator;
use yiiunit\data\validators\models\FakedValidationModel;
use yiiunit\TestCase;
/**
* @group validators
*/
class EachValidatorTest extends TestCase
{
protected function setUp()
{
parent::setUp();
$this->mockApplication();
}
public function testArrayFormat()
{
$validator = new EachValidator(['rule' => ['required']]);
$this->assertFalse($validator->validate('not array'));
$this->assertTrue($validator->validate(['value']));
}
/**
* @depends testArrayFormat
*/
public function testValidate()
{
$validator = new EachValidator(['rule' => ['integer']]);
$this->assertTrue($validator->validate([1, 3, 8]));
$this->assertFalse($validator->validate([1, 'text', 8]));
}
/**
* @depends testArrayFormat
*/
public function testFilter()
{
$model = FakedValidationModel::createWithAttributes([
'attr_one' => [
' to be trimmed '
],
]);
$validator = new EachValidator(['rule' => ['trim']]);
$validator->validateAttribute($model, 'attr_one');
$this->assertEquals('to be trimmed', $model->attr_one[0]);
}
/**
* @depends testValidate
*/
public function testAllowMessageFromRule()
{
$model = FakedValidationModel::createWithAttributes([
'attr_one' => [
'text'
],
]);
$validator = new EachValidator(['rule' => ['integer']]);
$validator->allowMessageFromRule = true;
$validator->validateAttribute($model, 'attr_one');
$this->assertContains('integer', $model->getFirstError('attr_one'));
$model->clearErrors();
$validator->allowMessageFromRule = false;
$validator->validateAttribute($model, 'attr_one');
$this->assertNotContains('integer', $model->getFirstError('attr_one'));
}
}