aheiland / fregata-symfony-bundle
数据库迁移框架,允许在不同 DBMS 或数据库结构之间进行数据迁移。与 Symfony 配套使用。
Requires
- php: >=8.1
- doctrine/dbal: ^3.0
- marcj/topsort: ^2.0
- symfony/config: ^6.0
- symfony/console: ^6.0
- symfony/dependency-injection: ^6.0
- symfony/finder: ^6.0
- symfony/http-kernel: ^6.0
- symfony/string: ^6.0
- symfony/yaml: ^6.0
Requires (Dev)
- mikey179/vfsstream: ^1.6
- phpunit/phpunit: ^9.1
This package is not auto-updated.
Last update: 2021-12-08 11:07:26 UTC
README
Fregata 是一个数据迁移框架。您可以使用它迁移任何类型的数据,但它具有帮助您在不同 DBMS 或数据库结构之间迁移的功能。
文档:
简介
Fregata 是一个数据迁移框架。它可能可以与 ETL (提取 - 转换 - 加载) 工具进行比较。
您可以使用它从文件、数据库或任何您想要的地方迁移数据,它在这一点上完全无偏见(一些测试迁移数据在 PHP 数组之间)。但请注意,它最初是针对数据库的,提供了一种在不同 DBMS 之间迁移数据的方法,即使结构不同。一些包含的功能专门为数据库构建。
为什么创建一个数据迁移框架?
虽然数据库迁移可能不是您日常工作任务,但我曾多次在不同项目中遇到它。这就是为什么我创建了 Fregata,以便我可以重用迁移工作流程。
有哪些用例?
以下是一些示例用例(基于经验)
- 当您想从一种 DBMS 转换到另一种时
- 当您想同步您的预发布数据库与生产数据库时(对于基于 CMS 的项目很有用)
设置
安装
使用 Composer 安装
composer require check24-profis/fregata-symfony-bundle
配置
YAML 配置
为了配置 Fregata 本身,您需要在配置目录中有一个 fregata.yaml
文件。
示例配置文件
fregata: migrations: # define any name for your migration main_migration: # define custom options for your migrations options: custom_opt: 'opt_value' special_cfg: foo: bar # load migrators from a directory # use the %fregata.root_dir% parameter to define a relative path from the project root migrators_directory: '%fregata.root_dir%/src/MainMigration' # load individual migrators # can be combined with migrators_directory migrators: - App\MainMigration\FirstMigrator # load tasks to execute before or after the migrators tasks: before: - App\MainMigration\BeforeTask after: - App\MainMigration\AfterTask other_migration: # extend an other migration to inherit its options, tasks and migrators parent: main_migration # overwrite a part of the options options: custom_opt: 'another_value' # load additional migrators or tasks migrators: - App\OtherMigration\Migrator
组件
迁移注册表
迁移注册表包含所有定义的迁移。您不需要与之交互。
迁移
迁移项目包含迁移的步骤。例如,从您的生产数据库到预发布数据库的数据迁移。每个迁移都根据您的配置创建并保存到注册表中。您不需要自己实例化迁移对象。
迁移包含 任务 和 迁移器。当运行迁移时,组件按照以下顺序执行
- 在任务之前
- 迁移器
- 在任务之后
选项
您可能需要为迁移项目设置特定的配置,这些配置可以被 任务 或 迁移器 使用。使用 options
键,您可以定义特定的迁移配置,它们将可通过 迁移上下文 访问。
父迁移
当有多个针对不同环境的 迁移 时,您可能希望避免整个配置的重复。您可以使用 parent
键扩展迁移。子迁移将继承父迁移的 选项、任务 和 迁移器。您仍然可以添加更多任务和迁移器,并覆盖选项。
任务
任务 可以在 迁移器 之前或之后执行。它们在启动迁移(在任务之前)或清理临时数据(在任务之后)时非常有用。
use Fregata\Migration\TaskInterface; class MyTask implements TaskInterface { public function execute() : ?string { // perform some verifications, delete temporary data, ... return 'Optional result message'; } }
迁移器
迁移器 是框架的主要组件。单个迁移器包含 3 个组件
- a puller
- a pusher
- an executor
它必须通过实现 Fregata\Migration\Migrator\MigratorInterface
从getter方法返回其组件。一个 迁移器 表示将数据从 源 迁移到 目标。例如,从 MySQL 表迁移到 PostgreSQL 表。
拉取器
拉取器 是一个负责从 源 拉取数据的 迁移器 组件。它返回数据以及可选的迁移项数。
use Doctrine\DBAL\Connection; use Fregata\Migration\Migrator\Component\PullerInterface; class Puller implements PullerInterface { private Connection $connection; public function __construct(Connection $connection) { $this->connection = $connection; } public function pull() { return $this->connection ->executeQuery('SELECT * FROM my_table') ->fetchAllAssociative(); } public function count() : ?int { return $this->connection ->executeQuery('SELECT COUNT(*) FROM my_table') ->fetchColumn(); } }
推送器
推送器 逐个获取 拉取器 获取的项目,并将数据推送到 目标。
use Doctrine\DBAL\Connection; use Fregata\Migration\Migrator\Component\PusherInterface; class Pusher implements PusherInterface { private Connection $connection; public function __construct(Connection $connection) { $this->connection = $connection; } /** * @return int number of items inserted */ public function push($data): int { return $this->connection->executeStatement( 'INSERT INTO my_table VALUES (:foo, :bar, :baz)', [ 'foo' => $data['foo'], 'bar' => some_function($data['bar']), 'baz' => 'default value', ] ); } }
在这里,$data
是示例 拉取器 返回值中的一个单独的项目。push()
方法被多次调用。将 拉取器 和 推送器 分离,允许您在不同来源之间迁移:从文件拉取并将数据推送到数据库等。
执行器
执行器 是将 拉取器 与 推送器 连接的组件。默认的执行器已提供,并适用于大多数情况:Fregata\Migration\Migrator\Component\Executor
。如果需要特定的行为,可以扩展默认的 执行器。
工具
迁移上下文
您可以通过将 Fregata\Migration\MigrationContext
服务注入到 任务 或 迁移 中来获取有关当前 迁移 的某些信息。
它提供以下内容:
- 当前 迁移 对象
- 当前迁移 名称
- 迁移 选项
- 如果适用,则提供 父迁移 名称
特性
依赖迁移器
如果您的 迁移器 需要按照特定顺序执行,您可以定义依赖关系,并将自动排序。
use Fregata\Migration\Migrator\DependentMigratorInterface; class DependentMigrator implements DependentMigratorInterface { public function getDependencies() : array { return [ DependencyMigrator::class, ]; } // other migrator methods ... }
在这里,DependencyMigrator
将在 DependentMigrator
之前执行。
批量拉取
当使用非常大的数据集时,您可能希望按块提取数据。
use Doctrine\DBAL\Connection; use Fregata\Migration\Migrator\Component\BatchPullerInterface; class BatchPulling implements BatchPullerInterface { private Connection $connection; private ?int $count = null; public function __construct(Connection $connection) { $this->connection = $connection; } public function pull(): \Generator { $limit = 50; $offset = 0; while ($offset < $this->count()) { yield $this->connection ->executeQuery(sprintf('SELECT * FROM my_table LIMIT %d, %d', $offset, $limit)) ->fetchAllAssociative(); $offset += $limit; } } public function count() : ?int { if (null === $this->count) { $this->count = $this->connection ->executeQuery('SELECT COUNT(*) FROM my_table') ->fetchColumn(); } return $this->count; } }
外键迁移
数据库迁移中最复杂的部分之一是关于外键。执行有效的外键迁移需要遵循多个步骤。这使用的是Doctrine DBAL。
您必须向迁移中添加2个任务
- 前置任务:
Fregata\Adapter\Doctrine\DBAL\ForeignKey\Task\ForeignKeyBeforeTask
- 后置任务:
Fregata\Adapter\Doctrine\DBAL\ForeignKey\Task\ForeignKeyAfterTask
前置任务将在目标数据库中创建临时列以保留原始引用和引用列。它还可以更改引用列以允许NULL
(仅当您指定时)。后置任务将在原始引用列中设置实际值,然后删除临时列。
然后迁移者必须提供数据库连接和外键列表
use Doctrine\DBAL\Connection; use Doctrine\DBAL\Schema\ForeignKeyConstraint; use Fregata\Adapter\Doctrine\DBAL\ForeignKey\ForeignKey; use Fregata\Adapter\Doctrine\DBAL\ForeignKey\Migrator\HasForeignKeysInterface; class ReferencingMigrator implements HasForeignKeysInterface { private Connection $connection; public function __construct(Connection $connection) { $this->connection = $connection; } public function getConnection() : Connection { return $this->connection; } /** * List the foreign keys constraints to keep * @return ForeignKey[] */ public function getForeignKeys() : array { $constraints = $this->connection->getSchemaManager()->listTableForeignKeys('my_table'); return array_map( function (ForeignKeyConstraint $constraint) { return new ForeignKey( $constraint, // DBAL constraint object 'target_referencing', // name of the referencing table ['fk'] // columns to change to allow NULL (will be set back to NOT NULL in the after task) ); }, $constraints ); } // other migrator methods ... }
迁移者负责数据迁移,这意味着您需要用源数据库中的原始主键/外键填充临时列。要在您的推送器中获取临时列的名称,需要引入CopyColumnHelper
服务。
use Doctrine\DBAL\Connection; use Fregata\Adapter\Doctrine\DBAL\ForeignKey\CopyColumnHelper; use Fregata\Migration\Migrator\Component\PusherInterface; class ReferencingForeignKeyPusher implements PusherInterface { private Connection $connection; private CopyColumnHelper $columnHelper; public function __construct(Connection $connection, CopyColumnHelper $columnHelper) { $this->connection = $connection; $this->columnHelper = $columnHelper; } /** * @return int number of items inserted */ public function push($data): int { return $this->connection->executeStatement( sprintf( 'INSERT INTO my_table (column, %s) VALUES (:value, :old_fk)', $this->columnHelper->localColumn('my_table', 'fk_column') ), [ 'value' => $data['value'], 'old_fk' => $data['fk_column'], ] ); } }
此示例显示的是本地(或引用)侧,但这也需要在外部(或引用)侧完成,使用CopyColumnHelper::foreignColumn()
。
CLI使用方法
Fregata提供了一个简单的程序来运行迁移,您可以使用以下命令启动它:
php vendor/bin/fregata
列出迁移
要列出您的安装迁移,请运行migration:list
命令。
> php vendor/bin/fregata migration:list
Registered migrations: 2
========================
main_migration
other_migration
获取迁移详细信息
要获取有关单个迁移的信息,请运行migration:show
命令。
> php vendor/bin/fregata migration:show main_migration main_migration : 1 migrators ============================ --- --------------------------------- # Migrator Name --- --------------------------------- 0 App\MainMigration\FirstMigrator --- ---------------------------------
执行迁移
运行迁移最重要的命令是:migration:execute
。
> php vendor/bin/fregata migration:execute main_migration Confirm execution of the "main_migration" migration ? (yes/no) [no]: > yes [OK] Starting "main_migration" migration: 1 migrators Before tasks: 1 =============== App\MainMigration\BeforeTask : OK Migrators: 1 ============ 0 - Executing "App\MainMigration\FirstMigrator" [3 items] : =========================================================== 3/3 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100% After tasks: 1 ============== App\MainMigration\AfterTask : OK [OK] Migrated successfully !
贡献
有一个可用的Docker设置,运行make start
来启动服务,运行make shell
在PHP容器内打开命令行。
如果您想测试框架的实现(使用Composer 路径存储库),在项目根目录下的_implementation
目录中安装它,Git默认会忽略它,并确保您正在使用自己的实现自动加载器。