aheiland / fregata-symfony-bundle

该包已被放弃,不再维护。作者建议使用 check24-profis/fregata-symfony-bundle 包。

数据库迁移框架,允许在不同 DBMS 或数据库结构之间进行数据迁移。与 Symfony 配套使用。

v3.0.0 2021-12-08 11:03 UTC

README

Latest Stable Version License

Fregata 是一个数据迁移框架。您可以使用它迁移任何类型的数据,但它具有帮助您在不同 DBMS 或数据库结构之间迁移的功能。

文档:

  1. 简介
  2. 设置
    1. 安装
    2. 配置
      1. YAML 配置
  3. 组件
    1. 迁移注册表
    2. 迁移
      1. 选项
      2. 父迁移
    3. 任务
    4. 迁移器
      1. 拉取器
      2. 推送器
      3. 执行器
  4. 工具
    1. 迁移上下文
  5. 功能
    1. 依赖迁移器
    2. 批量拉取
    3. 外键迁移
  6. 命令行界面使用
    1. 列出迁移
    2. 获取迁移详细信息
    3. 执行迁移
  7. 贡献

简介

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 shellPHP容器内打开命令行。

如果您想测试框架的实现(使用Composer 路径存储库),在项目根目录下的_implementation目录中安装它,Git默认会忽略它,并确保您正在使用自己的实现自动加载器。