From b4d9c0ee3f6a6b9e346a77231cc054e44b53e088 Mon Sep 17 00:00:00 2001 From: insolita Date: Wed, 5 Aug 2020 13:37:18 +0800 Subject: [PATCH 1/7] fix #15 --- src/lib/ValidationRulesBuilder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/ValidationRulesBuilder.php b/src/lib/ValidationRulesBuilder.php index 5445e964..ba1f5dd2 100644 --- a/src/lib/ValidationRulesBuilder.php +++ b/src/lib/ValidationRulesBuilder.php @@ -165,7 +165,7 @@ private function prepareTypeScope():void if ($attribute->isReadOnly()) { continue; } - if ($attribute->isRequired()) { + if ($attribute->defaultValue===null && $attribute->isRequired()) { $this->typeScope['required'][$attribute->columnName] = $attribute->columnName; } From dd27eccc30f2bec4a8e92d44faf4b03f9773515d Mon Sep 17 00:00:00 2001 From: insolita Date: Wed, 5 Aug 2020 13:44:36 +0800 Subject: [PATCH 2/7] fix #11 --- src/lib/AttributeResolver.php | 37 +++++++++++++++++++++++++++------- src/lib/MigrationBuilder.php | 38 +++++++++++++++++++++++++++++------ 2 files changed, 62 insertions(+), 13 deletions(-) diff --git a/src/lib/AttributeResolver.php b/src/lib/AttributeResolver.php index 7ddd20ad..9955ca25 100644 --- a/src/lib/AttributeResolver.php +++ b/src/lib/AttributeResolver.php @@ -16,8 +16,10 @@ use cebe\yii2openapi\lib\items\DbModel; use Yii; use yii\helpers\Inflector; +use yii\helpers\Json; use yii\helpers\StringHelper; use function in_array; +use function is_string; use function str_replace; use function strpos; use function substr; @@ -130,11 +132,6 @@ protected function resolveProperty($propertyName, SpecObjectInterface $property, ->asHasOne([$foreignPk => $attribute->columnName]); $this->relations[$propertyName] = $relation; } - } else { - //TODO: This case for self-table relations, should be covered later -// $attribute->setPhpType( -// TypeResolver::schemaToPhpType($relatedSchema->type, $relatedSchema->format) -// ); } } @@ -144,8 +141,8 @@ protected function resolveProperty($propertyName, SpecObjectInterface $property, $attribute->setPhpType($phpType) ->setDbType($this->guessDbType($property, ($propertyName === $this->primaryKey))) ->setUnique($property->{CustomSpecAttr::UNIQUE} ?? false) - ->setSize($property->maxLength ?? null) - ->setDefault($property->default ?? null); + ->setSize($property->maxLength ?? null); + $attribute->setDefault($this->guessDefault($property, $attribute)); [$min, $max] = $this->guessMinMax($property); $attribute->setLimits($min, $max, $property->minLength ?? null); if (isset($property->enum) && is_array($property->enum)) { @@ -222,4 +219,30 @@ protected function guessDbType(Schema $property, bool $isPk, bool $isReference = } return TypeResolver::schemaToDbType($property, $isPk); } + + protected function guessDefault(Schema $property, Attribute $attribute) + { + if (!isset($property->default)) { + return null; + } + + if ($attribute->phpType === 'array' && in_array($property->default, ['{}', '[]'])) { + return []; + } + if (is_string($property->default) + && $attribute->phpType === 'array' + && StringHelper::startsWith($attribute->dbType, 'json')) { + try { + return Json::decode($property->default); + } catch (\Throwable $e) { + return []; + } + } + + if ($attribute->phpType === 'integer' && $property->default !== null) { + return (int) $property->default; + } + + return $property->default; + } } diff --git a/src/lib/MigrationBuilder.php b/src/lib/MigrationBuilder.php index 788a52f1..30eaef77 100644 --- a/src/lib/MigrationBuilder.php +++ b/src/lib/MigrationBuilder.php @@ -52,6 +52,9 @@ class MigrationBuilder /**@var bool */ private $isPostgres; + /**@var bool */ + private $isMysql; + /** * @var MigrationModel $migration **/ @@ -84,6 +87,7 @@ public function __construct(Connection $db, DbModel $model) $this->tableSchema = $db->getTableSchema($model->getTableAlias(), true); $this->dbSchema = $db->getSchema(); $this->isPostgres = $this->db->getDriverName() === 'pgsql'; + $this->isMysql = $this->db->getDriverName() === 'mysql'; } public function build():MigrationModel @@ -226,12 +230,7 @@ private function buildColumnChanges(ColumnSchema $current, ColumnSchema $desired $this->migration->addUpCode($isUniqueDesired === true ? $addUnique : $dropUnique) ->addDownCode($isUniqueDesired === true ? $dropUnique : $addUnique); } - $changedAttributes = []; - foreach (['type', 'size', 'allowNull', 'defaultValue', 'enumValues'] as $attr) { - if ($current->$attr !== $desired->$attr) { - $changedAttributes[] = $attr; - } - } + $changedAttributes = $this->compareColumns($current, $desired); if (empty($changedAttributes)) { return; } @@ -362,4 +361,31 @@ private function normalizeTableName($tableName) } return $tableName; } + + private function compareColumns(ColumnSchema $current, ColumnSchema $desired) + { + $changedAttributes = []; + $isMysqlBoolean = $this->isMysql && $current->dbType === 'tinyint(1)' && $desired->type === 'boolean'; + if ($isMysqlBoolean) { + if (\is_bool($desired->defaultValue) || \is_string($desired->defaultValue)) { + $desired->defaultValue = (int) $desired->defaultValue; + } + if ($current->defaultValue !== $desired->defaultValue) { + $changedAttributes[] = 'defaultValue'; + } + if ($current->allowNull !== $desired->allowNull) { + $changedAttributes[] = 'allowNull'; + } + return $changedAttributes; + } + if ($current->phpType === 'integer' && $current->defaultValue !== null) { + $current->defaultValue = (int) $current->defaultValue; + } + foreach (['type', 'size', 'allowNull', 'defaultValue', 'enumValues'] as $attr) { + if ($current->$attr !== $desired->$attr) { + $changedAttributes[] = $attr; + } + } + return $changedAttributes; + } } From 27f0342ab4264fbd399f279ea4552f4d8189b39f Mon Sep 17 00:00:00 2001 From: insolita Date: Wed, 5 Aug 2020 14:24:42 +0800 Subject: [PATCH 3/7] fix #14 --- src/lib/MigrationBuilder.php | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/lib/MigrationBuilder.php b/src/lib/MigrationBuilder.php index 30eaef77..83ad73d7 100644 --- a/src/lib/MigrationBuilder.php +++ b/src/lib/MigrationBuilder.php @@ -303,26 +303,21 @@ private function buildColumnsChangePostgres(ColumnSchema $current, ColumnSchema private function buildRelations():void { $tableName = $this->model->getTableAlias(); - if (empty($this->model->relations)) { - //? Revert existed relations - foreach ($this->tableSchema->foreignKeys as $relation) { - $refTable = array_shift($relation); - $refCol = array_keys($relation)[0]; - $fkCol = $relation[$refCol]; - $fkName = $this->foreignKeyName($this->model->tableName, $fkCol, $refTable, $refCol); - $this->migration->addUpCode(sprintf(self::DROP_FK, $fkName, $tableName), true) - ->addDownCode(sprintf(self::ADD_FK, $fkName, $tableName, $fkCol, $refTable, $refCol)); - if ($refTable !== $this->model->tableName) { - $this->migration->dependencies[$refTable]; - } - } + $existedRelations = []; + foreach ($this->tableSchema->foreignKeys as $relation) { + $refTable = array_shift($relation); + $refCol = array_keys($relation)[0]; + $fkCol = $relation[$refCol]; + $fkName = $this->foreignKeyName($this->model->tableName, $fkCol, $refTable, $refCol); + $existedRelations[$fkName] = ['refTable'=>$refTable, 'refCol'=>$refCol, 'fkCol'=>$fkCol]; } foreach ($this->model->getHasOneRelations() as $relation) { $fkCol = $relation->getColumnName(); $refCol = $relation->getForeignName(); $refTable = $relation->getTableAlias(); $fkName = $this->foreignKeyName($this->model->tableName, $fkCol, $relation->getTableName(), $refCol); - if (isset($tableSchema->foreignKeys[$fkName])) { + if (isset($existedRelations[$fkName])) { + unset($existedRelations[$fkName]); continue; } $this->migration->addUpCode(sprintf(self::ADD_FK, $fkName, $tableName, $fkCol, $refTable, $refCol)) @@ -331,6 +326,11 @@ private function buildRelations():void $this->migration->dependencies[] = $refTable; } } + foreach ($existedRelations as $fkName => $relation) { + ['fkCol'=>$fkCol, 'refCol'=>$refCol, 'refTable'=>$refTable] = $relation; + $this->migration->addUpCode(sprintf(self::DROP_FK, $fkName, $tableName), true) + ->addDownCode(sprintf(self::ADD_FK, $fkName, $tableName, $fkCol, $refTable, $refCol)); + } } private function findUniqueIndexes():array From 9524c1cea413660e03b05ed202d658ee7f34015f Mon Sep 17 00:00:00 2001 From: insolita Date: Sun, 9 Aug 2020 15:36:30 +0800 Subject: [PATCH 4/7] add docker xdebug --- docker-compose.yml | 18 +++++++++--------- tests/docker/Dockerfile | 15 ++++++++++++++- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 1c85402e..a4438459 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,13 +13,14 @@ services: - DB_USER=dbuser - DB_PASSWORD=dbpass - IN_DOCKER=docker + - PHP_XDEBUG_ENABLED=1 + - XDEBUG_CONFIG="remote_host=host.docker.internal" + - PHP_IDE_CONFIG="serverName=Docker" depends_on: - mysql - postgres - maria tty: true - networks: - net: {} mysql: image: mysql:5.7 ports: @@ -32,8 +33,6 @@ services: MYSQL_USER: dbuser MYSQL_PASSWORD: dbpass MYSQL_DATABASE: testdb - networks: - net: {} maria: image: mariadb ports: @@ -47,8 +46,6 @@ services: MYSQL_PASSWORD: dbpass MYSQL_DATABASE: testdb MYSQL_INITDB_SKIP_TZINFO: 1 - networks: - net: {} postgres: image: postgres:12 ports: @@ -61,8 +58,11 @@ services: POSTGRES_USER: dbuser POSTGRES_PASSWORD: dbpass POSTGRES_DB: testdb - networks: - net: {} networks: - net: {} + default: + driver: bridge + ipam: + config: + - subnet: 172.14.0.0/24 + diff --git a/tests/docker/Dockerfile b/tests/docker/Dockerfile index 52955801..fc9fa62d 100644 --- a/tests/docker/Dockerfile +++ b/tests/docker/Dockerfile @@ -29,6 +29,8 @@ RUN apt-get update && \ --no-install-recommends && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \ + && pecl install xdebug-2.9.6 \ + && docker-php-ext-enable xdebug \ && docker-php-ext-install \ zip \ curl \ @@ -45,7 +47,7 @@ RUN apt-get update && \ # Install composer ENV COMPOSER_ALLOW_SUPERUSER=1 \ PHP_USER_ID=33 \ - PHP_ENABLE_XDEBUG=0 \ + PHP_ENABLE_XDEBUG=1 \ COMPOSER_HOME=/root/.composer/ \ PATH=/app:/app/vendor/bin:/root/.composer/vendor/bin:$PATH @@ -56,4 +58,15 @@ RUN curl -o /tmp/composer-setup.php https://getcomposer.org/installer \ && php /tmp/composer-setup.php --no-ansi --install-dir=/usr/local/bin --filename=composer \ && rm -f /tmp/composer-setup.* +# Enable Xdebug +ENV XDEBUGINI_PATH=/usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini + +RUN echo "xdebug.idekey=PHPSTORM" >> $XDEBUGINI_PATH && \ + echo "xdebug.default_enable=1" >> $XDEBUGINI_PATH && \ + echo "xdebug.remote_enable=1" >> $XDEBUGINI_PATH && \ + echo "xdebug.remote_connect_back=0" >> $XDEBUGINI_PATH && \ + echo "xdebug.remote_log=1" >> $XDEBUGINI_PATH && \ + echo "xdebug.remote_port=9000" >> $XDEBUGINI_PATH && \ + echo "xdebug.remote_autostart=0" >> $XDEBUGINI_PATH + WORKDIR /app From b74e85daf42aaa716c3ef523ae3e6ab596fa5062 Mon Sep 17 00:00:00 2001 From: insolita Date: Sun, 9 Aug 2020 15:37:54 +0800 Subject: [PATCH 5/7] refactor migration generator fix #13 - fix quotes - fix migration items order --- src/lib/ColumnToCode.php | 372 ++++++++++------------- src/lib/FakerStubResolver.php | 5 +- src/lib/MigrationBuilder.php | 201 ++++++------ src/lib/MigrationRecordBuilder.php | 171 +++++++++++ tests/unit/MigrationsGeneratorTest.php | 5 +- tests/unit/ValidatorRulesBuilderTest.php | 7 +- 6 files changed, 443 insertions(+), 318 deletions(-) create mode 100644 src/lib/MigrationRecordBuilder.php diff --git a/src/lib/ColumnToCode.php b/src/lib/ColumnToCode.php index 27ff0061..6934f5b9 100644 --- a/src/lib/ColumnToCode.php +++ b/src/lib/ColumnToCode.php @@ -10,21 +10,21 @@ use yii\db\ArrayExpression; use yii\db\ColumnSchema; use yii\db\ColumnSchemaBuilder; +use yii\db\Expression; use yii\db\JsonExpression; use yii\db\mysql\Schema as MySqlSchema; use yii\db\pgsql\Schema as PgSqlSchema; use yii\db\Schema; +use yii\helpers\Json; use yii\helpers\StringHelper; -use function array_key_exists; use function in_array; -use function is_array; -use function method_exists; +use function is_string; +use function preg_replace; +use function sprintf; +use function stripos; use function strpos; use function strtolower; -use function substr; use function trim; -use function ucfirst; -use const PHP_EOL; class ColumnToCode { @@ -64,8 +64,16 @@ class ColumnToCode /** * @var bool */ - private $typeOnly = false; - private $defaultOnly = false; + private $isBuiltinType = false; + + /** + * @var bool + */ + private $isPk = false; + + private $rawParts = ['type' => null, 'nullable' => null, 'default' => null]; + + private $fluentParts = ['type' => null, 'nullable' => null, 'default' => null]; /** * ColumnToCode constructor. @@ -80,113 +88,63 @@ public function __construct(Schema $dbSchema, ColumnSchema $column, bool $column $this->column = $column; $this->columnUnique = $columnUnique; $this->fromDb = $fromDb; + $this->resolve(); } - public function resolveTypeOnly():string - { - $this->typeOnly = true; - return $this->resolve(); - } - - public function resolveDefaultOnly():string - { - $this->defaultOnly = true; - return $this->resolve(); - } - - public function resolve():string + public function getCode(bool $quoted = false):string { - $dbType = $this->column->dbType; - $type = $this->column->type; - //Primary Keys - if (array_key_exists($type, self::PK_TYPE_MAP)) { - return '$this->' . self::PK_TYPE_MAP[$type]; + if ($this->isPk) { + return '$this->' . $this->fluentParts['type']; } - if (array_key_exists($dbType, self::PK_TYPE_MAP)) { - return '$this->' . self::PK_TYPE_MAP[$dbType]; + if ($this->isBuiltinType) { + $parts = [$this->fluentParts['type'], $this->fluentParts['nullable'], $this->fluentParts['default']]; + if ($this->columnUnique) { + $parts[] = 'unique()'; + } + array_unshift($parts, '$this'); + return implode('->', array_filter(array_map('trim', $parts), 'trim')); } - if ($this->fromDb === true) { - $categoryType = (new ColumnSchemaBuilder(''))->categoryMap[$type] ?? ''; + if (!$this->rawParts['default']) { + $default = ''; + } elseif ($this->isPostgres() && $this->isEnum()) { + $default = $this->rawParts['default'] ? ' DEFAULT ' . self::escapeQuotes(trim($this->rawParts['default'])) : ''; } else { - $categoryType = (new ColumnSchemaBuilder(''))->categoryMap[$dbType] ?? ''; - } - - $columnTypeMethod = 'resolve' . ucfirst($categoryType) . 'Type'; - - if (StringHelper::startsWith($dbType, 'enum')) { - $columnTypeMethod = 'resolveEnumType'; - } - if (StringHelper::startsWith($dbType, 'set')) { - $columnTypeMethod = 'resolveSetType'; - } - if (StringHelper::startsWith($dbType, 'tsvector')) { - $columnTypeMethod = 'resolveTsvectorType'; - } - if (isset($column->dimension) && $column->dimension > 0) { - $columnTypeMethod = 'resolveArrayType'; + $default = $this->rawParts['default'] ? ' DEFAULT ' . trim($this->rawParts['default']) : ''; } - if (method_exists($this, $columnTypeMethod)) { - return $this->$columnTypeMethod(); + $code = $this->rawParts['type'] . ' ' . $this->rawParts['nullable'] . $default; + if ($this->isMysql() && $this->isEnum()) { + return $quoted ? '"' . str_replace("\'", "'", $code) . '"' : $code; } - - return $categoryType && !$this->defaultOnly? $this->resolveCommon() : $this->resolveRaw(); + return $quoted ? "'" . $code . "'" : $code; } - private function buildRawDefaultValue():string + public function getType():string { - $value = $this->column->defaultValue; - $nullable = $this->column->allowNull; - $isJson = in_array($this->column->dbType, ['json', 'jsonb']); - if ($value === null) { - return $nullable === true ? 'DEFAULT NULL' : ''; - } - switch (gettype($value)) { - case 'integer': - return 'DEFAULT ' . $value; - case 'object': - if ($value instanceof JsonExpression) { - return 'DEFAULT '.self::defaultValueJson($value->getValue()); - } - if ($value instanceof ArrayExpression) { - return 'DEFAULT '.self::defaultValueArray($value->getValue()); - } - return 'DEFAULT ' .(string) $value; - case 'double': - // ensure type cast always has . as decimal separator in all locales - return 'DEFAULT ' . str_replace(',', '.', (string)$value); - case 'boolean': - return 'DEFAULT ' . ($value ? 'TRUE' : 'FALSE'); - case 'array': - return $isJson? 'DEFAULT '.self::defaultValueJson($value) - :'DEFAULT '.self::defaultValueArray($value); - default: - if (stripos($value, 'NULL::') !== false) { - return 'DEFAULT NULL'; - } - return 'DEFAULT '.self::wrapQuotes($value); + if ($this->isEnum() && $this->isPostgres()) { + return "'".sprintf('enum_%1$s USING %1$s::enum_%1$s', $this->column->name)."'"; } + return $this->isBuiltinType ? '$this->' . $this->fluentParts['type'] : "'".$this->rawParts['type']."'"; } - private static function defaultValueJson(array $value):string + public function getDefaultValue():?string { - return "'".\json_encode($value)."'"; + return $this->rawParts['default']; } - private static function defaultValueArray(array $value):string + + public function isJson():bool { - return "'{".trim(\json_encode($value), '[]')."}'"; + return in_array(strtolower($this->column->dbType), ['json', 'jsonb'], true); } - public static function escapeQuotes(string $str):string + + public function isEnum():bool { - return str_replace(["'", '"', '$'], ["\\'", "\\'", '\$'], $str); + return StringHelper::startsWith($this->column->dbType, 'enum'); } - public static function wrapQuotesOnlyRaw(string $code, bool $escapeQuotes = false):string + public static function escapeQuotes(string $str):string { - if (strpos($code, '$this->') === false) { - return $escapeQuotes ? '"' . self::escapeQuotes($code) . '"' : '"' . $code . '"'; - } - return $code; + return str_replace(["'", '"', '$'], ["\\'", "\\'", '\$'], $str); } public static function wrapQuotes(string $str, string $quotes = "'", bool $escape = true):string @@ -197,175 +155,167 @@ public static function wrapQuotes(string $str, string $quotes = "'", bool $escap return $quotes . $str . $quotes; } - public static function enumToString(array $enum): string + public static function enumToString(array $enum):string { - $items = implode(",", array_map(function ($v) { - return self::wrapQuotes($v); - }, $enum)); + $items = implode(", ", array_map('self::wrapQuotes', $enum)); return self::escapeQuotes($items); } - private function resolveCommon():string + public static function mysqlEnumToString(array $enum):string { - $size = $this->column->size ? '(' . $this->column->size . ')' : '()'; - $default = $this->buildDefaultValue(); - $nullable = $this->column->allowNull === true ? 'null()' : 'notNull()'; - if (array_key_exists($this->column->dbType, self::INT_TYPE_MAP)) { - $type = self::INT_TYPE_MAP[$this->column->dbType] . $size; - } elseif (array_key_exists($this->column->type, self::INT_TYPE_MAP)) { - $type = self::INT_TYPE_MAP[$this->column->type] . $size; - } else { - $type = $this->column->type . $size; - } - return $this->buildString($type, $default, $nullable); + return implode(', ', array_map('self::wrapQuotes', $enum)); } - private function resolveRaw():string + private static function defaultValueJson(array $value):string { - $nullable = $this->column->allowNull ? 'NULL' : 'NOT NULL'; - $type = $this->column->dbType; - $default = $this->isDefaultAllowed() ? $this->buildRawDefaultValue(): ''; - if ($this->defaultOnly) { - return $default; - } + return "\\'" . new Expression(Json::encode($value)) . "\\'"; + } - $size = $this->column->size ? '(' . $this->column->size . ')' : ''; - $type = strpos($type, '(') === false ? $type . $size : $type; - if ($this->typeOnly === true) { - return $type; - } - $columns = $nullable . ($default ? ' ' . trim($default) : ''); - return $type . ' ' . $columns; + private static function defaultValueArray(array $value):string + { + return "'{" . str_replace('"', "\"", trim(Json::encode($value), '[]')) . "}'"; } - private function resolveEnumType():string + private function resolve():void { - if (!$this->column->enumValues || !is_array($this->column->enumValues)) { - return ''; + $dbType = strtolower($this->column->dbType); + $dbType = preg_replace('~(.+)\(\d+\)~', '$1', $dbType); + $type = $this->column->type; + //Primary Keys + if (array_key_exists($type, self::PK_TYPE_MAP)) { + $this->rawParts['type'] = $type; + $this->fluentParts['type'] = self::PK_TYPE_MAP[$type]; + $this->isPk = true; + return; } - $default = $this->buildRawDefaultValue(); - if ($this->defaultOnly) { - return $default; + if (array_key_exists($dbType, self::PK_TYPE_MAP)) { + $this->rawParts['type'] = $dbType; + $this->fluentParts['type'] = self::PK_TYPE_MAP[$dbType]; + $this->isPk = true; + return; } - $nullable = $this->column->allowNull ? 'NULL' : 'NOT NULL'; - if ($this->isPostgres()) { - $type = $this->typeOnly - ? \sprintf('enum_%1$s USING %1$s::enum_%1$s', $this->column->name) - : 'enum_'.$this->column->name; - } else { - $values = array_map( - function ($v) { - return self::wrapQuotes($v); - }, - $this->column->enumValues - ); - $type = "enum(" . implode(', ', $values) . ")"; + + if ($dbType === 'varchar') { + $type = $dbType = 'string'; } - if ($this->typeOnly === true) { - return $type; + if ($this->fromDb === true) { + $this->isBuiltinType = isset((new ColumnSchemaBuilder(''))->categoryMap[$type]); + } else { + $this->isBuiltinType = isset((new ColumnSchemaBuilder(''))->categoryMap[$dbType]); } - $columns = $nullable . ($default ? ' ' . trim($default) : ''); - return $type . ' ' . $columns; - } - - private function resolveSetType():string - { - $default = $this->buildRawDefaultValue(); - if ($this->defaultOnly) { - return $default; + $fluentSize = $this->column->size ? '(' . $this->column->size . ')' : '()'; + $rawSize = $this->column->size ? '(' . $this->column->size . ')' : ''; + $this->rawParts['nullable'] = $this->column->allowNull ? 'NULL' : 'NOT NULL'; + $this->fluentParts['nullable'] = $this->column->allowNull === true ? 'null()' : 'notNull()'; + if (array_key_exists($dbType, self::INT_TYPE_MAP)) { + $this->fluentParts['type'] = self::INT_TYPE_MAP[$dbType] . $fluentSize; + $this->rawParts['type'] = + $this->column->dbType . (strpos($this->column->dbType, '(') !== false ? '' : $rawSize); + } elseif (array_key_exists($type, self::INT_TYPE_MAP)) { + $this->fluentParts['type'] = self::INT_TYPE_MAP[$type] . $fluentSize; + $this->rawParts['type'] = + $this->column->dbType . (strpos($this->column->dbType, '(') !== false ? '' : $rawSize); + } elseif ($this->isEnum()) { + $this->resolveEnumType(); + } else { + $this->fluentParts['type'] = $type . $fluentSize; + $this->rawParts['type'] = + $this->column->dbType . (strpos($this->column->dbType, '(') !== false ? '' : $rawSize); } - $type = $this->column->dbType; - $nullable = $this->column->allowNull ? 'NULL' : 'NOT NULL'; - $columns = $nullable . ($default ? ' ' . trim($default) : ''); - return $type . ' ' . $columns; + $this->resolveDefaultValue(); } - private function resolveArrayType():string + private function resolveEnumType():void { - $default = $this->buildDefaultValue(); - if ($this->defaultOnly) { - return $default; + if ($this->isPostgres()) { + $this->rawParts['type'] = 'enum_' . $this->column->name; + return; } - $nullable = $this->column->allowNull === true ? 'null()' : 'notNull()'; - $type = $this->column->dbType; - return $this->buildString($type, $default, $nullable); + $this->rawParts['type'] = 'enum(' . self::mysqlEnumToString($this->column->enumValues) . ')'; } - private function resolveTsvectorType():string + private function resolveDefaultValue():void { - //\var_dump($this->column); - return $this->resolveRaw(); - } - - private function buildDefaultValue():string - { - $value = $this->column->defaultValue; if (!$this->isDefaultAllowed()) { - return ''; + return; } - if ($value === null) { - return ($this->column->allowNull === true)? 'defaultValue(null)' : ''; + $value = $this->column->defaultValue; + if ($value === null || (is_string($value) && (stripos($value, 'null::') !== false))) { + $this->fluentParts['default'] = ($this->column->allowNull === true) ? 'defaultValue(null)' : ''; + $this->rawParts['default'] = ($this->column->allowNull === true) ? 'NULL' : ''; + return; } - switch (gettype($value)) { case 'integer': - return 'defaultValue(' . (int)$value . ')'; + $this->fluentParts['default'] = 'defaultValue(' . $value . ')'; + $this->rawParts['default'] = $value; + break; case 'double': case 'float': // ensure type cast always has . as decimal separator in all locales - return 'defaultValue("' . str_replace(',', '.', (string)$value) . '")'; + $value = str_replace(',', '.', (string)$value); + $this->fluentParts['default'] = 'defaultValue("' . $value . '")'; + $this->rawParts['default'] = $value; + break; case 'boolean': - return $value === true ? 'defaultValue(true)' : 'defaultValue(false)'; + $this->fluentParts['default'] = (bool)$value === true ? 'defaultValue(true)' : 'defaultValue(false)'; + if ($this->isPostgres()) { + $this->rawParts['default'] = ((bool)$value === true ? "'t'" : "'f'"); + } else { + $this->rawParts['default'] = ((bool)$value === true ? '1' : '0'); + } + break; case 'object': if ($value instanceof JsonExpression) { - return 'defaultValue(' . json_encode($value->getValue()) . ')'; + $this->fluentParts['default'] = "defaultValue('" . Json::encode($value->getValue()) . "')"; + $this->rawParts['default'] = self::defaultValueJson($value->getValue()); + } elseif ($value instanceof ArrayExpression) { + $this->fluentParts['default'] = "defaultValue('" . Json::encode($value->getValue()) . "')"; + $this->rawParts['default'] = self::defaultValueArray($value->getValue()); + } else { + $this->fluentParts['default'] = 'defaultExpression("' . self::escapeQuotes((string)$value) . '")'; } - return 'defaultExpression("' . self::escapeQuotes((string)$value) . '")'; + break; case 'array': - return (string)'defaultValue(' . json_encode($value) . ')'; + $this->fluentParts['default'] = "defaultValue('" . Json::encode($value) . "')"; + $this->rawParts['default'] = $this->isJson() + ? self::defaultValueJson($value) + : self::defaultValueArray($value); + break; default: - { - if (stripos($value, 'NULL::') !== false) { - return ''; - } - if ( - StringHelper::startsWith($value, 'CURRENT') + $isExpression = StringHelper::startsWith($value, 'CURRENT') || StringHelper::startsWith($value, 'LOCAL') - || substr($value, -1, 1) === ')') { - //TIMESTAMP MARKER OR DATABASE FUNCTION - return 'defaultExpression("' . self::escapeQuotes((string)$value) . '")'; + || substr($value, -1, 1) === ')'; + if ($isExpression) { + $this->fluentParts['default'] = 'defaultExpression("' . self::escapeQuotes((string)$value) . '")'; + } else { + $this->fluentParts['default'] = 'defaultValue("' . self::escapeQuotes((string)$value) . '")'; + } + $this->rawParts['default'] = self::wrapQuotes($value); + if ($this->isMysql() && $this->isEnum()) { + $this->rawParts['default'] = self::escapeQuotes($this->rawParts['default']); } - return 'defaultValue("' . self::escapeQuotes((string)$value) . '")'; - } - } - } - - private function buildString(string $type, $default = '', $nullable = ''):string - { - if ($this->typeOnly === true) { - $columnParts = [$type]; - } else { - $columnParts = [$type, $nullable, $default]; - if ($this->columnUnique) { - $columnParts[] = 'unique()'; - } } - array_unshift($columnParts, '$this'); - return implode('->', array_filter(array_map('trim', $columnParts), 'trim')); } private function isDefaultAllowed():bool { $type = strtolower($this->column->dbType); - if ($this->dbSchema instanceof MySqlSchema && in_array($type, ['blob', 'geometry', 'text', 'json'])) { - //Only mysql specific restriction, mariadb can it - return strpos($this->dbSchema->getServerVersion(), 'MariaDB') !== false; - } - return true; + return !($this->isMysql() && !$this->isMariaDb() && in_array($type, ['blob', 'geometry', 'text', 'json'])); } private function isPostgres():bool { return $this->dbSchema instanceof PgSqlSchema; } + + private function isMysql():bool + { + return $this->dbSchema instanceof MySqlSchema; + } + + private function isMariaDb():bool + { + return strpos($this->dbSchema->getServerVersion(), 'MariaDB') !== false; + } } diff --git a/src/lib/FakerStubResolver.php b/src/lib/FakerStubResolver.php index 2a12dc51..cae49831 100644 --- a/src/lib/FakerStubResolver.php +++ b/src/lib/FakerStubResolver.php @@ -9,6 +9,8 @@ use cebe\openapi\SpecObjectInterface; use cebe\yii2openapi\lib\items\Attribute; +use yii\helpers\VarDumper; +use const PHP_EOL; /** * Guess faker for attribute @@ -68,7 +70,8 @@ private function fakeForString():?string return $formats[$this->property->format]; } if (!empty($this->property->enum) && is_array($this->property->enum)) { - return '$faker->randomElement(' . var_export($this->property->enum, true) . ')'; + $items = \str_replace([PHP_EOL, ' ',',]'], ['', '', ']'], VarDumper::export($this->property->enum)); + return '$faker->randomElement(' . $items . ')'; } if ($this->attribute->columnName === 'title' && $this->attribute->size diff --git a/src/lib/MigrationBuilder.php b/src/lib/MigrationBuilder.php index 83ad73d7..74d5c155 100644 --- a/src/lib/MigrationBuilder.php +++ b/src/lib/MigrationBuilder.php @@ -12,27 +12,16 @@ use yii\base\NotSupportedException; use yii\db\ColumnSchema; use yii\db\Connection; -use yii\helpers\VarDumper; use function array_intersect; use function array_map; use function in_array; +use function is_bool; +use function is_string; use function sprintf; +use function str_replace; class MigrationBuilder { - public const INDENT = ' '; - private const ADD_TABLE = self::INDENT . "\$this->createTable('%s', %s);"; - private const DROP_TABLE = self::INDENT . "\$this->dropTable('%s');"; - private const ADD_COLUMN = self::INDENT . "\$this->addColumn('%s', '%s', %s);"; - private const DROP_COLUMN = self::INDENT . "\$this->dropColumn('%s', '%s');"; - private const ALTER_COLUMN = self::INDENT . "\$this->alterColumn('%s', '%s', %s);"; - private const ADD_FK = self::INDENT . "\$this->addForeignKey('%s', '%s', '%s', '%s', '%s');"; - private const DROP_FK = self::INDENT . "\$this->dropForeignKey('%s', '%s');"; - private const ADD_PK = self::INDENT . "\$this->addPrimaryKey('%s', '%s', '%s');"; - private const ADD_ENUM = self::INDENT . "\$this->execute('CREATE TYPE enum_%s AS ENUM(%s)');"; - private const DROP_ENUM = self::INDENT . "\$this->execute('DROP TYPE enum_%s');"; - private const ADD_UNIQUE = self::INDENT . "\$this->createIndex('%s', '%s', '%s', true);"; - private const DROP_INDEX = self::INDENT . "\$this->dropIndex('%s', '%s');"; /** * @var \yii\db\Connection @@ -76,18 +65,24 @@ class MigrationBuilder private $newColumns; /** - * @var \yii\db\Schema + * @var \cebe\yii2openapi\lib\MigrationRecordBuilder */ - private $dbSchema; + private $recordBuilder; + /** + * MigrationBuilder constructor. + * @param \yii\db\Connection $db + * @param \cebe\yii2openapi\lib\items\DbModel $model + * @throws \yii\base\NotSupportedException + */ public function __construct(Connection $db, DbModel $model) { $this->db = $db; $this->model = $model; $this->tableSchema = $db->getTableSchema($model->getTableAlias(), true); - $this->dbSchema = $db->getSchema(); $this->isPostgres = $this->db->getDriverName() === 'pgsql'; $this->isMysql = $this->db->getDriverName() === 'mysql'; + $this->recordBuilder = new MigrationRecordBuilder($db->getSchema()); } public function build():MigrationModel @@ -100,23 +95,15 @@ public function buildFresh():MigrationModel $this->migration = new MigrationModel($this->model, true); $this->uniqueColumns = $this->model->getUniqueColumnsList(); $this->newColumns = $this->model->attributesToColumnSchema(); + $builder = $this->recordBuilder; $tableName = $this->model->getTableAlias(); - $codeColumns = VarDumper::export(array_map( - function (ColumnSchema $column) { - $isUnique = in_array($column->name, $this->uniqueColumns, true); - return $this->columnToCode($column, $isUnique, false); - }, - $this->model->attributesToColumnSchema() - )); - $codeColumns = str_replace(PHP_EOL, PHP_EOL . self::INDENT, $codeColumns); - $this->migration->addUpCode(sprintf(self::ADD_TABLE, $this->model->getTableAlias(), $codeColumns)) - ->addDownCode(sprintf(self::DROP_TABLE, $this->model->getTableAlias())); + $this->migration->addUpCode($builder->createTable($tableName, $this->newColumns, $this->uniqueColumns)) + ->addDownCode($builder->dropTable($tableName)); if ($this->isPostgres) { $enums = $this->model->getEnumAttributes(); foreach ($enums as $attr) { - $items = ColumnToCode::enumToString($attr->enumValues); - $this->migration->addUpCode(sprintf(self::ADD_ENUM, $attr->columnName, $items), true) - ->addDownCode(sprintf(self::DROP_ENUM, $attr->columnName)); + $this->migration->addUpCode($builder->createEnum($attr->columnName, $attr->enumValues), true) + ->addDownCode($builder->dropEnum($attr->columnName)); } } foreach ($this->model->getHasOneRelations() as $relation) { @@ -124,8 +111,8 @@ function (ColumnSchema $column) { $refCol = $relation->getForeignName(); $refTable = $relation->getTableAlias(); $fkName = $this->foreignKeyName($this->model->tableName, $fkCol, $relation->getTableName(), $refCol); - $this->migration->addUpCode(sprintf(self::ADD_FK, $fkName, $tableName, $fkCol, $refTable, $refCol)) - ->addDownCode(sprintf(self::DROP_FK, $fkName, $tableName)); + $this->migration->addUpCode($builder->addFk($fkName, $tableName, $fkCol, $refTable, $refCol)) + ->addDownCode($builder->dropFk($fkName, $tableName)); if ($relation->getTableName() !== $this->model->tableName) { $this->migration->dependencies[] = $refTable; } @@ -180,14 +167,12 @@ private function buildColumnsCreation(array $columns):void // TODO: Avoid pk changes, or previous pk should be dropped before } $isUnique = in_array($column->name, $this->uniqueColumns, true); - $columnCode = ColumnToCode::wrapQuotesOnlyRaw($this->columnToCode($column, $isUnique, false)); $tableName = $this->model->getTableAlias(); - $this->migration->addUpCode(sprintf(self::ADD_COLUMN, $tableName, $column->name, $columnCode)) - ->addDownCode(sprintf(self::DROP_COLUMN, $tableName, $column->name)); + $this->migration->addUpCode($this->recordBuilder->addColumn($tableName, $column, $isUnique)) + ->addDownCode($this->recordBuilder->dropColumn($tableName, $column->name)); if ($this->isPostgres && $column->dbType === 'enum') { - $items = ColumnToCode::enumToString($column->enumValues); - $this->migration->addUpCode(sprintf(self::ADD_ENUM, $column->name, $items), true) - ->addDownCode(sprintf(self::DROP_ENUM, $column->name)); + $this->migration->addUpCode($this->recordBuilder->createEnum($column->name, $column->enumValues), true) + ->addDownCode($this->recordBuilder->dropEnum($column->name), true); } } } @@ -199,17 +184,15 @@ private function buildColumnsDrop(array $columns):void { foreach ($columns as $column) { if ($column->isPrimaryKey) { - // TODO: drop pk index and foreign keys before or avoid drop + // TODO: drop pk index and foreign keys before or avoid drop? } $isUnique = in_array($column->name, $this->currentUniqueColumns, true); - $columnCode = $this->columnToCode($column, $isUnique, true); $tableName = $this->model->getTableAlias(); - $this->migration->addDownCode(sprintf(self::ADD_COLUMN, $tableName, $column->name, $columnCode)) - ->addUpCode(sprintf(self::DROP_COLUMN, $tableName, $column->name)); + $this->migration->addDownCode($this->recordBuilder->addDbColumn($tableName, $column, $isUnique)) + ->addUpCode($this->recordBuilder->dropColumn($tableName, $column->name)); if ($this->isPostgres && $column->dbType === 'enum') { - $items = ColumnToCode::enumToString($column->enumValues); - $this->migration->addDownCode(sprintf(self::ADD_ENUM, $column->name, $items, true)) - ->addUpCode(sprintf(self::DROP_ENUM, $column->name), true); + $this->migration->addDownCode($this->recordBuilder->createEnum($column->name, $column->enumValues)) + ->addUpCode($this->recordBuilder->dropEnum($column->name), true); } } } @@ -224,18 +207,13 @@ private function buildColumnChanges(ColumnSchema $current, ColumnSchema $desired } $columnName = $current->name; $tableName = $this->model->getTableAlias(); - if ($isUniqueCurrent !== $isUniqueDesired) { - $addUnique = sprintf(self::ADD_UNIQUE, 'unique_' . $columnName, $tableName, $columnName); - $dropUnique = sprintf(self::DROP_INDEX, 'unique_' . $columnName, $tableName); - $this->migration->addUpCode($isUniqueDesired === true ? $addUnique : $dropUnique) - ->addDownCode($isUniqueDesired === true ? $dropUnique : $addUnique); - } $changedAttributes = $this->compareColumns($current, $desired); if (empty($changedAttributes)) { return; } if ($this->isPostgres) { $this->buildColumnsChangePostgres($current, $desired, $changedAttributes); + $this->addUniqueIndex($isUniqueCurrent, $isUniqueDesired, $tableName, $columnName); return; } $newColumn = clone $current; @@ -245,58 +223,58 @@ private function buildColumnChanges(ColumnSchema $current, ColumnSchema $desired if (!empty($newColumn->enumValues)) { $newColumn->dbType = 'enum'; } - $upCode = $this->columnToCode($newColumn, false, true); //unique marks solved in separated queries - $downCode = $this->columnToCode($current, false, true); - $upCode = ColumnToCode::wrapQuotesOnlyRaw($upCode); - $downCode = ColumnToCode::wrapQuotesOnlyRaw($downCode); - $this->migration->addUpCode(sprintf(self::ALTER_COLUMN, $tableName, $columnName, $upCode)) - ->addDownCode(sprintf(self::ALTER_COLUMN, $tableName, $columnName, $downCode)); + $this->migration->addUpCode($this->recordBuilder->alterColumn($tableName, $newColumn)) + ->addDownCode($this->recordBuilder->alterColumn($tableName, $current)); + $this->addUniqueIndex($isUniqueCurrent, $isUniqueDesired, $tableName, $columnName); } private function buildColumnsChangePostgres(ColumnSchema $current, ColumnSchema $desired, array $changes):void { $tableName = $this->model->getTableAlias(); - $columnName = $desired->name; - $upCodes = $downCodes = []; $isChangeToEnum = $current->type !== $desired->type && !empty($desired->enumValues); $isChangeFromEnum = $current->type !== $desired->type && !empty($current->enumValues); + $isChangedEnum = $current->type === $desired->type && !empty($current->enumValues); + if ($isChangedEnum) { + // Generation for change enum values not supported. Do it manually + // This action require several steps and can't be applied during single transaction + return; + } if ($isChangeToEnum) { - $items = ColumnToCode::enumToString($desired->enumValues); - $this->migration->addUpCode(sprintf(self::ADD_ENUM, $desired->name, $items), true); + $this->migration->addUpCode($this->recordBuilder->createEnum($desired->name, $desired->enumValues), true); } if ($isChangeFromEnum) { - $this->migration->addUpCode(sprintf(self::DROP_ENUM, $current->name)); + $this->migration->addUpCode($this->recordBuilder->dropEnum($current->name)); } if (!empty(array_intersect(['type', 'size'], $changes))) { - $upCodes[] = (new ColumnToCode($this->dbSchema, $desired, false))->resolveTypeOnly(); - $downCodes[] = (new ColumnToCode($this->dbSchema, $current, false, true))->resolveTypeOnly(); + $this->migration->addUpCode($this->recordBuilder->alterColumnType($tableName, $desired)); + $this->migration->addDownCode($this->recordBuilder->alterColumnTypeFromDb($tableName, $current)); } if (in_array('allowNull', $changes, true)) { - $upCodes[] = $desired->allowNull === true ? 'DROP NOT NULL' : 'SET NOT NULL'; - $downCodes[] = $desired->allowNull === true ? 'SET NOT NULL' : 'DROP NOT NULL'; + if ($desired->allowNull === true) { + $this->migration->addUpCode($this->recordBuilder->dropColumnNotNull($tableName, $desired)); + $this->migration->addDownCode($this->recordBuilder->setColumnNotNull($tableName, $current), true); + } else { + $this->migration->addUpCode($this->recordBuilder->setColumnNotNull($tableName, $desired)); + $this->migration->addDownCode($this->recordBuilder->dropColumnNotNull($tableName, $current), true); + } } if (in_array('defaultValue', $changes, true)) { - $upCodes[] = $desired->defaultValue !== null - ? 'SET ' . (new ColumnToCode($this->dbSchema, $desired, false))->resolveDefaultOnly() - : 'DROP DEFAULT'; - $downCodes[] = $current->defaultValue !== null - ? 'SET ' . (new ColumnToCode($this->dbSchema, $current, false))->resolveDefaultOnly() - : 'DROP DEFAULT'; - } - foreach ($upCodes as $upCode) { - $upCode = ColumnToCode::wrapQuotesOnlyRaw($upCode); - $this->migration->addUpCode(sprintf(self::ALTER_COLUMN, $tableName, $columnName, $upCode)); - } - foreach ($downCodes as $downCode) { - $downCode = ColumnToCode::wrapQuotesOnlyRaw($downCode); - $this->migration->addDownCode(sprintf(self::ALTER_COLUMN, $tableName, $columnName, $downCode), true); + $upCode = $desired->defaultValue === null + ? $this->recordBuilder->dropColumnDefault($tableName, $desired) + : $this->recordBuilder->setColumnDefault($tableName, $desired); + $downCode = $current->defaultValue === null + ? $this->recordBuilder->dropColumnDefault($tableName, $current) + : $this->recordBuilder->setColumnDefaultFromDb($tableName, $current); + if ($upCode && $downCode) { + $this->migration->addUpCode($upCode)->addDownCode($downCode, true); + } } if ($isChangeFromEnum) { - $items = ColumnToCode::enumToString($current->enumValues); - $this->migration->addDownCode(sprintf(self::ADD_ENUM, $current->name, $items), true); + $this->migration + ->addDownCode($this->recordBuilder->createEnum($current->name, $current->enumValues), true); } if ($isChangeToEnum) { - $this->migration->addDownCode(sprintf(self::DROP_ENUM, $desired->name), true); + $this->migration->addDownCode($this->recordBuilder->dropEnum($current->name), true); } } @@ -304,12 +282,11 @@ private function buildRelations():void { $tableName = $this->model->getTableAlias(); $existedRelations = []; - foreach ($this->tableSchema->foreignKeys as $relation) { - $refTable = array_shift($relation); + foreach ($this->tableSchema->foreignKeys as $fkName => $relation) { + $refTable = $this->unPrefixTableName(array_shift($relation)); $refCol = array_keys($relation)[0]; $fkCol = $relation[$refCol]; - $fkName = $this->foreignKeyName($this->model->tableName, $fkCol, $refTable, $refCol); - $existedRelations[$fkName] = ['refTable'=>$refTable, 'refCol'=>$refCol, 'fkCol'=>$fkCol]; + $existedRelations[$fkName] = ['refTable' => $refTable, 'refCol' => $refCol, 'fkCol' => $fkCol]; } foreach ($this->model->getHasOneRelations() as $relation) { $fkCol = $relation->getColumnName(); @@ -320,16 +297,18 @@ private function buildRelations():void unset($existedRelations[$fkName]); continue; } - $this->migration->addUpCode(sprintf(self::ADD_FK, $fkName, $tableName, $fkCol, $refTable, $refCol)) - ->addDownCode(sprintf(self::DROP_FK, $fkName, $tableName)); + $this->migration + ->addUpCode($this->recordBuilder->addFk($fkName, $tableName, $fkCol, $refTable, $refCol)) + ->addDownCode($this->recordBuilder->dropFk($fkName, $tableName)); if ($relation->getTableName() !== $this->model->tableName) { $this->migration->dependencies[] = $refTable; } } foreach ($existedRelations as $fkName => $relation) { - ['fkCol'=>$fkCol, 'refCol'=>$refCol, 'refTable'=>$refTable] = $relation; - $this->migration->addUpCode(sprintf(self::DROP_FK, $fkName, $tableName), true) - ->addDownCode(sprintf(self::ADD_FK, $fkName, $tableName, $fkCol, $refTable, $refCol)); + ['fkCol' => $fkCol, 'refCol' => $refCol, 'refTable' => $refTable] = $relation; + $this->migration + ->addUpCode($this->recordBuilder->dropFk($fkName, $tableName), true) + ->addDownCode($this->recordBuilder->addFk($fkName, $tableName, $fkCol, $refTable, $refCol), true); } } @@ -342,11 +321,6 @@ private function findUniqueIndexes():array } } - private function columnToCode(ColumnSchema $column, bool $isUnique, bool $fromDb = false):string - { - return (new ColumnToCode($this->dbSchema, $column, $isUnique, $fromDb))->resolve(); - } - private function foreignKeyName(string $table, string $column, string $foreignTable, string $foreignColumn):string { $table = $this->normalizeTableName($table); @@ -354,7 +328,7 @@ private function foreignKeyName(string $table, string $column, string $foreignTa return "fk_{$table}_{$column}_{$foreignTable}_{$foreignColumn}"; } - private function normalizeTableName($tableName) + private function normalizeTableName(string $tableName):string { if (preg_match('~^{{%?(.*)}}$~', $tableName, $m)) { return $m[1]; @@ -362,13 +336,18 @@ private function normalizeTableName($tableName) return $tableName; } + private function unPrefixTableName(string $tableName):string + { + return str_replace($this->db->tablePrefix, '', $tableName); + } + private function compareColumns(ColumnSchema $current, ColumnSchema $desired) { $changedAttributes = []; $isMysqlBoolean = $this->isMysql && $current->dbType === 'tinyint(1)' && $desired->type === 'boolean'; if ($isMysqlBoolean) { - if (\is_bool($desired->defaultValue) || \is_string($desired->defaultValue)) { - $desired->defaultValue = (int) $desired->defaultValue; + if (is_bool($desired->defaultValue) || is_string($desired->defaultValue)) { + $desired->defaultValue = (int)$desired->defaultValue; } if ($current->defaultValue !== $desired->defaultValue) { $changedAttributes[] = 'defaultValue'; @@ -379,7 +358,7 @@ private function compareColumns(ColumnSchema $current, ColumnSchema $desired) return $changedAttributes; } if ($current->phpType === 'integer' && $current->defaultValue !== null) { - $current->defaultValue = (int) $current->defaultValue; + $current->defaultValue = (int)$current->defaultValue; } foreach (['type', 'size', 'allowNull', 'defaultValue', 'enumValues'] as $attr) { if ($current->$attr !== $desired->$attr) { @@ -388,4 +367,24 @@ private function compareColumns(ColumnSchema $current, ColumnSchema $desired) } return $changedAttributes; } + + /** + * @param bool $isUniqueCurrent + * @param bool $isUniqueDesired + * @param string $tableName + * @param string $columnName + */ + private function addUniqueIndex( + bool $isUniqueCurrent, + bool $isUniqueDesired, + string $tableName, + string $columnName + ):void { + if ($isUniqueCurrent !== $isUniqueDesired) { + $addUnique = $this->recordBuilder->addUniqueIndex($tableName, $columnName); + $dropUnique = $this->recordBuilder->dropUniqueIndex($tableName, $columnName); + $this->migration->addUpCode($isUniqueDesired === true ? $addUnique : $dropUnique) + ->addDownCode($isUniqueDesired === true ? $dropUnique : $addUnique); + } + } } diff --git a/src/lib/MigrationRecordBuilder.php b/src/lib/MigrationRecordBuilder.php new file mode 100644 index 00000000..04854cc3 --- /dev/null +++ b/src/lib/MigrationRecordBuilder.php @@ -0,0 +1,171 @@ + and contributors + * @license https://github.com/cebe/yii2-openapi/blob/master/LICENSE + */ + +namespace cebe\yii2openapi\lib; + +use yii\db\ColumnSchema; +use yii\db\Schema; +use yii\helpers\VarDumper; +use function sprintf; +use function str_replace; + +class MigrationRecordBuilder +{ + public const INDENT = ' '; + public const DROP_INDEX = MigrationRecordBuilder::INDENT . "\$this->dropIndex('%s', '%s');"; + public const DROP_FK = MigrationRecordBuilder::INDENT . "\$this->dropForeignKey('%s', '%s');"; + public const ADD_TABLE = MigrationRecordBuilder::INDENT . "\$this->createTable('%s', %s);"; + public const ADD_UNIQUE = MigrationRecordBuilder::INDENT . "\$this->createIndex('%s', '%s', '%s', true);"; + public const DROP_COLUMN = MigrationRecordBuilder::INDENT . "\$this->dropColumn('%s', '%s');"; + public const ADD_ENUM = MigrationRecordBuilder::INDENT . "\$this->execute('CREATE TYPE enum_%s AS ENUM(%s)');"; + public const DROP_ENUM = MigrationRecordBuilder::INDENT . "\$this->execute('DROP TYPE enum_%s');"; + public const DROP_TABLE = MigrationRecordBuilder::INDENT . "\$this->dropTable('%s');"; + public const ADD_FK = MigrationRecordBuilder::INDENT . "\$this->addForeignKey('%s', '%s', '%s', '%s', '%s');"; + public const ADD_COLUMN = MigrationRecordBuilder::INDENT . "\$this->addColumn('%s', '%s', %s);"; + public const ALTER_COLUMN = MigrationRecordBuilder::INDENT . "\$this->alterColumn('%s', '%s', %s);"; + + /** + * @var \yii\db\Schema + */ + private $dbSchema; + + public function __construct(Schema $dbSchema) + { + $this->dbSchema = $dbSchema; + } + + /** + * @param string $tableAlias + * @param array|\yii\db\ColumnSchema $columns + * @param array $uniqueNames + * @return string + */ + public function createTable(string $tableAlias, array $columns, array $uniqueNames):string + { + $codeColumns = array_map( + function (ColumnSchema $column) use ($uniqueNames) { + $isUnique = in_array($column->name, $uniqueNames, true); + $converter = $this->columnToCode($column, $isUnique, false); + return $converter->getCode(); + }, + $columns + ); + $codeColumns = str_replace([PHP_EOL, "\\\'"], [PHP_EOL . self::INDENT, "'"], VarDumper::export($codeColumns)); + return sprintf(self::ADD_TABLE, $tableAlias, $codeColumns); + } + + public function addColumn(string $tableAlias, ColumnSchema $column, bool $isUnique = false):string + { + $converter = $this->columnToCode($column, $isUnique, false); + return sprintf(self::ADD_COLUMN, $tableAlias, $column->name, $converter->getCode(true)); + } + + public function addDbColumn(string $tableAlias, ColumnSchema $column, bool $isUnique = false):string + { + $converter = $this->columnToCode($column, $isUnique, true); + return sprintf(self::ADD_COLUMN, $tableAlias, $column->name, $converter->getCode(true)); + } + + public function alterColumn(string $tableAlias, ColumnSchema $column):string + { + $converter = $this->columnToCode($column, false, true); + return sprintf(self::ALTER_COLUMN, $tableAlias, $column->name, $converter->getCode(true)); + } + + + public function alterColumnType(string $tableAlias, ColumnSchema $column):string + { + $converter = $this->columnToCode($column, false, false); + return sprintf(self::ALTER_COLUMN, $tableAlias, $column->name, $converter->getType()); + } + + public function alterColumnTypeFromDb(string $tableAlias, ColumnSchema $column):string + { + $converter = $this->columnToCode($column, false, true); + return sprintf(self::ALTER_COLUMN, $tableAlias, $column->name, $converter->getType()); + } + + public function setColumnDefault(string $tableAlias, ColumnSchema $column):string + { + $converter = $this->columnToCode($column, false, false); + $default = $converter->getDefaultValue(); + if ($default === null) { + return ''; + } + return sprintf(self::ALTER_COLUMN, $tableAlias, $column->name, '"SET DEFAULT '.$default.'"'); + } + + public function setColumnDefaultFromDb(string $tableAlias, ColumnSchema $column):string + { + $converter = $this->columnToCode($column, false, true); + $default = $converter->getDefaultValue(); + if ($default === null) { + return ''; + } + return sprintf(self::ALTER_COLUMN, $tableAlias, $column->name, '"SET DEFAULT '.$default.'"'); + } + + public function dropColumnDefault(string $tableAlias, ColumnSchema $column):string + { + return sprintf(self::ALTER_COLUMN, $tableAlias, $column->name, '"DROP DEFAULT"'); + } + + public function setColumnNotNull(string $tableAlias, ColumnSchema $column):string + { + return sprintf(self::ALTER_COLUMN, $tableAlias, $column->name, 'SET NOT NULL'); + } + + public function dropColumnNotNull(string $tableAlias, ColumnSchema $column):string + { + return sprintf(self::ALTER_COLUMN, $tableAlias, $column->name, 'DROP NOT NULL'); + } + + public function createEnum(string $columnName, array $values):string + { + return sprintf(self::ADD_ENUM, $columnName, ColumnToCode::enumToString($values)); + } + + public function addFk(string $fkName, string $tableAlias, string $fkCol, string $refTable, string $refCol):string + { + return sprintf(self::ADD_FK, $fkName, $tableAlias, $fkCol, $refTable, $refCol); + } + + public function addUniqueIndex(string $tableAlias, string $columnName):string + { + return sprintf(self::ADD_UNIQUE, 'unique_' . $columnName, $tableAlias, $columnName); + } + + public function dropTable(string $tableAlias):string + { + return sprintf(self::DROP_TABLE, $tableAlias); + } + + public function dropEnum(string $columnName):string + { + return sprintf(self::DROP_ENUM, $columnName); + } + + public function dropFk(string $fkName, string $tableAlias):string + { + return sprintf(self::DROP_FK, $fkName, $tableAlias); + } + + public function dropColumn(string $tableAlias, string $columnName):string + { + return sprintf(self::DROP_COLUMN, $tableAlias, $columnName); + } + + public function dropUniqueIndex(string $tableAlias, string $columnName):string + { + return sprintf(self::DROP_INDEX, 'unique_' . $columnName, $tableAlias); + } + + private function columnToCode(ColumnSchema $column, bool $isUnique, bool $fromDb = false): ColumnToCode + { + return new ColumnToCode($this->dbSchema, $column, $isUnique, $fromDb); + } +} diff --git a/tests/unit/MigrationsGeneratorTest.php b/tests/unit/MigrationsGeneratorTest.php index 409cf77c..dc0c2a9f 100644 --- a/tests/unit/MigrationsGeneratorTest.php +++ b/tests/unit/MigrationsGeneratorTest.php @@ -5,10 +5,9 @@ use cebe\yii2openapi\lib\items\Attribute; use cebe\yii2openapi\lib\items\DbModel; use cebe\yii2openapi\lib\items\MigrationModel; -use cebe\yii2openapi\lib\MigrationBuilder; +use cebe\yii2openapi\lib\MigrationRecordBuilder; use cebe\yii2openapi\lib\MigrationsGenerator; use tests\TestCase; -use yii\db\Connection; use yii\db\Schema; use yii\db\TableSchema; use yii\helpers\VarDumper; @@ -62,7 +61,7 @@ public function simpleDbModelsProvider():array ], ]); $codes = str_replace(PHP_EOL, - PHP_EOL . MigrationBuilder::INDENT, + PHP_EOL . MigrationRecordBuilder::INDENT, VarDumper::export([ 'id' => '$this->primaryKey()', 'title' => '$this->string(60)->notNull()->unique()', diff --git a/tests/unit/ValidatorRulesBuilderTest.php b/tests/unit/ValidatorRulesBuilderTest.php index 71793796..abfd4db7 100644 --- a/tests/unit/ValidatorRulesBuilderTest.php +++ b/tests/unit/ValidatorRulesBuilderTest.php @@ -30,11 +30,13 @@ public function testBuild() ->setRequired(true)->setPhpType('int')->setDbType('integer'), (new Attribute('state'))->setPhpType('string')->setDbType('string')->setEnumValues(['active', 'draft']), (new Attribute('created_at'))->setPhpType('string')->setDbType('datetime'), - (new Attribute('contact_email'))->setPhpType('string')->setDbType('string') + (new Attribute('contact_email'))->setPhpType('string')->setDbType('string'), + (new Attribute('required_with_def'))->setPhpType('string') + ->setDbType('string')->setRequired()->setDefault('xxx') ], ]); $expected = [ - new ValidationRule(['title', 'article', 'state', 'created_at', 'contact_email'], 'trim'), + new ValidationRule(['title', 'article', 'state', 'created_at', 'contact_email', 'required_with_def'], 'trim'), new ValidationRule(['title', 'category_id'], 'required'), new ValidationRule(['category_id'], 'integer'), new ValidationRule(['category_id'], 'exist', ['targetRelation'=>'Category']), @@ -47,6 +49,7 @@ public function testBuild() new ValidationRule(['created_at'], 'datetime'), new ValidationRule(['contact_email'], 'string'), new ValidationRule(['contact_email'], 'email'), + new ValidationRule(['required_with_def'], 'string'), ]; $rules = (new ValidationRulesBuilder($model))->build(); From 3ad29ca2c9874995b325724bf6fa4b53f66e6ac6 Mon Sep 17 00:00:00 2001 From: insolita Date: Mon, 10 Aug 2020 14:35:48 +0800 Subject: [PATCH 6/7] update tests --- phpunit.xml.dist | 3 + tests/fixtures/blog.php | 2 +- tests/migrations/m100000_000000_maria.php | 8 +-- tests/migrations/m100000_000000_mysql.php | 8 +-- tests/migrations/m100000_000000_pgsql.php | 23 ++++-- ...0000_000004_create_table_post_comments.php | 2 +- ...0000_000004_create_table_post_comments.php | 2 +- ...0000_000004_create_table_post_comments.php | 2 +- tests/specs/blog/models/base/Category.php | 2 +- tests/specs/blog/models/base/Comment.php | 2 +- tests/specs/blog/models/base/Post.php | 2 +- ...0000_000000_change_table_v2_categories.php | 6 +- ... m200000_000001_change_table_v2_posts.php} | 10 +-- ... m200000_000002_change_table_v2_users.php} | 6 +- ...200000_000003_change_table_v2_comments.php | 4 ++ ...0000_000000_change_table_v2_categories.php | 6 +- ... m200000_000001_change_table_v2_posts.php} | 10 +-- ... m200000_000002_change_table_v2_users.php} | 6 +- ...200000_000003_change_table_v2_comments.php | 6 +- ...0000_000000_change_table_v2_categories.php | 6 +- ... m200000_000001_change_table_v2_posts.php} | 20 ++---- ... m200000_000002_change_table_v2_users.php} | 14 ++-- ...200000_000003_change_table_v2_comments.php | 10 ++- .../m200000_000004_create_table_v2_tags.php | 2 +- tests/specs/blog_v2/models/PostFaker.php | 5 +- tests/specs/blog_v2/models/TagFaker.php | 5 +- tests/specs/postgres_custom.php | 11 +++ tests/specs/postgres_custom.yaml | 72 +++++++++++++++++++ ...200000_000000_create_table_v3_pgcustom.php | 25 +++++++ ...200000_000000_change_table_v3_pgcustom.php | 39 ++++++++++ tests/specs/postgres_custom/models/Custom.php | 10 +++ .../postgres_custom/models/CustomFaker.php | 27 +++++++ .../postgres_custom/models/base/Custom.php | 35 +++++++++ 33 files changed, 308 insertions(+), 83 deletions(-) rename tests/specs/blog_v2/migrations_maria_db/{m200000_000002_change_table_v2_posts.php => m200000_000001_change_table_v2_posts.php} (58%) rename tests/specs/blog_v2/migrations_maria_db/{m200000_000001_change_table_v2_users.php => m200000_000002_change_table_v2_users.php} (89%) rename tests/specs/blog_v2/migrations_mysql_db/{m200000_000002_change_table_v2_posts.php => m200000_000001_change_table_v2_posts.php} (58%) rename tests/specs/blog_v2/migrations_mysql_db/{m200000_000001_change_table_v2_users.php => m200000_000002_change_table_v2_users.php} (89%) rename tests/specs/blog_v2/migrations_pgsql_db/{m200000_000002_change_table_v2_posts.php => m200000_000001_change_table_v2_posts.php} (53%) rename tests/specs/blog_v2/migrations_pgsql_db/{m200000_000001_change_table_v2_users.php => m200000_000002_change_table_v2_users.php} (88%) create mode 100644 tests/specs/postgres_custom.php create mode 100644 tests/specs/postgres_custom.yaml create mode 100644 tests/specs/postgres_custom/migrations/m200000_000000_create_table_v3_pgcustom.php create mode 100644 tests/specs/postgres_custom/migrations_pgsql_db/m200000_000000_change_table_v3_pgcustom.php create mode 100644 tests/specs/postgres_custom/models/Custom.php create mode 100644 tests/specs/postgres_custom/models/CustomFaker.php create mode 100644 tests/specs/postgres_custom/models/base/Custom.php diff --git a/phpunit.xml.dist b/phpunit.xml.dist index fb71ca51..27958442 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -11,6 +11,9 @@ + + ./src/lib + ./vendor ./tests diff --git a/tests/fixtures/blog.php b/tests/fixtures/blog.php index 3de13344..e290bcaa 100644 --- a/tests/fixtures/blog.php +++ b/tests/fixtures/blog.php @@ -99,7 +99,7 @@ ->setDescription('The User') ->setFakerStub('$uniqueFaker->numberBetween(0, 2147483647)'), 'message' => (new Attribute('message', ['phpType' => 'array', 'dbType' => 'json'])) - ->setRequired()->setDefault('{}')->setFakerStub('[]'), + ->setRequired()->setDefault([])->setFakerStub('[]'), 'created_at' => (new Attribute('created_at',['phpType' => 'int', 'dbType' => 'integer'])) ->setRequired()->setFakerStub('$faker->unixTime'), ], diff --git a/tests/migrations/m100000_000000_maria.php b/tests/migrations/m100000_000000_maria.php index 1f0f890d..ec590bd8 100644 --- a/tests/migrations/m100000_000000_maria.php +++ b/tests/migrations/m100000_000000_maria.php @@ -45,12 +45,12 @@ public function up() 'created_at' => $this->date()->null()->defaultValue(null), 'created_by_id' => $this->integer()->null()->defaultValue(null), ]); - $this->addForeignKey('fk_v2_posts_category_id_categories_id', + $this->addForeignKey('fk_v2_posts_category_id_v2_categories_id', '{{%v2_posts}}', 'category_id', '{{%v2_categories}}', 'id'); - $this->addForeignKey('fk_v2_posts_created_by_id_users_id', + $this->addForeignKey('fk_v2_posts_created_by_id_v2_users_id', '{{%v2_posts}}', 'created_by_id', '{{%v2_users}}', @@ -98,8 +98,8 @@ public function down() $this->dropForeignKey('fk_v2_comments_author_id_v2_users_id', '{{%v2_comments}}'); $this->dropForeignKey('fk_v2_comments_post_id_v2_posts_uid', '{{%v2_comments}}'); $this->dropTable('{{%v2_comments}}'); - $this->dropForeignKey('fk_v2_posts_created_by_id_users_id', '{{%v2_posts}}'); - $this->dropForeignKey('fk_v2_posts_category_id_categories_id', '{{%v2_posts}}'); + $this->dropForeignKey('fk_v2_posts_created_by_id_v2_users_id', '{{%v2_posts}}'); + $this->dropForeignKey('fk_v2_posts_category_id_v2_categories_id', '{{%v2_posts}}'); $this->dropTable('{{%v2_posts}}'); $this->dropTable('{{%v2_users}}'); $this->dropTable('{{%v2_categories}}'); diff --git a/tests/migrations/m100000_000000_mysql.php b/tests/migrations/m100000_000000_mysql.php index 4b312036..d5b97f52 100644 --- a/tests/migrations/m100000_000000_mysql.php +++ b/tests/migrations/m100000_000000_mysql.php @@ -44,12 +44,12 @@ public function up() 'created_at' => $this->date()->null()->defaultValue(null), 'created_by_id' => $this->integer()->null()->defaultValue(null), ]); - $this->addForeignKey('fk_v2_posts_category_id_categories_id', + $this->addForeignKey('fk_v2_posts_category_id_v2_categories_id', '{{%v2_posts}}', 'category_id', '{{%v2_categories}}', 'id'); - $this->addForeignKey('fk_v2_posts_created_by_id_users_id', + $this->addForeignKey('fk_v2_posts_created_by_id_v2_users_id', '{{%v2_posts}}', 'created_by_id', '{{%v2_users}}', @@ -97,8 +97,8 @@ public function down() $this->dropForeignKey('fk_v2_comments_author_id_v2_users_id', '{{%v2_comments}}'); $this->dropForeignKey('fk_v2_comments_post_id_v2_posts_uid', '{{%v2_comments}}'); $this->dropTable('{{%v2_comments}}'); - $this->dropForeignKey('fk_v2_posts_created_by_id_users_id', '{{%v2_posts}}'); - $this->dropForeignKey('fk_v2_posts_category_id_categories_id', '{{%v2_posts}}'); + $this->dropForeignKey('fk_v2_posts_created_by_id_v2_users_id', '{{%v2_posts}}'); + $this->dropForeignKey('fk_v2_posts_category_id_v2_categories_id', '{{%v2_posts}}'); $this->dropTable('{{%v2_posts}}'); $this->dropTable('{{%v2_users}}'); $this->dropTable('{{%v2_categories}}'); diff --git a/tests/migrations/m100000_000000_pgsql.php b/tests/migrations/m100000_000000_pgsql.php index f3d0c75c..9e30b0e7 100644 --- a/tests/migrations/m100000_000000_pgsql.php +++ b/tests/migrations/m100000_000000_pgsql.php @@ -1,5 +1,7 @@ $this->date()->null()->defaultValue(null), 'created_by_id' => $this->integer()->null()->defaultValue(null), ]); - $this->addForeignKey('fk_v2_posts_category_id_categories_id', + $this->addForeignKey('fk_v2_posts_category_id_v2_categories_id', '{{%v2_posts}}', 'category_id', '{{%v2_categories}}', 'id'); - $this->addForeignKey('fk_v2_posts_created_by_id_users_id', + $this->addForeignKey('fk_v2_posts_created_by_id_v2_users_id', '{{%v2_posts}}', 'created_by_id', '{{%v2_users}}', @@ -85,6 +87,17 @@ public function safeUp() 'str_datetime' => $this->timestamp()->null()->defaultValue(null), 'str_country' => $this->text()->null()->defaultValue(null), ]); + + $this->execute('CREATE TYPE status_enum AS ENUM(\'active\', \'draft\')'); + $this->createTable('{{%v3_pgcustom}}', [ + 'id' => $this->bigPrimaryKey(), + 'num'=>$this->integer()->defaultValue(0), + 'json1'=>$this->json(), + 'json2'=>$this->json()->null()->defaultValue(null), + 'json3'=>$this->json()->defaultValue(Json::encode(['foo'=>'bar', 'bar'=>'baz'])), + 'json4'=>"json DEFAULT '".new Expression(Json::encode(['ffo'=>'bar']))."'", + 'status'=> 'status_enum' + ]); } public function safeDown() @@ -93,10 +106,12 @@ public function safeDown() $this->dropForeignKey('fk_v2_comments_author_id_v2_users_id', '{{%v2_comments}}'); $this->dropForeignKey('fk_v2_comments_post_id_v2_posts_uid', '{{%v2_comments}}'); $this->dropTable('{{%v2_comments}}'); - $this->dropForeignKey('fk_v2_posts_created_by_id_users_id', '{{%v2_posts}}'); - $this->dropForeignKey('fk_v2_posts_category_id_categories_id', '{{%v2_posts}}'); + $this->dropForeignKey('fk_v2_posts_created_by_id_v2_users_id', '{{%v2_posts}}'); + $this->dropForeignKey('fk_v2_posts_category_id_v2_categories_id', '{{%v2_posts}}'); $this->dropTable('{{%v2_posts}}'); $this->dropTable('{{%v2_users}}'); $this->dropTable('{{%v2_categories}}'); + $this->dropTable('{{%v3_pgcustom}}'); + $this->execute('DROP TYPE status_enum'); } } diff --git a/tests/specs/blog/migrations/m200000_000004_create_table_post_comments.php b/tests/specs/blog/migrations/m200000_000004_create_table_post_comments.php index 7ed94036..2b1e05b6 100644 --- a/tests/specs/blog/migrations/m200000_000004_create_table_post_comments.php +++ b/tests/specs/blog/migrations/m200000_000004_create_table_post_comments.php @@ -11,7 +11,7 @@ public function up() 'id' => $this->bigPrimaryKey(), 'post_id' => $this->bigInteger()->notNull(), 'author_id' => $this->integer()->notNull(), - 'message' => 'json NOT NULL DEFAULT \'{}\'', + 'message' => 'json NOT NULL DEFAULT \'[]\'', 'created_at' => $this->integer()->notNull(), ]); $this->addForeignKey('fk_post_comments_post_id_blog_posts_uid', '{{%post_comments}}', 'post_id', '{{%blog_posts}}', 'uid'); diff --git a/tests/specs/blog/migrations_maria_db/m200000_000004_create_table_post_comments.php b/tests/specs/blog/migrations_maria_db/m200000_000004_create_table_post_comments.php index 7ed94036..2b1e05b6 100644 --- a/tests/specs/blog/migrations_maria_db/m200000_000004_create_table_post_comments.php +++ b/tests/specs/blog/migrations_maria_db/m200000_000004_create_table_post_comments.php @@ -11,7 +11,7 @@ public function up() 'id' => $this->bigPrimaryKey(), 'post_id' => $this->bigInteger()->notNull(), 'author_id' => $this->integer()->notNull(), - 'message' => 'json NOT NULL DEFAULT \'{}\'', + 'message' => 'json NOT NULL DEFAULT \'[]\'', 'created_at' => $this->integer()->notNull(), ]); $this->addForeignKey('fk_post_comments_post_id_blog_posts_uid', '{{%post_comments}}', 'post_id', '{{%blog_posts}}', 'uid'); diff --git a/tests/specs/blog/migrations_pgsql_db/m200000_000004_create_table_post_comments.php b/tests/specs/blog/migrations_pgsql_db/m200000_000004_create_table_post_comments.php index 380a57e6..5431ebf6 100644 --- a/tests/specs/blog/migrations_pgsql_db/m200000_000004_create_table_post_comments.php +++ b/tests/specs/blog/migrations_pgsql_db/m200000_000004_create_table_post_comments.php @@ -11,7 +11,7 @@ public function safeUp() 'id' => $this->bigPrimaryKey(), 'post_id' => $this->bigInteger()->notNull(), 'author_id' => $this->integer()->notNull(), - 'message' => 'json NOT NULL DEFAULT \'{}\'', + 'message' => 'json NOT NULL DEFAULT \'[]\'', 'created_at' => $this->integer()->notNull(), ]); $this->addForeignKey('fk_post_comments_post_id_blog_posts_uid', '{{%post_comments}}', 'post_id', '{{%blog_posts}}', 'uid'); diff --git a/tests/specs/blog/models/base/Category.php b/tests/specs/blog/models/base/Category.php index 82fb76f4..9de01362 100644 --- a/tests/specs/blog/models/base/Category.php +++ b/tests/specs/blog/models/base/Category.php @@ -22,7 +22,7 @@ public function rules() { return [ [['title'], 'trim'], - [['title', 'active'], 'required'], + [['title'], 'required'], [['title'], 'unique'], [['title'], 'string', 'max' => 255], [['active'], 'boolean'], diff --git a/tests/specs/blog/models/base/Comment.php b/tests/specs/blog/models/base/Comment.php index fe8b20ed..40920593 100644 --- a/tests/specs/blog/models/base/Comment.php +++ b/tests/specs/blog/models/base/Comment.php @@ -24,7 +24,7 @@ public static function tableName() public function rules() { return [ - [['post_id', 'author_id', 'message', 'created_at'], 'required'], + [['post_id', 'author_id', 'created_at'], 'required'], [['post_id'], 'integer'], [['post_id'], 'exist', 'targetRelation' => 'Post'], [['author_id'], 'integer'], diff --git a/tests/specs/blog/models/base/Post.php b/tests/specs/blog/models/base/Post.php index a8678377..038ccc60 100644 --- a/tests/specs/blog/models/base/Post.php +++ b/tests/specs/blog/models/base/Post.php @@ -28,7 +28,7 @@ public function rules() { return [ [['title', 'slug', 'created_at'], 'trim'], - [['title', 'category_id', 'active'], 'required'], + [['title', 'category_id'], 'required'], [['category_id'], 'integer'], [['category_id'], 'exist', 'targetRelation' => 'Category'], [['created_by_id'], 'integer'], diff --git a/tests/specs/blog_v2/migrations_maria_db/m200000_000000_change_table_v2_categories.php b/tests/specs/blog_v2/migrations_maria_db/m200000_000000_change_table_v2_categories.php index 503eddd0..d2b5b92b 100644 --- a/tests/specs/blog_v2/migrations_maria_db/m200000_000000_change_table_v2_categories.php +++ b/tests/specs/blog_v2/migrations_maria_db/m200000_000000_change_table_v2_categories.php @@ -8,15 +8,15 @@ class m200000_000000_change_table_v2_categories extends \yii\db\Migration public function up() { $this->addColumn('{{%v2_categories}}', 'cover', $this->text()->notNull()); - $this->alterColumn('{{%v2_categories}}', 'active', $this->boolean()->notNull()); - $this->createIndex('unique_title', '{{%v2_categories}}', 'title', true); + $this->alterColumn('{{%v2_categories}}', 'active', $this->tinyInteger(1)->notNull()); $this->alterColumn('{{%v2_categories}}', 'title', $this->string(100)->notNull()); + $this->createIndex('unique_title', '{{%v2_categories}}', 'title', true); } public function down() { - $this->alterColumn('{{%v2_categories}}', 'title', $this->string(255)->notNull()); $this->dropIndex('unique_title', '{{%v2_categories}}'); + $this->alterColumn('{{%v2_categories}}', 'title', $this->string(255)->notNull()); $this->alterColumn('{{%v2_categories}}', 'active', $this->tinyInteger(1)->notNull()->defaultValue(0)); $this->dropColumn('{{%v2_categories}}', 'cover'); } diff --git a/tests/specs/blog_v2/migrations_maria_db/m200000_000002_change_table_v2_posts.php b/tests/specs/blog_v2/migrations_maria_db/m200000_000001_change_table_v2_posts.php similarity index 58% rename from tests/specs/blog_v2/migrations_maria_db/m200000_000002_change_table_v2_posts.php rename to tests/specs/blog_v2/migrations_maria_db/m200000_000001_change_table_v2_posts.php index e43d2e85..1d57d6b3 100644 --- a/tests/specs/blog_v2/migrations_maria_db/m200000_000002_change_table_v2_posts.php +++ b/tests/specs/blog_v2/migrations_maria_db/m200000_000001_change_table_v2_posts.php @@ -3,26 +3,20 @@ /** * Table for Post */ -class m200000_000002_change_table_v2_posts extends \yii\db\Migration +class m200000_000001_change_table_v2_posts extends \yii\db\Migration { public function up() { $this->addColumn('{{%v2_posts}}', 'id', $this->bigPrimaryKey()); $this->addColumn('{{%v2_posts}}', 'lang', "enum('ru', 'eng') NULL DEFAULT 'ru'"); $this->dropColumn('{{%v2_posts}}', 'uid'); - $this->alterColumn('{{%v2_posts}}', 'active', $this->boolean()->notNull()); + $this->alterColumn('{{%v2_posts}}', 'active', $this->tinyInteger(1)->notNull()); $this->alterColumn('{{%v2_posts}}', 'category_id', $this->bigInteger()->notNull()); $this->alterColumn('{{%v2_posts}}', 'created_by_id', $this->bigInteger()->null()->defaultValue(null)); - $this->createIndex('unique_title', '{{%v2_posts}}', 'title', true); - $this->addForeignKey('fk_v2_posts_category_id_v2_categories_id', '{{%v2_posts}}', 'category_id', '{{%v2_categories}}', 'id'); - $this->addForeignKey('fk_v2_posts_created_by_id_v2_users_id', '{{%v2_posts}}', 'created_by_id', '{{%v2_users}}', 'id'); } public function down() { - $this->dropForeignKey('fk_v2_posts_created_by_id_v2_users_id', '{{%v2_posts}}'); - $this->dropForeignKey('fk_v2_posts_category_id_v2_categories_id', '{{%v2_posts}}'); - $this->dropIndex('unique_title', '{{%v2_posts}}'); $this->alterColumn('{{%v2_posts}}', 'created_by_id', $this->integer(11)->null()->defaultValue(null)); $this->alterColumn('{{%v2_posts}}', 'category_id', $this->integer(11)->notNull()); $this->alterColumn('{{%v2_posts}}', 'active', $this->tinyInteger(1)->notNull()->defaultValue(0)); diff --git a/tests/specs/blog_v2/migrations_maria_db/m200000_000001_change_table_v2_users.php b/tests/specs/blog_v2/migrations_maria_db/m200000_000002_change_table_v2_users.php similarity index 89% rename from tests/specs/blog_v2/migrations_maria_db/m200000_000001_change_table_v2_users.php rename to tests/specs/blog_v2/migrations_maria_db/m200000_000002_change_table_v2_users.php index 56e3a98b..450749e8 100644 --- a/tests/specs/blog_v2/migrations_maria_db/m200000_000001_change_table_v2_users.php +++ b/tests/specs/blog_v2/migrations_maria_db/m200000_000002_change_table_v2_users.php @@ -3,15 +3,15 @@ /** * Table for User */ -class m200000_000001_change_table_v2_users extends \yii\db\Migration +class m200000_000002_change_table_v2_users extends \yii\db\Migration { public function up() { $this->addColumn('{{%v2_users}}', 'login', $this->text()->notNull()->unique()); $this->dropColumn('{{%v2_users}}', 'username'); $this->alterColumn('{{%v2_users}}', 'created_at', $this->timestamp()->null()->defaultValue(null)); + $this->alterColumn('{{%v2_users}}', 'email', $this->string()->notNull()); $this->createIndex('unique_email', '{{%v2_users}}', 'email', true); - $this->alterColumn('{{%v2_users}}', 'email', $this->text()->notNull()); $this->alterColumn('{{%v2_users}}', 'password', $this->string()->notNull()); $this->alterColumn('{{%v2_users}}', 'role', "enum('admin', 'editor', 'reader') NULL DEFAULT NULL"); } @@ -20,8 +20,8 @@ public function down() { $this->alterColumn('{{%v2_users}}', 'role', $this->string(20)->null()->defaultValue("reader")); $this->alterColumn('{{%v2_users}}', 'password', $this->string(255)->notNull()); - $this->alterColumn('{{%v2_users}}', 'email', $this->string(200)->notNull()); $this->dropIndex('unique_email', '{{%v2_users}}'); + $this->alterColumn('{{%v2_users}}', 'email', $this->string(200)->notNull()); $this->alterColumn('{{%v2_users}}', 'created_at', $this->timestamp()->null()->defaultExpression("CURRENT_TIMESTAMP")); $this->addColumn('{{%v2_users}}', 'username', $this->string(200)->notNull()); $this->dropColumn('{{%v2_users}}', 'login'); diff --git a/tests/specs/blog_v2/migrations_maria_db/m200000_000003_change_table_v2_comments.php b/tests/specs/blog_v2/migrations_maria_db/m200000_000003_change_table_v2_comments.php index 665208af..25ffc775 100644 --- a/tests/specs/blog_v2/migrations_maria_db/m200000_000003_change_table_v2_comments.php +++ b/tests/specs/blog_v2/migrations_maria_db/m200000_000003_change_table_v2_comments.php @@ -7,6 +7,8 @@ class m200000_000003_change_table_v2_comments extends \yii\db\Migration { public function up() { + $this->dropForeignKey('fk_v2_comments_post_id_v2_posts_uid', '{{%v2_comments}}'); + $this->dropForeignKey('fk_v2_comments_author_id_v2_users_id', '{{%v2_comments}}'); $this->addColumn('{{%v2_comments}}', 'user_id', $this->bigInteger()->null()->defaultValue(null)); $this->dropColumn('{{%v2_comments}}', 'author_id'); $this->alterColumn('{{%v2_comments}}', 'created_at', $this->timestamp()->notNull()); @@ -25,5 +27,7 @@ public function down() $this->alterColumn('{{%v2_comments}}', 'created_at', $this->integer(11)->notNull()); $this->addColumn('{{%v2_comments}}', 'author_id', $this->integer(11)->notNull()); $this->dropColumn('{{%v2_comments}}', 'user_id'); + $this->addForeignKey('fk_v2_comments_author_id_v2_users_id', '{{%v2_comments}}', 'id', 'v2_users', 'author_id'); + $this->addForeignKey('fk_v2_comments_post_id_v2_posts_uid', '{{%v2_comments}}', 'uid', 'v2_posts', 'post_id'); } } diff --git a/tests/specs/blog_v2/migrations_mysql_db/m200000_000000_change_table_v2_categories.php b/tests/specs/blog_v2/migrations_mysql_db/m200000_000000_change_table_v2_categories.php index 503eddd0..d2b5b92b 100644 --- a/tests/specs/blog_v2/migrations_mysql_db/m200000_000000_change_table_v2_categories.php +++ b/tests/specs/blog_v2/migrations_mysql_db/m200000_000000_change_table_v2_categories.php @@ -8,15 +8,15 @@ class m200000_000000_change_table_v2_categories extends \yii\db\Migration public function up() { $this->addColumn('{{%v2_categories}}', 'cover', $this->text()->notNull()); - $this->alterColumn('{{%v2_categories}}', 'active', $this->boolean()->notNull()); - $this->createIndex('unique_title', '{{%v2_categories}}', 'title', true); + $this->alterColumn('{{%v2_categories}}', 'active', $this->tinyInteger(1)->notNull()); $this->alterColumn('{{%v2_categories}}', 'title', $this->string(100)->notNull()); + $this->createIndex('unique_title', '{{%v2_categories}}', 'title', true); } public function down() { - $this->alterColumn('{{%v2_categories}}', 'title', $this->string(255)->notNull()); $this->dropIndex('unique_title', '{{%v2_categories}}'); + $this->alterColumn('{{%v2_categories}}', 'title', $this->string(255)->notNull()); $this->alterColumn('{{%v2_categories}}', 'active', $this->tinyInteger(1)->notNull()->defaultValue(0)); $this->dropColumn('{{%v2_categories}}', 'cover'); } diff --git a/tests/specs/blog_v2/migrations_mysql_db/m200000_000002_change_table_v2_posts.php b/tests/specs/blog_v2/migrations_mysql_db/m200000_000001_change_table_v2_posts.php similarity index 58% rename from tests/specs/blog_v2/migrations_mysql_db/m200000_000002_change_table_v2_posts.php rename to tests/specs/blog_v2/migrations_mysql_db/m200000_000001_change_table_v2_posts.php index e43d2e85..1d57d6b3 100644 --- a/tests/specs/blog_v2/migrations_mysql_db/m200000_000002_change_table_v2_posts.php +++ b/tests/specs/blog_v2/migrations_mysql_db/m200000_000001_change_table_v2_posts.php @@ -3,26 +3,20 @@ /** * Table for Post */ -class m200000_000002_change_table_v2_posts extends \yii\db\Migration +class m200000_000001_change_table_v2_posts extends \yii\db\Migration { public function up() { $this->addColumn('{{%v2_posts}}', 'id', $this->bigPrimaryKey()); $this->addColumn('{{%v2_posts}}', 'lang', "enum('ru', 'eng') NULL DEFAULT 'ru'"); $this->dropColumn('{{%v2_posts}}', 'uid'); - $this->alterColumn('{{%v2_posts}}', 'active', $this->boolean()->notNull()); + $this->alterColumn('{{%v2_posts}}', 'active', $this->tinyInteger(1)->notNull()); $this->alterColumn('{{%v2_posts}}', 'category_id', $this->bigInteger()->notNull()); $this->alterColumn('{{%v2_posts}}', 'created_by_id', $this->bigInteger()->null()->defaultValue(null)); - $this->createIndex('unique_title', '{{%v2_posts}}', 'title', true); - $this->addForeignKey('fk_v2_posts_category_id_v2_categories_id', '{{%v2_posts}}', 'category_id', '{{%v2_categories}}', 'id'); - $this->addForeignKey('fk_v2_posts_created_by_id_v2_users_id', '{{%v2_posts}}', 'created_by_id', '{{%v2_users}}', 'id'); } public function down() { - $this->dropForeignKey('fk_v2_posts_created_by_id_v2_users_id', '{{%v2_posts}}'); - $this->dropForeignKey('fk_v2_posts_category_id_v2_categories_id', '{{%v2_posts}}'); - $this->dropIndex('unique_title', '{{%v2_posts}}'); $this->alterColumn('{{%v2_posts}}', 'created_by_id', $this->integer(11)->null()->defaultValue(null)); $this->alterColumn('{{%v2_posts}}', 'category_id', $this->integer(11)->notNull()); $this->alterColumn('{{%v2_posts}}', 'active', $this->tinyInteger(1)->notNull()->defaultValue(0)); diff --git a/tests/specs/blog_v2/migrations_mysql_db/m200000_000001_change_table_v2_users.php b/tests/specs/blog_v2/migrations_mysql_db/m200000_000002_change_table_v2_users.php similarity index 89% rename from tests/specs/blog_v2/migrations_mysql_db/m200000_000001_change_table_v2_users.php rename to tests/specs/blog_v2/migrations_mysql_db/m200000_000002_change_table_v2_users.php index 56e3a98b..450749e8 100644 --- a/tests/specs/blog_v2/migrations_mysql_db/m200000_000001_change_table_v2_users.php +++ b/tests/specs/blog_v2/migrations_mysql_db/m200000_000002_change_table_v2_users.php @@ -3,15 +3,15 @@ /** * Table for User */ -class m200000_000001_change_table_v2_users extends \yii\db\Migration +class m200000_000002_change_table_v2_users extends \yii\db\Migration { public function up() { $this->addColumn('{{%v2_users}}', 'login', $this->text()->notNull()->unique()); $this->dropColumn('{{%v2_users}}', 'username'); $this->alterColumn('{{%v2_users}}', 'created_at', $this->timestamp()->null()->defaultValue(null)); + $this->alterColumn('{{%v2_users}}', 'email', $this->string()->notNull()); $this->createIndex('unique_email', '{{%v2_users}}', 'email', true); - $this->alterColumn('{{%v2_users}}', 'email', $this->text()->notNull()); $this->alterColumn('{{%v2_users}}', 'password', $this->string()->notNull()); $this->alterColumn('{{%v2_users}}', 'role', "enum('admin', 'editor', 'reader') NULL DEFAULT NULL"); } @@ -20,8 +20,8 @@ public function down() { $this->alterColumn('{{%v2_users}}', 'role', $this->string(20)->null()->defaultValue("reader")); $this->alterColumn('{{%v2_users}}', 'password', $this->string(255)->notNull()); - $this->alterColumn('{{%v2_users}}', 'email', $this->string(200)->notNull()); $this->dropIndex('unique_email', '{{%v2_users}}'); + $this->alterColumn('{{%v2_users}}', 'email', $this->string(200)->notNull()); $this->alterColumn('{{%v2_users}}', 'created_at', $this->timestamp()->null()->defaultExpression("CURRENT_TIMESTAMP")); $this->addColumn('{{%v2_users}}', 'username', $this->string(200)->notNull()); $this->dropColumn('{{%v2_users}}', 'login'); diff --git a/tests/specs/blog_v2/migrations_mysql_db/m200000_000003_change_table_v2_comments.php b/tests/specs/blog_v2/migrations_mysql_db/m200000_000003_change_table_v2_comments.php index d7ea9cf9..a1b329b2 100644 --- a/tests/specs/blog_v2/migrations_mysql_db/m200000_000003_change_table_v2_comments.php +++ b/tests/specs/blog_v2/migrations_mysql_db/m200000_000003_change_table_v2_comments.php @@ -7,6 +7,8 @@ class m200000_000003_change_table_v2_comments extends \yii\db\Migration { public function up() { + $this->dropForeignKey('fk_v2_comments_post_id_v2_posts_uid', '{{%v2_comments}}'); + $this->dropForeignKey('fk_v2_comments_author_id_v2_users_id', '{{%v2_comments}}'); $this->addColumn('{{%v2_comments}}', 'user_id', $this->bigInteger()->null()->defaultValue(null)); $this->dropColumn('{{%v2_comments}}', 'author_id'); $this->alterColumn('{{%v2_comments}}', 'created_at', $this->timestamp()->notNull()); @@ -21,9 +23,11 @@ public function down() $this->dropForeignKey('fk_v2_comments_user_id_v2_users_id', '{{%v2_comments}}'); $this->dropForeignKey('fk_v2_comments_post_id_v2_posts_id', '{{%v2_comments}}'); $this->alterColumn('{{%v2_comments}}', 'post_id', $this->bigInteger(20)->notNull()); - $this->alterColumn('{{%v2_comments}}', 'message', "json NOT NULL"); + $this->alterColumn('{{%v2_comments}}', 'message', 'json NOT NULL'); $this->alterColumn('{{%v2_comments}}', 'created_at', $this->integer(11)->notNull()); $this->addColumn('{{%v2_comments}}', 'author_id', $this->integer(11)->notNull()); $this->dropColumn('{{%v2_comments}}', 'user_id'); + $this->addForeignKey('fk_v2_comments_author_id_v2_users_id', '{{%v2_comments}}', 'id', 'v2_users', 'author_id'); + $this->addForeignKey('fk_v2_comments_post_id_v2_posts_uid', '{{%v2_comments}}', 'uid', 'v2_posts', 'post_id'); } } diff --git a/tests/specs/blog_v2/migrations_pgsql_db/m200000_000000_change_table_v2_categories.php b/tests/specs/blog_v2/migrations_pgsql_db/m200000_000000_change_table_v2_categories.php index c3c7add0..1078e37b 100644 --- a/tests/specs/blog_v2/migrations_pgsql_db/m200000_000000_change_table_v2_categories.php +++ b/tests/specs/blog_v2/migrations_pgsql_db/m200000_000000_change_table_v2_categories.php @@ -9,15 +9,15 @@ public function safeUp() { $this->addColumn('{{%v2_categories}}', 'cover', $this->text()->notNull()); $this->alterColumn('{{%v2_categories}}', 'active', "DROP DEFAULT"); - $this->createIndex('unique_title', '{{%v2_categories}}', 'title', true); $this->alterColumn('{{%v2_categories}}', 'title', $this->string(100)); + $this->createIndex('unique_title', '{{%v2_categories}}', 'title', true); } public function safeDown() { $this->dropIndex('unique_title', '{{%v2_categories}}'); - $this->dropColumn('{{%v2_categories}}', 'cover'); - $this->alterColumn('{{%v2_categories}}', 'active', "SET DEFAULT FALSE"); $this->alterColumn('{{%v2_categories}}', 'title', $this->string(255)); + $this->dropColumn('{{%v2_categories}}', 'cover'); + $this->alterColumn('{{%v2_categories}}', 'active', "SET DEFAULT 'f'"); } } diff --git a/tests/specs/blog_v2/migrations_pgsql_db/m200000_000002_change_table_v2_posts.php b/tests/specs/blog_v2/migrations_pgsql_db/m200000_000001_change_table_v2_posts.php similarity index 53% rename from tests/specs/blog_v2/migrations_pgsql_db/m200000_000002_change_table_v2_posts.php rename to tests/specs/blog_v2/migrations_pgsql_db/m200000_000001_change_table_v2_posts.php index 26289c06..fadbe5e7 100644 --- a/tests/specs/blog_v2/migrations_pgsql_db/m200000_000002_change_table_v2_posts.php +++ b/tests/specs/blog_v2/migrations_pgsql_db/m200000_000001_change_table_v2_posts.php @@ -3,33 +3,27 @@ /** * Table for Post */ -class m200000_000002_change_table_v2_posts extends \yii\db\Migration +class m200000_000001_change_table_v2_posts extends \yii\db\Migration { public function safeUp() { - $this->execute('CREATE TYPE enum_lang AS ENUM(\'ru\',\'eng\')'); + $this->execute('CREATE TYPE enum_lang AS ENUM(\'ru\', \'eng\')'); $this->addColumn('{{%v2_posts}}', 'id', $this->bigPrimaryKey()); - $this->addColumn('{{%v2_posts}}', 'lang', "enum_lang NULL DEFAULT 'ru'"); + $this->addColumn('{{%v2_posts}}', 'lang', 'enum_lang NULL DEFAULT \'ru\''); $this->dropColumn('{{%v2_posts}}', 'uid'); $this->alterColumn('{{%v2_posts}}', 'active', "DROP DEFAULT"); $this->alterColumn('{{%v2_posts}}', 'category_id', $this->bigInteger()); $this->alterColumn('{{%v2_posts}}', 'created_by_id', $this->bigInteger()); - $this->createIndex('unique_title', '{{%v2_posts}}', 'title', true); - $this->addForeignKey('fk_v2_posts_category_id_v2_categories_id', '{{%v2_posts}}', 'category_id', '{{%v2_categories}}', 'id'); - $this->addForeignKey('fk_v2_posts_created_by_id_v2_users_id', '{{%v2_posts}}', 'created_by_id', '{{%v2_users}}', 'id'); } public function safeDown() { - $this->dropForeignKey('fk_v2_posts_created_by_id_v2_users_id', '{{%v2_posts}}'); - $this->dropForeignKey('fk_v2_posts_category_id_v2_categories_id', '{{%v2_posts}}'); - $this->dropIndex('unique_title', '{{%v2_posts}}'); + $this->alterColumn('{{%v2_posts}}', 'created_by_id', $this->integer()); + $this->alterColumn('{{%v2_posts}}', 'category_id', $this->integer()); $this->addColumn('{{%v2_posts}}', 'uid', $this->bigInteger()->notNull()); - $this->execute('DROP TYPE enum_lang'); $this->dropColumn('{{%v2_posts}}', 'lang'); $this->dropColumn('{{%v2_posts}}', 'id'); - $this->alterColumn('{{%v2_posts}}', 'active', "SET DEFAULT FALSE"); - $this->alterColumn('{{%v2_posts}}', 'category_id', $this->integer()); - $this->alterColumn('{{%v2_posts}}', 'created_by_id', $this->integer()); + $this->execute('DROP TYPE enum_lang'); + $this->alterColumn('{{%v2_posts}}', 'active', "SET DEFAULT 'f'"); } } diff --git a/tests/specs/blog_v2/migrations_pgsql_db/m200000_000001_change_table_v2_users.php b/tests/specs/blog_v2/migrations_pgsql_db/m200000_000002_change_table_v2_users.php similarity index 88% rename from tests/specs/blog_v2/migrations_pgsql_db/m200000_000001_change_table_v2_users.php rename to tests/specs/blog_v2/migrations_pgsql_db/m200000_000002_change_table_v2_users.php index 8e4dd383..e8c5c92a 100644 --- a/tests/specs/blog_v2/migrations_pgsql_db/m200000_000001_change_table_v2_users.php +++ b/tests/specs/blog_v2/migrations_pgsql_db/m200000_000002_change_table_v2_users.php @@ -3,30 +3,30 @@ /** * Table for User */ -class m200000_000001_change_table_v2_users extends \yii\db\Migration +class m200000_000002_change_table_v2_users extends \yii\db\Migration { public function safeUp() { - $this->execute('CREATE TYPE enum_role AS ENUM(\'admin\',\'editor\',\'reader\')'); + $this->execute('CREATE TYPE enum_role AS ENUM(\'admin\', \'editor\', \'reader\')'); $this->addColumn('{{%v2_users}}', 'login', $this->text()->notNull()->unique()); $this->dropColumn('{{%v2_users}}', 'username'); $this->alterColumn('{{%v2_users}}', 'created_at', "DROP DEFAULT"); - $this->createIndex('unique_email', '{{%v2_users}}', 'email', true); $this->alterColumn('{{%v2_users}}', 'email', $this->text()); + $this->createIndex('unique_email', '{{%v2_users}}', 'email', true); $this->alterColumn('{{%v2_users}}', 'password', $this->string()); - $this->alterColumn('{{%v2_users}}', 'role', "enum_role USING role::enum_role"); + $this->alterColumn('{{%v2_users}}', 'role', 'enum_role USING role::enum_role'); $this->alterColumn('{{%v2_users}}', 'role', "DROP DEFAULT"); } public function safeDown() { + $this->alterColumn('{{%v2_users}}', 'role', $this->string(20)); + $this->alterColumn('{{%v2_users}}', 'password', $this->string(255)); $this->dropIndex('unique_email', '{{%v2_users}}'); + $this->alterColumn('{{%v2_users}}', 'email', $this->string(200)); $this->addColumn('{{%v2_users}}', 'username', $this->string(200)->notNull()); $this->dropColumn('{{%v2_users}}', 'login'); $this->alterColumn('{{%v2_users}}', 'created_at', "SET DEFAULT 'CURRENT_TIMESTAMP'"); - $this->alterColumn('{{%v2_users}}', 'email', $this->string(200)); - $this->alterColumn('{{%v2_users}}', 'password', $this->string(255)); - $this->alterColumn('{{%v2_users}}', 'role', $this->string(20)); $this->alterColumn('{{%v2_users}}', 'role', "SET DEFAULT 'reader'"); $this->execute('DROP TYPE enum_role'); } diff --git a/tests/specs/blog_v2/migrations_pgsql_db/m200000_000003_change_table_v2_comments.php b/tests/specs/blog_v2/migrations_pgsql_db/m200000_000003_change_table_v2_comments.php index 2af6194e..6418a5d5 100644 --- a/tests/specs/blog_v2/migrations_pgsql_db/m200000_000003_change_table_v2_comments.php +++ b/tests/specs/blog_v2/migrations_pgsql_db/m200000_000003_change_table_v2_comments.php @@ -7,6 +7,8 @@ class m200000_000003_change_table_v2_comments extends \yii\db\Migration { public function safeUp() { + $this->dropForeignKey('fk_v2_comments_author_id_v2_users_id', '{{%v2_comments}}'); + $this->dropForeignKey('fk_v2_comments_post_id_v2_posts_uid', '{{%v2_comments}}'); $this->addColumn('{{%v2_comments}}', 'user_id', $this->bigInteger()->null()->defaultValue(null)); $this->dropColumn('{{%v2_comments}}', 'author_id'); $this->alterColumn('{{%v2_comments}}', 'created_at', $this->timestamp()); @@ -20,10 +22,12 @@ public function safeDown() { $this->dropForeignKey('fk_v2_comments_user_id_v2_users_id', '{{%v2_comments}}'); $this->dropForeignKey('fk_v2_comments_post_id_v2_posts_id', '{{%v2_comments}}'); + $this->alterColumn('{{%v2_comments}}', 'message', 'jsonb'); + $this->alterColumn('{{%v2_comments}}', 'created_at', $this->integer()); $this->addColumn('{{%v2_comments}}', 'author_id', $this->integer()->notNull()); $this->dropColumn('{{%v2_comments}}', 'user_id'); - $this->alterColumn('{{%v2_comments}}', 'created_at', $this->integer()); - $this->alterColumn('{{%v2_comments}}', 'message', "jsonb"); - $this->alterColumn('{{%v2_comments}}', 'message', "SET DEFAULT '[]'"); + $this->alterColumn('{{%v2_comments}}', 'message', "SET DEFAULT \'[]\'"); + $this->addForeignKey('fk_v2_comments_post_id_v2_posts_uid', '{{%v2_comments}}', 'uid', 'v2_posts', 'post_id'); + $this->addForeignKey('fk_v2_comments_author_id_v2_users_id', '{{%v2_comments}}', 'id', 'v2_users', 'author_id'); } } diff --git a/tests/specs/blog_v2/migrations_pgsql_db/m200000_000004_create_table_v2_tags.php b/tests/specs/blog_v2/migrations_pgsql_db/m200000_000004_create_table_v2_tags.php index 1da20213..f321bd78 100644 --- a/tests/specs/blog_v2/migrations_pgsql_db/m200000_000004_create_table_v2_tags.php +++ b/tests/specs/blog_v2/migrations_pgsql_db/m200000_000004_create_table_v2_tags.php @@ -7,7 +7,7 @@ class m200000_000004_create_table_v2_tags extends \yii\db\Migration { public function safeUp() { - $this->execute('CREATE TYPE enum_lang AS ENUM(\'ru\',\'eng\')'); + $this->execute('CREATE TYPE enum_lang AS ENUM(\'ru\', \'eng\')'); $this->createTable('{{%v2_tags}}', [ 'id' => $this->bigPrimaryKey(), 'name' => $this->string(100)->notNull()->unique(), diff --git a/tests/specs/blog_v2/models/PostFaker.php b/tests/specs/blog_v2/models/PostFaker.php index 3778c534..44adfa96 100644 --- a/tests/specs/blog_v2/models/PostFaker.php +++ b/tests/specs/blog_v2/models/PostFaker.php @@ -18,10 +18,7 @@ public function generateModel() $model->id = $uniqueFaker->numberBetween(0, 2147483647); $model->title = substr($faker->sentence, 0, 255); $model->slug = substr($uniqueFaker->slug, 0, 200); - $model->lang = $faker->randomElement(array ( - 0 => 'ru', - 1 => 'eng', -)); + $model->lang = $faker->randomElement(['ru','eng']); $model->active = $faker->boolean; $model->created_at = $faker->iso8601; return $model; diff --git a/tests/specs/blog_v2/models/TagFaker.php b/tests/specs/blog_v2/models/TagFaker.php index 8a19853c..ede4020c 100644 --- a/tests/specs/blog_v2/models/TagFaker.php +++ b/tests/specs/blog_v2/models/TagFaker.php @@ -17,10 +17,7 @@ public function generateModel() $model = new Tag(); $model->id = $uniqueFaker->numberBetween(0, 2147483647); $model->name = substr($faker->text(100), 0, 100); - $model->lang = $faker->randomElement(array ( - 0 => 'ru', - 1 => 'eng', -)); + $model->lang = $faker->randomElement(['ru','eng']); return $model; } } diff --git a/tests/specs/postgres_custom.php b/tests/specs/postgres_custom.php new file mode 100644 index 00000000..09a74691 --- /dev/null +++ b/tests/specs/postgres_custom.php @@ -0,0 +1,11 @@ + '@specs/postgres_custom.yaml', + 'generateUrls' => false, + 'generateControllers' => false, + 'generateModels' => true, + 'generateMigrations' => true, + 'excludeModels' => [ + 'Error', + ], +]; \ No newline at end of file diff --git a/tests/specs/postgres_custom.yaml b/tests/specs/postgres_custom.yaml new file mode 100644 index 00000000..989ca0bb --- /dev/null +++ b/tests/specs/postgres_custom.yaml @@ -0,0 +1,72 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Custom postgres columns + license: + name: MIT +servers: + - url: http://petstore.swagger.io/v1 +paths: + /: + get: + summary: List all pets + operationId: listPets + tags: + - pets + parameters: + - name: limit + in: query + description: How many items to return at one time (max 100) + required: false + schema: + type: integer + format: int32 + responses: + '200': + description: A paged array of pets + headers: + x-next: + description: A link to the next page of responses + schema: + type: string + +components: + schemas: + Custom: + x-table: v3_pgcustom + required: + - id + properties: + id: + type: integer + format: int64 + readOnly: True + num: + type: integer + default: 0 + json1: + type: array + x-db-type: json + default: [] + json2: + type: array + x-db-type: json + default: '{}' + json3: + type: array + x-db-type: json + default: + - foo: foobar + - xxx: yyy + json4: + type: array + x-db-type: json + default: '{"foo": "bar", "bar": "baz"}' + status: + type: string + x-db-type: enum + default: draft + enum: + - draft + - pending + - active diff --git a/tests/specs/postgres_custom/migrations/m200000_000000_create_table_v3_pgcustom.php b/tests/specs/postgres_custom/migrations/m200000_000000_create_table_v3_pgcustom.php new file mode 100644 index 00000000..3033a033 --- /dev/null +++ b/tests/specs/postgres_custom/migrations/m200000_000000_create_table_v3_pgcustom.php @@ -0,0 +1,25 @@ +createTable('{{%v3_pgcustom}}', [ + 'id' => $this->bigPrimaryKey(), + 'num' => $this->integer()->null()->defaultValue(0), + 'json1' => 'json NOT NULL DEFAULT \'[]\'', + 'json2' => 'json NOT NULL DEFAULT \'[]\'', + 'json3' => 'json NOT NULL DEFAULT \'[{"foo":"foobar"},{"xxx":"yyy"}]\'', + 'json4' => 'json NOT NULL DEFAULT \'{"foo":"bar","bar":"baz"}\'', + 'status' => 'enum(\'draft\', \'pending\', \'active\') NULL DEFAULT \'draft\'', + ]); + } + + public function down() + { + $this->dropTable('{{%v3_pgcustom}}'); + } +} diff --git a/tests/specs/postgres_custom/migrations_pgsql_db/m200000_000000_change_table_v3_pgcustom.php b/tests/specs/postgres_custom/migrations_pgsql_db/m200000_000000_change_table_v3_pgcustom.php new file mode 100644 index 00000000..8884549b --- /dev/null +++ b/tests/specs/postgres_custom/migrations_pgsql_db/m200000_000000_change_table_v3_pgcustom.php @@ -0,0 +1,39 @@ +execute('CREATE TYPE enum_status AS ENUM(\'draft\',\'pending\',\'active\')'); + $this->alterColumn('{{%v3_pgcustom}}', 'json1', "SET NOT NULL"); + $this->alterColumn('{{%v3_pgcustom}}', 'json1', 'SET DEFAULT \'[]\''); + $this->alterColumn('{{%v3_pgcustom}}', 'json2', "SET NOT NULL"); + $this->alterColumn('{{%v3_pgcustom}}', 'json2', 'SET DEFAULT \'[]\''); + $this->alterColumn('{{%v3_pgcustom}}', 'json3', "SET NOT NULL"); + $this->alterColumn('{{%v3_pgcustom}}', 'json3', 'SET DEFAULT \'[{"foo":"foobar"},{"xxx":"yyy"}]\''); + $this->alterColumn('{{%v3_pgcustom}}', 'json4', "SET NOT NULL"); + $this->alterColumn('{{%v3_pgcustom}}', 'json4', 'SET DEFAULT \'{"foo":"bar","bar":"baz"}\''); + $this->execute('DROP TYPE enum_status'); + $this->alterColumn('{{%v3_pgcustom}}', 'status', "enum_status USING status::enum_status"); + $this->alterColumn('{{%v3_pgcustom}}', 'status', "SET DEFAULT 'draft'"); + } + + public function safeDown() + { + $this->alterColumn('{{%v3_pgcustom}}', 'json1', "DROP NOT NULL"); + $this->alterColumn('{{%v3_pgcustom}}', 'json1', "DROP DEFAULT"); + $this->alterColumn('{{%v3_pgcustom}}', 'json2', "DROP NOT NULL"); + $this->alterColumn('{{%v3_pgcustom}}', 'json2', "DROP DEFAULT"); + $this->alterColumn('{{%v3_pgcustom}}', 'json3', "DROP NOT NULL"); + $this->alterColumn('{{%v3_pgcustom}}', 'json3', 'SET DEFAULT \'{"bar":"baz","foo":"bar"}\''); + $this->alterColumn('{{%v3_pgcustom}}', 'json4', "DROP NOT NULL"); + $this->alterColumn('{{%v3_pgcustom}}', 'json4', 'SET DEFAULT \'{"ffo":"bar"}\''); + $this->alterColumn('{{%v3_pgcustom}}', 'status', $this->string()); + $this->alterColumn('{{%v3_pgcustom}}', 'status', "DROP DEFAULT"); + $this->execute('CREATE TYPE enum_status AS ENUM(\'active\',\'draft\')'); + $this->execute('DROP TYPE enum_status'); + } +} diff --git a/tests/specs/postgres_custom/models/Custom.php b/tests/specs/postgres_custom/models/Custom.php new file mode 100644 index 00000000..e2856754 --- /dev/null +++ b/tests/specs/postgres_custom/models/Custom.php @@ -0,0 +1,10 @@ +language); + $uniqueFaker = new UniqueGenerator($faker); + $model = new Custom(); + $model->id = $uniqueFaker->numberBetween(0, 2147483647); + $model->num = $faker->numberBetween(0, 2147483647); + $model->json1 = []; + $model->json2 = []; + $model->json3 = []; + $model->json4 = []; + $model->status = $faker->randomElement(['draft','pending','active']); + return $model; + } +} diff --git a/tests/specs/postgres_custom/models/base/Custom.php b/tests/specs/postgres_custom/models/base/Custom.php new file mode 100644 index 00000000..80bea3b8 --- /dev/null +++ b/tests/specs/postgres_custom/models/base/Custom.php @@ -0,0 +1,35 @@ + ['draft', 'pending', 'active']], + [['json1', 'json2', 'json3', 'json4'], 'safe'], + ]; + } + +} From a461c65a7bf0e05f8695dccfe66538391290c7a9 Mon Sep 17 00:00:00 2001 From: insolita Date: Mon, 10 Aug 2020 14:36:57 +0800 Subject: [PATCH 7/7] fix #16 --- src/lib/ValidationRulesBuilder.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/lib/ValidationRulesBuilder.php b/src/lib/ValidationRulesBuilder.php index ba1f5dd2..d1117f50 100644 --- a/src/lib/ValidationRulesBuilder.php +++ b/src/lib/ValidationRulesBuilder.php @@ -100,8 +100,7 @@ private function addRulesByAttributeName(Attribute $attribute):void //@TODO: probably also patterns for file, image $patterns = [ '~e?mail~i' => 'email', - '~(url|site|website|href|link)~i' => 'url', - '~(ip|ipaddr)~i' => 'ip', + '~(url|site|website|href|link)~i' => 'url' ]; foreach ($patterns as $pattern => $validator) { if (preg_match($pattern, strtolower($attribute->columnName))) {