diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 960a49a05c..529050b0b2 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -69,6 +69,7 @@ Yii Framework 2 Change Log - Enh #1921: Grid view ActionColumn now allow to name buttons like `{controller/action}` (creocoder) - Enh #1973: `yii message/extract` is now able to generate `.po` files (SergeiKutanov, samdark) - Enh #1984: ActionFilter will now mark event as handled when action run is aborted (cebe) +- Enh #2003: Added `filter` property to `ExistValidator` and `UniqueValidator` to support adding additional filtering conditions (qiangxue) - Enh: Added `favicon.ico` and `robots.txt` to default application templates (samdark) - Enh: Added `Widget::autoIdPrefix` to support prefixing automatically generated widget IDs (qiangxue) - Enh: Support for file aliases in console command 'message' (omnilight) diff --git a/framework/validators/ExistValidator.php b/framework/validators/ExistValidator.php index fb5cc7fe8a..379599f79f 100644 --- a/framework/validators/ExistValidator.php +++ b/framework/validators/ExistValidator.php @@ -54,6 +54,13 @@ class ExistValidator extends Validator * If the key and the value are the same, you can just specify the value. */ public $targetAttribute; + /** + * @var string|array|\Closure additional filter to be applied to the DB query used to check the existence of the attribute value. + * This can be a string or an array representing the additional query condition (refer to [[\yii\db\Query::where()]] + * on the format of query condition), or an anonymous function with the signature `function ($query)`, where `$query` + * is the [[\yii\db\Query|Query]] object that you can modify in the function. + */ + public $filter; /** @@ -72,8 +79,6 @@ class ExistValidator extends Validator */ public function validateAttribute($object, $attribute) { - /** @var \yii\db\ActiveRecordInterface $targetClass */ - $targetClass = $this->targetClass === null ? get_class($object) : $this->targetClass; $targetAttribute = $this->targetAttribute === null ? $attribute : $this->targetAttribute; if (is_array($targetAttribute)) { @@ -92,8 +97,11 @@ class ExistValidator extends Validator } } + $targetClass = $this->targetClass === null ? get_class($object) : $this->targetClass; + $query = $this->createQuery($targetClass, $params); + /** @var \yii\db\ActiveRecordInterface $className */ - if (!$targetClass::find()->where($params)->exists()) { + if (!$query->exists()) { $this->addError($object, $attribute, $this->message); } } @@ -113,10 +121,20 @@ class ExistValidator extends Validator throw new InvalidConfigException('The "targetAttribute" property must be configured as a string.'); } - /** @var \yii\db\ActiveRecordInterface $targetClass */ - $targetClass = $this->targetClass; - $query = $targetClass::find(); - $query->where([$this->targetAttribute => $value]); + $query = $this->createQuery($this->targetClass, [$this->targetAttribute => $value]); + return $query->exists() ? null : [$this->message, []]; } + + protected function createQuery($targetClass, $condition) + { + /** @var \yii\db\ActiveRecordInterface $targetClass */ + $query = $targetClass::find()->where($condition); + if ($this->filter instanceof \Closure) { + call_user_func($this->filter, $query); + } elseif ($this->filter !== null) { + $query->andWhere($this->filter); + } + return $query; + } } diff --git a/framework/validators/UniqueValidator.php b/framework/validators/UniqueValidator.php index 51474b0440..0b311d615f 100644 --- a/framework/validators/UniqueValidator.php +++ b/framework/validators/UniqueValidator.php @@ -51,6 +51,13 @@ class UniqueValidator extends Validator * If the key and the value are the same, you can just specify the value. */ public $targetAttribute; + /** + * @var string|array|\Closure additional filter to be applied to the DB query used to check the uniqueness of the attribute value. + * This can be a string or an array representing the additional query condition (refer to [[\yii\db\Query::where()]] + * on the format of query condition), or an anonymous function with the signature `function ($query)`, where `$query` + * is the [[\yii\db\Query|Query]] object that you can modify in the function. + */ + public $filter; /** * @inheritdoc @@ -91,6 +98,12 @@ class UniqueValidator extends Validator $query = $targetClass::find(); $query->where($params); + if ($this->filter instanceof \Closure) { + call_user_func($this->filter, $query); + } elseif ($this->filter !== null) { + $query->andWhere($this->filter); + } + if (!$object instanceof ActiveRecordInterface || $object->getIsNewRecord()) { // if current $object isn't in the database yet then it's OK just to call exists() $exists = $query->exists();