mirror of
https://github.com/yiisoft/yii2.git
synced 2025-11-03 05:48:11 +08:00
Refactor validateAttribute method in UniqueValidator (#13202)
* Refactor validateAttribute method in UniqueValidator Extract prepareParams and prepareQuery from validateAttribute, so they can be tested separately. * Added issue number to changelog * Eliminated unneeded variable * Renamed methods and parameters, update PHPDocs
This commit is contained in:
committed by
Boudewijn Vahrmeijer
parent
01997441cc
commit
bfba0aa711
@ -8,7 +8,11 @@
|
||||
namespace yii\validators;
|
||||
|
||||
use Yii;
|
||||
use yii\base\Model;
|
||||
use yii\db\ActiveQuery;
|
||||
use yii\db\ActiveQueryInterface;
|
||||
use yii\db\ActiveRecordInterface;
|
||||
use yii\db\Query;
|
||||
use yii\helpers\Inflector;
|
||||
|
||||
/**
|
||||
@ -44,7 +48,7 @@ class UniqueValidator extends Validator
|
||||
*/
|
||||
public $targetClass;
|
||||
/**
|
||||
* @var string|array the name of the ActiveRecord attribute that should be used to
|
||||
* @var string|array the name of the [[\yii\db\ActiveRecord|ActiveRecord]] attribute that should be used to
|
||||
* validate the uniqueness of the current attribute value. If not set, it will use the name
|
||||
* of the attribute currently being validated. You may use an array to validate the uniqueness
|
||||
* of multiple columns at the same time. The array values are the attributes that will be
|
||||
@ -111,32 +115,38 @@ class UniqueValidator extends Validator
|
||||
/* @var $targetClass ActiveRecordInterface */
|
||||
$targetClass = $this->targetClass === null ? get_class($model) : $this->targetClass;
|
||||
$targetAttribute = $this->targetAttribute === null ? $attribute : $this->targetAttribute;
|
||||
$conditions = $this->prepareConditions($targetAttribute, $model, $attribute);
|
||||
|
||||
if (is_array($targetAttribute)) {
|
||||
$params = [];
|
||||
foreach ($targetAttribute as $k => $v) {
|
||||
$params[$v] = is_int($k) ? $model->$v : $model->$k;
|
||||
}
|
||||
} else {
|
||||
$params = [$targetAttribute => $model->$attribute];
|
||||
}
|
||||
|
||||
foreach ($params as $value) {
|
||||
foreach ($conditions as $value) {
|
||||
if (is_array($value)) {
|
||||
$this->addError($model, $attribute, Yii::t('yii', '{attribute} is invalid.'));
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$query = $targetClass::find();
|
||||
$query->andWhere($params);
|
||||
|
||||
if ($this->filter instanceof \Closure) {
|
||||
call_user_func($this->filter, $query);
|
||||
} elseif ($this->filter !== null) {
|
||||
$query->andWhere($this->filter);
|
||||
if ($this->modelExists($targetClass, $conditions, $model)) {
|
||||
if (count($targetAttribute) > 1) {
|
||||
$this->addComboNotUniqueError($model, $attribute);
|
||||
} else {
|
||||
$this->addError($model, $attribute, $this->message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the $model exists in the database.
|
||||
*
|
||||
* @param string $targetClass the name of the ActiveRecord class that should be used to validate the uniqueness
|
||||
* of the current attribute value.
|
||||
* @param array $conditions conditions, compatible with [[\yii\db\Query::where()|Query::where()]] key-value format.
|
||||
* @param Model $model the data model to be validated
|
||||
*
|
||||
* @return bool whether the model already exists
|
||||
*/
|
||||
private function modelExists($targetClass, $conditions, $model)
|
||||
{
|
||||
/** @var ActiveRecordInterface $targetClass $query */
|
||||
$query = $this->prepareQuery($targetClass, $conditions);
|
||||
|
||||
if (!$model instanceof ActiveRecordInterface || $model->getIsNewRecord() || $model->className() !== $targetClass::className()) {
|
||||
// if current $model isn't in the database yet then it's OK just to call exists()
|
||||
@ -144,11 +154,11 @@ class UniqueValidator extends Validator
|
||||
$exists = $query->exists();
|
||||
} else {
|
||||
// if current $model is in the database already we can't use exists()
|
||||
/* @var $models ActiveRecordInterface[] */
|
||||
/** @var $models ActiveRecordInterface[] */
|
||||
$models = $query->select($targetClass::primaryKey())->limit(2)->all();
|
||||
$n = count($models);
|
||||
if ($n === 1) {
|
||||
$keys = array_keys($params);
|
||||
$keys = array_keys($conditions);
|
||||
$pks = $targetClass::primaryKey();
|
||||
sort($keys);
|
||||
sort($pks);
|
||||
@ -164,13 +174,59 @@ class UniqueValidator extends Validator
|
||||
}
|
||||
}
|
||||
|
||||
if ($exists) {
|
||||
if (count($targetAttribute) > 1) {
|
||||
$this->addComboNotUniqueError($model, $attribute);
|
||||
} else {
|
||||
$this->addError($model, $attribute, $this->message);
|
||||
}
|
||||
return $exists;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a query by applying filtering conditions defined in $conditions method property
|
||||
* and [[filter]] class property.
|
||||
*
|
||||
* @param ActiveRecordInterface $targetClass the name of the ActiveRecord class that should be used to validate
|
||||
* the uniqueness of the current attribute value.
|
||||
* @param array $conditions conditions, compatible with [[\yii\db\Query::where()|Query::where()]] key-value format
|
||||
*
|
||||
* @return ActiveQueryInterface|ActiveQuery
|
||||
*/
|
||||
private function prepareQuery($targetClass, $conditions)
|
||||
{
|
||||
$query = $targetClass::find();
|
||||
$query->andWhere($conditions);
|
||||
|
||||
if ($this->filter instanceof \Closure) {
|
||||
call_user_func($this->filter, $query);
|
||||
} elseif ($this->filter !== null) {
|
||||
$query->andWhere($this->filter);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes attributes' relations described in $targetAttribute parameter into conditions, compatible with
|
||||
* [[\yii\db\Query::where()|Query::where()]] key-value format.
|
||||
*
|
||||
* @param string|array $targetAttribute the name of the [[\yii\db\ActiveRecord|ActiveRecord]] attribute that
|
||||
* should be used to validate the uniqueness of the current attribute value. You may use an array to validate
|
||||
* the uniqueness of multiple columns at the same time. The array values are the attributes that will be
|
||||
* used to validate the uniqueness, while the array keys are the attributes whose values are to be validated.
|
||||
* If the key and the value are the same, you can just specify the value.
|
||||
* @param Model $model the data model to be validated
|
||||
* @param string $attribute the name of the attribute to be validated in the $model
|
||||
|
||||
* @return array conditions, compatible with [[\yii\db\Query::where()|Query::where()]] key-value format.
|
||||
*/
|
||||
private function prepareConditions($targetAttribute, $model, $attribute)
|
||||
{
|
||||
if (is_array($targetAttribute)) {
|
||||
$conditions = [];
|
||||
foreach ($targetAttribute as $k => $v) {
|
||||
$conditions[$v] = is_int($k) ? $model->$v : $model->$k;
|
||||
}
|
||||
} else {
|
||||
$conditions = [$targetAttribute => $model->$attribute];
|
||||
}
|
||||
|
||||
return $conditions;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user