ramunasd / migration-bundle
Doctrine的架构和数据迁移
Requires
- php: >=5.5
- doctrine/data-fixtures: ~1.1
- doctrine/dbal: ~2.5
- doctrine/orm: ~2.5
- symfony/symfony: ~2.7
Requires (Dev)
- doctrine/doctrine-bundle: ^1.6
This package is auto-updated.
Last update: 2023-12-29 02:32:20 UTC
README
基于Doctrine的数据库架构和固定数据操作器。
概述
MigrationBundle是开源ORO平台迁移包的衍生版本。ORO开发者创建了一个非常好的工具,但他们不感兴趣于社区的贡献。这就是为什么我们衍生了这个包,使其对每个人都可以使用。
特性
- 数据库无关迁移
- 语义化迁移版本
- 快速安装到最新版本
- 自动安装器生成
- 版本化固定数据和示例数据
- 自定义扩展
- 迁移钩子
安装
将包添加到composer.json
composer require ramunasd/migration-bundle
然后将包添加到应用程序内核
// app/AppKernel.php <?php use Symfony\Component\HttpKernel\Kernel; class AppKernel extends Kernel { public function registerBundles() { $bundles = array( // ... new Rdv\Bundle\MigrationBundle\RdvMigrationBundle(), ); } // ... }
配置
rdv_migration: migration_path: "Migrations/Schema" fixtures_path_main: "Migrations/Data/ORM" fixtures_path_demo: "Migrations/Data/Demo/ORM"
数据库结构迁移
每个包可以有自己的迁移文件,允许更新数据库架构。
迁移文件应位于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 RDV\Bundle\MigrationBundle\Migration\Migration; use RDV\Bundle\MigrationBundle\Migration\QueryBag; use RDV\Bundle\MigrationBundle\Migration\Extension\RenameExtension; use RDV\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 RDV\Bundle\MigrationBundle\Migration\Installation; use RDV\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']); } }
要运行迁移,有 rdv:migration:load 命令。此命令从包中收集迁移文件,按版本号排序并应用更改。
此命令支持一些附加选项
- force - 导致生成的迁移 SQL 语句物理执行到您的数据库中;
- dry-run - 输出迁移列表而不应用它们;
- show-queries - 输出每个迁移文件的数据库查询列表;
- bundles - 要加载数据的包列表。如果没有设置此选项,则从所有包中获取迁移;
- exclude - 要跳过的包名称列表。
此外,还有 rdv:migration:dump 命令,可帮助创建安装文件。此命令将当前数据库结构作为纯 sql 或作为 Doctrine\DBAL\Schema\Schema
查询输出。
此命令支持一些附加选项
- plain-sql - 以纯 sql 查询的形式输出模式;
- bundle - 要生成迁移的包名称;
- migration-version - 迁移版本号。此选项将设置由生成的安装文件中的
getMigrationVersion
方法返回的值。
对于包来说,有一个很好的做法是拥有当前版本的安装文件和从先前版本迁移到当前版本的迁移文件。
以下算法可用于您包的新版本
- 创建新的迁移
- 使用 rdv:migration:load 应用它
- 使用 rdv:migration:dump 生成新的安装文件
- 如果需要,将迁移扩展调用添加到生成的安装中。
数据库结构迁移的扩展
有时您无法使用标准的Doctrine方法来修改数据库结构。例如,Schema::renameTable
方法不起作用,因为它会先删除现有表,然后创建新表。为了帮助您处理此类情况,并允许在任何迁移中添加一些有用的功能,我们设计了一种扩展机制。以下示例展示了如何使用RenameExtension。
<?php namespace Acme\Bundle\TestBundle\Migrations\Schema\v1_0; use Doctrine\DBAL\Schema\Schema; use RDV\Bundle\MigrationBundle\Migration\Migration; use RDV\Bundle\MigrationBundle\Migration\QueryBag; use RDV\Bundle\MigrationBundle\Migration\Extension\RenameExtension; use RDV\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 RDV\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: rdv_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
。
数据固定
Symfony允许使用数据固定加载数据。但是,这些固定每次执行doctrine:fixtures:load
命令时都会运行。
为了避免多次加载相同的固定,创建了rdv:migration:data:load
命令。此命令保证每个数据固定只加载一次。
此命令支持两种类型的迁移文件:main
数据固定和demo
数据固定。在安装过程中,用户可以选择是否加载演示数据。
此命令的数据固定应放在Migrations/Data/ORM
或Migrations/Data/Demo/ORM
目录中。
可以使用标准Doctrine排序或依赖关系功能更改固定顺序。有关固定排序的更多信息,请参阅doctrine数据固定手册。
版本化固定
有些固定需要多次执行。例如,上传国家数据的固定。通常,如果您添加新的国家列表,您需要创建新的数据固定来上传这些数据。为了避免这种情况,您可以使用版本化数据固定。
为了使设备版本化,此设备必须实现VersionedFixtureInterface接口。方法getVersion
返回设备数据的版本,而getLoadedVersion
返回当前加载的设备的版本。
示例
<?php namespace Acme\DemoBundle\Migrations\Data\ORM; use Doctrine\Common\DataFixtures\AbstractFixture; use Doctrine\Common\Persistence\ObjectManager; use RDV\Bundle\MigrationBundle\Fixture\VersionedFixtureInterface; class LoadSomeDataFixture extends AbstractFixture implements VersionedFixtureInterface { /** * @var string */ protected $loadedVersion; /** * {@inheritdoc} */ public function getVersion() { return '1.0'; } /** * {@inheritdoc} */ public function setLoadedVersion($version = null) { $this->loadedVersion = $version; } /** * {@inheritdoc} */ public function load(ObjectManager $manager) { // Here we can use fixture data code which will be run time after time if ($this->loadedVersion === null) { // loadedVersion is null for first time } } }
在此示例中,如果设备尚未加载,它将被加载,并将版本1.0保存为当前加载的此设备版本。
为了能够再次加载此设备,设备必须返回一个大于1.0的版本,例如1.0.1或1.1。版本号必须是PHP标准化的版本号字符串。有关PHP标准化版本号字符串的更多信息,请参阅PHP手册。