diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 20eb6f5..b00b525 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -9,6 +9,7 @@ Yii Framework 2 Change Log - Bug #13362: Fixed return value of `yii\caching\MemCache::setValues()` (masterklavi) - Enh #13963: Added tests for yii\behaviors\TimestampBehavior (vladis84) - Enh #13994: Refactored `yii\filters\RateLimiter`. Added tests (vladis84) +- Bug #13842: Fixed ambiguous error sql while using unique validator (vladis84) - Enh #13820: Add new HTTP status code 451 (yyxx9988) - Bug #13671: Fixed error handler trace to work correctly with XDebug (samdark) - Bug #13657: Fixed `yii\helpers\StringHelper::truncateHtml()` skip extra tags at the end (sam002) diff --git a/framework/helpers/ActiveQueryHelper.php b/framework/helpers/ActiveQueryHelper.php new file mode 100644 index 0000000..f136c56 --- /dev/null +++ b/framework/helpers/ActiveQueryHelper.php @@ -0,0 +1,107 @@ +from; + + if (empty($from)) { + $tableNames[] = self::getTableNameForModel($query); + } elseif (is_array($from)) { + $tableNames = array_values($from); + } elseif (is_string($from)) { + $tableNames = preg_split('/\s*,\s*/', trim($from), -1, PREG_SPLIT_NO_EMPTY); + } else { + self::generateNotSupportedTypeFrom($from); + } + + // Clear table alias. + foreach ($tableNames as &$tableName) { + $tableName = preg_replace('/^(\w+)\s*.*$/', '$1', $tableName); + } + + return $tableNames; + } + + /** + * Tables alias calculate on from. + * @param ActiveQuery $query + * @return string[] table alias + */ + public static function getTablesAlias(ActiveQuery $query) + { + $tablesAlias = []; + $from = $query->from; + + if (empty($from)) { + return [self::getTableNameForModel($query)]; + } + + if (is_string($from)) { + $tableNames = preg_split('/\s*,\s*/', trim($from), -1, PREG_SPLIT_NO_EMPTY); + } elseif (is_array($from)) { + $tableNames = $from; + } else { + self::generateNotSupportedTypeFrom($from); + } + + foreach ($tableNames as $alias => $tableName) { + if (is_string($alias)) { + $tablesAlias[] = $alias; + } else { + $tablesAlias[] = self::getAliasForTableName($tableName); + } + } + + return $tablesAlias; + } + + /** + * @param string $tableName + * @return string + */ + private static function getAliasForTableName($tableName) + { + $cleanedTableName = preg_replace('/\'|\"|`|as/u', '', trim($tableName)); + + $alias = preg_replace('/^.+\s+(\w+)$/', '$1', $cleanedTableName); + + return $alias; + } + + /** + * Table name get Model. + * @param ActiveQuery $query + * @return type + */ + private static function getTableNameForModel(ActiveQuery $query) + { + /* @var $modelClass ActiveRecord */ + $modelClass = $query->modelClass; + + return $modelClass::tableName(); + } + + /** + * @param mixed $from + * @throws InvalidConfigException + */ + private static function generateNotSupportedTypeFrom($from) + { + $error = sprintf('Not supported type "$s"', gettype($from)); + + throw new InvalidConfigException($error); + } +} diff --git a/framework/validators/UniqueValidator.php b/framework/validators/UniqueValidator.php index 7446484..d17b5be 100644 --- a/framework/validators/UniqueValidator.php +++ b/framework/validators/UniqueValidator.php @@ -14,6 +14,7 @@ use yii\db\ActiveRecord; use yii\db\ActiveQueryInterface; use yii\db\ActiveRecordInterface; use yii\helpers\Inflector; +use yii\helpers\ActiveQueryHelper; /** * UniqueValidator validates that the attribute value is unique in the specified database table. @@ -249,14 +250,15 @@ class UniqueValidator extends Validator // Add table prefix for column $targetClass = $this->getTargetClass($model); - $tableName = $targetClass::tableName(); - $conditionsWithTableName = []; + $query = $targetClass::find(); + $tableAlias = ActiveQueryHelper::getTablesAlias($query)[0]; + $prefixedConditions = []; foreach ($conditions as $columnName => $columnValue) { - $prefixedColumnName = "{$tableName}.$columnName"; - $conditionsWithTableName[$prefixedColumnName] = $columnValue; + $prefixedColumn = "{$tableAlias}.{$columnName}"; + $prefixedConditions[$prefixedColumn] = $columnValue; } - return $conditionsWithTableName; + return $prefixedConditions; } /** diff --git a/tests/framework/helpers/ActiveQueryHelperTest.php b/tests/framework/helpers/ActiveQueryHelperTest.php new file mode 100644 index 0000000..f78e592 --- /dev/null +++ b/tests/framework/helpers/ActiveQueryHelperTest.php @@ -0,0 +1,92 @@ +assertEquals([Profile::tableName()], $tableNames); + } + + public function testGetTableNames_isFromArray() + { + $query = new ActiveQuery(null); + $query->from = ['prf' => 'profile', 'usr' => 'user']; + + $tableNames = ActiveQueryHelper::getTableNames($query); + + $this->assertEquals(['profile', 'user'], $tableNames); + } + + public function testGetTableNames_isFromString() + { + $query = new ActiveQuery(null); + $query->from = 'profile AS \'prf\', user "usr", `order`, "customer"'; + + $tableNames = ActiveQueryHelper::getTableNames($query); + + $this->assertEquals(['profile', 'user', '`order`', '"customer"'], $tableNames); + } + + public function testGetTableNames_isFromObject_generateException() + { + $query = new ActiveQuery(null); + $query->from = new \stdClass; + + $this->setExpectedException('\yii\base\InvalidConfigException'); + + ActiveQueryHelper::getTableNames($query); + } + + public function testGetTablesAlias_notFilledFrom() + { + $query = new ActiveQuery(Profile::className()); + + $tablesAlias = ActiveQueryHelper::getTablesAlias($query); + + $this->assertEquals([Profile::tableName()], $tablesAlias); + } + + public function testGetTablesAlias_isFromArray() + { + $query = new ActiveQuery(null); + $query->from = ['prf' => 'profile', 'usr' => 'user']; + + $tablesAlias = ActiveQueryHelper::getTablesAlias($query); + + $this->assertEquals(['prf', 'usr'], $tablesAlias); + } + + public function testGetTablesAlias_isFromString() + { + $query = new ActiveQuery(null); + $query->from = 'profile AS \'prf\', user "usr", service srv, order'; + + $tablesAlias = ActiveQueryHelper::getTablesAlias($query); + + $this->assertEquals(['prf', 'usr', 'srv', 'order'], $tablesAlias); + } + + public function testGetTablesAlias_isFromObject_generateException() + { + $query = new ActiveQuery(null); + $query->from = new \stdClass; + + $this->setExpectedException('\yii\base\InvalidConfigException'); + + ActiveQueryHelper::getTablesAlias($query); + } +}