From 3c1f3e20cfa260d1484fece29b255103b3bb6f5e Mon Sep 17 00:00:00 2001 From: vladis84 Date: Tue, 14 Mar 2017 20:37:52 +0500 Subject: [PATCH] Fixes #13704: Fixed `yii\validators\UniqueValidator` to prefix attribute name with model's database table name --- framework/CHANGELOG.md | 1 + framework/validators/UniqueValidator.php | 28 +++++++++++++++++-- .../validators/UniqueValidatorTest.php | 26 +++++++++++++++++ 3 files changed, 52 insertions(+), 3 deletions(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index c231784a41..dcc11495c7 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -50,6 +50,7 @@ Yii Framework 2 Change Log - Bug #4408: Add support for unicode word characters and `+` character in attribute names (sammousa, kmindi) - Bug #10372: Fixed console controller including complex typed arguments in help (sammousa) - Bug #13738: Fixed `getQueryParams()` method in `yii.js` to correctly parse URL with question mark and no query parameters (vladdnepr) +- Bug #13704: Fixed `yii\validators\UniqueValidator` to prefix attribute name with model's database table name (vladis84) 2.0.11.2 February 08, 2017 -------------------------- diff --git a/framework/validators/UniqueValidator.php b/framework/validators/UniqueValidator.php index 0a7c868d10..74464847a7 100644 --- a/framework/validators/UniqueValidator.php +++ b/framework/validators/UniqueValidator.php @@ -10,9 +10,9 @@ namespace yii\validators; use Yii; use yii\base\Model; use yii\db\ActiveQuery; +use yii\db\ActiveRecord; use yii\db\ActiveQueryInterface; use yii\db\ActiveRecordInterface; -use yii\db\Query; use yii\helpers\Inflector; /** @@ -119,7 +119,7 @@ class UniqueValidator extends Validator public function validateAttribute($model, $attribute) { /* @var $targetClass ActiveRecordInterface */ - $targetClass = $this->targetClass === null ? get_class($model) : $this->targetClass; + $targetClass = $this->getTargetClass($model); $targetAttribute = $this->targetAttribute === null ? $attribute : $this->targetAttribute; $rawConditions = $this->prepareConditions($targetAttribute, $model, $attribute); $conditions[] = $this->targetAttributeJunction === 'or' ? 'or' : 'and'; @@ -141,6 +141,15 @@ class UniqueValidator extends Validator } } + /** + * @param Model $model the data model to be validated + * @return string Target class name + */ + private function getTargetClass($model) + { + return $this->targetClass === null ? get_class($model) : $this->targetClass; + } + /** * Checks whether the $model exists in the database. * @@ -234,7 +243,20 @@ class UniqueValidator extends Validator $conditions = [$targetAttribute => $model->$attribute]; } - return $conditions; + if (!$model instanceof ActiveRecord) { + return $conditions; + } + + // Add table prefix for column + $targetClass = $this->getTargetClass($model); + $tableName = $targetClass::tableName(); + $conditionsWithTableName = []; + foreach ($conditions as $columnName => $columnValue) { + $prefixedColumnName = "{$tableName}.$columnName"; + $conditionsWithTableName[$prefixedColumnName] = $columnValue; + } + + return $conditionsWithTableName; } /** diff --git a/tests/framework/validators/UniqueValidatorTest.php b/tests/framework/validators/UniqueValidatorTest.php index 6d25d6058f..c77369d167 100644 --- a/tests/framework/validators/UniqueValidatorTest.php +++ b/tests/framework/validators/UniqueValidatorTest.php @@ -331,6 +331,32 @@ abstract class UniqueValidatorTest extends DatabaseTestCase $result = $this->invokeMethod(new UniqueValidator(), 'prepareConditions', [$targetAttribute, $model, $attribute]); $expected = ['val_attr_b' => 'test value b', 'val_attr_c' => 'test value a']; $this->assertEquals($expected, $result); + + // Add table prefix for column name + $model = Profile::findOne(1); + $attribute = 'id'; + $targetAttribute = 'id'; + $result = $this->invokeMethod(new UniqueValidator(), 'prepareConditions', [$targetAttribute, $model, $attribute]); + $expected = [Profile::tableName() . '.' . $attribute => $model->id]; + $this->assertEquals($expected, $result); + } + + public function testGetTargetClassWithFilledTargetClassProperty() + { + $validator = new UniqueValidator(['targetClass' => Profile::className()]); + $model = new FakedValidationModel(); + $actualTargetClass = $this->invokeMethod($validator, 'getTargetClass', [$model]); + + $this->assertEquals(Profile::className(), $actualTargetClass); + } + + public function testGetTargetClassWithNotFilledTargetClassProperty() + { + $validator = new UniqueValidator(); + $model = new FakedValidationModel(); + $actualTargetClass = $this->invokeMethod($validator, 'getTargetClass', [$model]); + + $this->assertEquals(FakedValidationModel::className(), $actualTargetClass); } public function testPrepareQuery()