aymdev/fregata

数据库迁移框架,允许在不同DBMS或数据库结构之间进行数据迁移。

维护者

详细信息

github.com/AymDev/Fregata

源代码

问题

安装: 157

依赖项: 2

建议者: 0

安全: 0

星标: 23

关注者: 3

分支: 2

公开问题: 3

类型:框架

v1.1.0 2022-03-12 15:38 UTC

This package is auto-updated.

Last update: 2024-09-12 20:49:42 UTC


README

Latest Stable Version License

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

文档:

  1. 简介
  2. 设置
    1. 安装
    2. 配置
      1. 内核和服务容器
      2. YAML配置
  3. 组件
    1. 迁移注册表
    2. 迁移
      1. 选项
      2. 父迁移
    3. 任务
    4. 迁移器
      1. 拉取器
      2. 推送器
      3. 执行器
  4. 工具
    1. 迁移上下文
  5. 特性
    1. 依赖迁移器
    2. 批量拉取
    3. 外键迁移
  6. CLI使用
    1. 列出迁移
    2. 获取迁移的详细信息
    3. 执行迁移
  7. 贡献

简介

Fregata是一个数据迁移框架。它可能与ETL(提取-转换-加载)工具相似。

您可以使用它来迁移文件、数据库或任何您想要的数据,在这方面它是完全无差别的(其中一些测试迁移数据在PHP数组之间)。但请注意,它最初是针对数据库的,提供了一种在不同DBMS之间迁移数据的方法,即使它们的结构不同。一些包含的功能是专门为数据库构建的。

为什么要创建数据迁移框架?

虽然数据库迁移可能不是您日常的任务,但我多次在不同的项目上遇到了它。这就是为什么我创建了Fregata,以便我可以重用迁移工作流程。

有哪些用例?

以下是一些用例示例(来自经验)

  • 当您想从一种DBMS切换到另一种DBMS时
  • 当您想将预发布数据库与生产数据库同步时(适用于基于CMS的项目)

设置

安装

使用Composer安装

composer require aymdev/fregata

配置

Fregata默认期望您在项目根目录中有一个config和一个cache目录。

内核和服务容器

如果您需要使用与默认结构不同的目录结构,您可以扩展Fregata\Configuration\AbstractFregataKernel类。然后您将需要实现指定您的配置和缓存目录的方法。

重要:您的内核完全限定类名必须App\FregataKernel

内核包含一个服务容器,由Symfony的DependencyInjection组件构建。这意味着您可以在配置目录中的services.yaml文件中像在Symfony应用程序中那样定义自己的服务。

以下是一个推荐的最小services.yaml以启动您的项目

services:
    _defaults:
        autowire: true

    App\:
        resource: '../src/'

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键扩展迁移。子迁移将继承父的optionstasksmigrators。您仍然可以添加更多任务和迁移器,并覆盖选项。

任务

任务可以在执行迁移器之前或之后执行。它们在启动迁移(在任务之前)或清理临时数据(在任务之后)时非常有用。

use Fregata\Migration\TaskInterface;

class MyTask implements TaskInterface
{
    public function execute() : ?string
    {
        // perform some verifications, delete temporary data, ...
        return 'Optional result message';
    }
}

迁移器

迁移器是框架的主要组件。单个迁移器包含3个组件

  • 一个拉取器
  • 一个推送器
  • 一个执行器

它必须通过实现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使用

飞燕 提供了一个简单的程序来运行迁移,您可以通过以下方式启动它:

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 配置,提供 MySQL 5.7 服务。

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