okvpn / migration-bundle
使用 Doctrine 进行数据库架构迁移
Requires
- php: >=7.0
- doctrine/dbal: ^2.1
- doctrine/orm: ^2.1
- symfony/doctrine-bridge: ~2.7|~3.0|~4.0
- symfony/framework-bundle: ~2.7|~3.0|~4.0
Requires (Dev)
- phpunit/phpunit: ^5.7
- symfony/twig-bundle: ~3.4|~4.2
Conflicts
- twig/twig: <1.34|<2.4,>=2
README
数据库结构和数据操作器。
简介
OkvpnMigrationBundle 允许使用数据库无关的 PHP 代码编写数据库迁移,它使用外部的 doctrine/dbal 库 Doctrine 架构管理器。
<?php // src/Migrations/Schema/v1_5 namespace App\Migrations\Schema\v1_5; use Doctrine\DBAL\Schema\Schema; use Okvpn\Bundle\MigrationBundle\Migration\Migration; use Okvpn\Bundle\MigrationBundle\Migration\QueryBag; class AppMigration implements Migration { /** * {@inheritdoc} */ public function getMigrationVersion() { return 'v1_5'; } /** * {@inheritdoc} */ public function up(Schema $schema, QueryBag $queries) { $table = $schema->createTable('meteo'); $table->addColumn('id', 'integer', ['autoincrement' => true]); $table->addColumn('timestamp', 'datetime'); $table->addColumn('temp', 'decimal', ['precision' => 4, 'scale' => 2]); $table->addColumn('pressure', 'decimal', ['precision' => 6, 'scale' => 2]); $table->addColumn('humidity', 'decimal', ['precision' => 4, 'scale' => 2]); $table->setPrimaryKey(['id']); $table->addIndex(['timestamp']); } }
功能
- 使用数据库无关的 PHP 代码编写数据库迁移。
- 每个包内部定位迁移并支持多个位置。
- 与 Doctrine 和 Symfony 3-4 的不同版本兼容。
- 数据库结构迁移的扩展。
- 迁移前后的事件。
安装
使用 composer 安装
composer require okvpn/migration-bundle
如果您不使用 Symfony Flex,必须在应用中手动启用此包
Symfony 4 config/bundles.php
<?php return [ Okvpn\Bundle\MigrationBundle\OkvpnMigrationBundle::class => ['all' => true], //... ];
Symfony 2 - 3,在 app/AppKernel.php
中启用此包
<?php use Symfony\Component\HttpKernel\Kernel; class AppKernel extends Kernel { public function registerBundles() { $bundles = [ new Okvpn\Bundle\MigrationBundle\OkvpnMigrationBundle(), //... ]; } }
数据库结构迁移
每个包都可以有迁移文件,允许更新数据库架构。
迁移文件应位于 Migrations\Schema\version_number
文件夹中。版本号必须是一个 PHP 标准化的版本号字符串,但有一些限制。这个字符串不能包含 "." 和 "+" 字符作为版本部分的分隔符。更多关于 PHP 标准化版本号字符串的信息可以在 PHP 手册 中找到。
每个迁移类必须实现 Migration 接口,并且必须实现 up
方法。该方法接收当前数据库结构在 schema
参数和 queries
参数,后者可以用于添加额外的查询。
使用 schema
参数,您可以在不担心数据库引擎兼容性的情况下创建或更新数据库结构。如果您想在应用架构修改之前或之后执行额外的 SQL 查询,可以使用 queries
参数。此参数表示一个 查询包,允许添加将在应用之前(使用 addPreQuery
方法)或之后(使用 addQuery
或 addPostQuery
方法)执行的额外查询。查询可以是字符串或实现 MigrationQuery 接口类的实例。此接口有几个现成的实现。
- SqlMigrationQuery - 表示一个或多个 SQL 查询
- ParametrizedSqlMigrationQuery - 与前面的类类似,但每个查询都可以有自己的参数。
如果您需要创建 MigrationQuery 的自定义实现,则 ConnectionAwareInterface 可能很有帮助。只需在需要数据库连接的迁移查询类中实现此接口即可。您还可以使用 ParametrizedMigrationQuery 类作为迁移查询的基类。
如果您在同一版本内有多个迁移类并且需要确保它们按照指定的顺序执行,您可以使用OrderedMigrationInterface接口。
迁移文件示例
<?php namespace Acme\Bundle\TestBundle\Migrations\Schema\v1_0; use Doctrine\DBAL\Schema\Schema; use Okvpn\Bundle\MigrationBundle\Migration\Migration; use Okvpn\Bundle\MigrationBundle\Migration\QueryBag; use Okvpn\Bundle\MigrationBundle\Migration\Extension\RenameExtension; use Okvpn\Bundle\MigrationBundle\Migration\Extension\RenameExtensionAwareInterface; class AcmeTestBundle implements Migration, RenameExtensionAwareInterface { /** * @var RenameExtension */ protected $renameExtension; /** * @inheritdoc */ public function setRenameExtension(RenameExtension $renameExtension) { $this->renameExtension = $renameExtension; } /** * @inheritdoc */ public function up(Schema $schema, QueryBag $queries) { $table = $schema->createTable('test_table'); $table->addColumn('id', 'integer', ['autoincrement' => true]); $table->addColumn('created', 'datetime', []); $table->addColumn('field', 'string', ['length' => 500]); $table->addColumn('another_field', 'string', ['length' => 255]); $table->setPrimaryKey(['id']); $this->renameExtension->renameTable( $schema, $queries, 'old_table_name', 'new_table_name' ); $queries->addQuery( "ALTER TABLE another_table ADD COLUMN test_column INT NOT NULL", ); } }
每个包还可以有一个安装文件。此迁移文件替换运行多个迁移文件。安装迁移类必须实现Installation接口,并必须实现up
和getMigrationVersion
方法。getMigrationVersion
方法必须返回此安装文件替换的最大迁移版本号。
在安装过程中(这意味着您从零开始安装系统),如果找到安装迁移文件,它将首先加载,然后加载版本号大于getMigrationVersion
方法返回的版本的迁移文件。
例如。我们有v1_0
、v1_1
、v1_2
、v1_3
迁移。此外,我们还有一个安装迁移类。此类返回v1_2
作为迁移版本。因此,在安装过程中,将首先加载安装迁移文件,然后仅加载v1_3
迁移文件。从v1_0
到v1_2
的迁移将不会加载。
安装迁移文件示例
<?php namespace Acme\Bundle\TestBundle\Migrations\Schema; use Doctrine\DBAL\Schema\Schema; use Okvpn\Bundle\MigrationBundle\Migration\Installation; use Okvpn\Bundle\MigrationBundle\Migration\QueryBag; class AcmeTestBundleInstaller implements Installation { /** * @inheritdoc */ public function getMigrationVersion() { return 'v1_1'; } /** * @inheritdoc */ public function up(Schema $schema, QueryBag $queries) { $table = $schema->createTable('test_installation_table'); $table->addColumn('id', 'integer', ['autoincrement' => true]); $table->addColumn('field', 'string', ['length' => 500]); $table->setPrimaryKey(['id']); } }
要运行迁移,有okvpn:migration:load命令。此命令从包中收集迁移文件,按版本号排序,并应用更改。
此命令支持一些附加选项
- force - 导致由迁移生成的SQL语句在您的数据库中物理执行;
- dry-run - 输出迁移列表而不应用它们;
- show-queries - 输出每个迁移文件的数据库查询列表;
- bundles - 要加载数据的包列表。如果未设置此选项,则迁移将来自所有包;
- exclude - 要跳过的包名称列表。
此外,还有okvpn:migration:dump命令,用于帮助创建安装文件。此命令将当前数据库结构输出为纯sql或Doctrine\DBAL\Schema\Schema
查询。
此命令支持一些附加选项
- plain-sql - 输出为纯sql查询的schema;
- bundle - 生成迁移的包名称;
- migration-version - 迁移版本号。此选项将设置由生成的安装文件中的
getMigrationVersion
方法返回的值。
对于包来说,最好有当前版本的安装文件和从旧版本迁移到当前版本的迁移文件。
以下算法可以用于您包的新版本
- 创建新迁移
- 使用okvpn:migration:load应用它
- 使用okvpn:migration:dump生成新的安装文件
- 如果需要 - 向生成的安装文件中添加迁移扩展调用。
数据库结构迁移的扩展
有时您不能使用标准的Doctrine方法来修改数据库结构。例如,Schema::renameTable
不工作,因为它先删除现有表然后创建新表。为了帮助您管理这种情况,并允许在任何迁移中添加一些有用的功能,设计了一种扩展机制。以下示例显示了如何使用RenameExtension。
<?php namespace Acme\Bundle\TestBundle\Migrations\Schema\v1_0; use Doctrine\DBAL\Schema\Schema; use Okvpn\Bundle\MigrationBundle\Migration\Migration; use Okvpn\Bundle\MigrationBundle\Migration\QueryBag; use Okvpn\Bundle\MigrationBundle\Migration\Extension\RenameExtension; use Okvpn\Bundle\MigrationBundle\Migration\Extension\RenameExtensionAwareInterface; class AcmeTestBundle implements Migration, RenameExtensionAwareInterface { /** * @var RenameExtension */ protected $renameExtension; /** * @inheritdoc */ public function setRenameExtension(RenameExtension $renameExtension) { $this->renameExtension = $renameExtension; } /** * @inheritdoc */ public function up(Schema $schema, QueryBag $queries) { $this->renameExtension->renameTable( $schema, $queries, 'old_table_name', 'new_table_name' ); } }
如您所见,要使用RenameExtension,您的迁移类应实现RenameExtensionAwareInterface接口和setRenameExtension
方法。此外,还有一些额外的有用接口,您可以在迁移类中使用。
ContainerAwareInterface
- 提供对Symfony依赖容器的访问- DatabasePlatformAwareInterface - 允许编写数据库类型无关的迁移
- NameGeneratorAwareInterface - 提供对DbIdentifierNameGenerator类的访问,该类可以用来生成索引、外键约束等名称。
为数据库结构迁移创建自己的扩展
要创建自己的扩展,您需要完成以下简单的步骤
- 在
YourBundle/Migration/Extension
目录中创建一个扩展类。使用YourBundle/Migration/Extension
目录不是强制性的,但强烈推荐。例如
<?php namespace Acme\Bundle\TestBundle\Migration\Extension; use Doctrine\DBAL\Schema\Schema; use Okvpn\Bundle\MigrationBundle\Migration\QueryBag; class MyExtension { public function doSomething(Schema $schema, QueryBag $queries, /* other parameters, for example */ $tableName) { $table = $schema->getTable($tableName); // highly recommended to make sure that a table exists $query = 'SOME SQL'; /* or $query = new SqlMigrationQuery('SOME SQL'); */ $queries->addQuery($query); } }
- 在相同的命名空间中创建
*AwareInterface
。重要的是接口名称应该是{ExtensionClass}AwareInterface
,设置方法应该是set{ExtensionClass}({ExtensionClass} ${extensionName})
。例如
<?php namespace Acme\Bundle\TestBundle\Migration\Extension; /** * MyExtensionAwareInterface should be implemented by migrations that depends on a MyExtension. */ interface MyExtensionAwareInterface { /** * Sets the MyExtension * * @param MyExtension $myExtension */ public function setMyExtension(MyExtension $myExtension); }
- 在依赖容器中注册扩展。例如
parameters: acme_test.migration.extension.my.class: Acme\Bundle\TestBundle\Migration\Extension\MyExtension services: acme_test.migration.extension.my: class: %acme_test.migration.extension.my.class% tags: - { name: okvpn_migration.extension, extension_name: test /*, priority: -10 - priority attribute is optional an can be helpful if you need to override existing extension */ }
如果您的扩展类需要访问数据库平台或名称生成器,您应相应地实现DatabasePlatformAwareInterface或NameGeneratorAwareInterface。另外,如果您的扩展需要使用其他扩展,则扩展类应仅实现所需扩展的*AwareInterface
。