tobento/service-migration

适用于任何PHP应用的迁移管理器。

1.1.2 2024-08-13 14:00 UTC

This package is auto-updated.

Last update: 2024-09-13 14:13:09 UTC


README

迁移服务提供了一种灵活的方式来处理PHP应用的迁移。

目录

入门

运行此命令以添加运行中的迁移服务项目最新版本。

composer require tobento/service-migration

要求

  • PHP 8.0或更高版本

亮点

  • 框架无关,适用于任何项目
  • 解耦设计

简单示例

创建迁移

namespace App\Blog;

use Tobento\Service\Migration\MigrationInterface;
use Tobento\Service\Migration\ActionsInterface;
use Tobento\Service\Migration\Actions;
use Tobento\Service\Migration\Action\FilesCopy;
use Tobento\Service\Migration\Action\FilesDelete;
use Tobento\Service\Migration\Action\DirCopy;
use Tobento\Service\Migration\Action\DirDelete;

/**
 * BlogMigration
 */
class BlogMigration implements MigrationInterface
{
    /**
     * Return a description of the migration.
     *
     * @return string
     */    
    public function description(): string
    {
        return 'Blog migration.';
    }
        
    /**
     * Return the actions to be processed on install.
     *
     * @return ActionsInterface
     */    
    public function install(): ActionsInterface
    {            
        return new Actions(
            new FilesCopy(
                files: [
                    'dir/to/store/config/' => [
                        'dir/blog/config/blog.php',
                    ],         
                ], 
                description: 'Blog configuration files installed.',
            ),           
            new DirCopy(
                dir: 'dir/blog/views/',
                destDir: 'dir/to/store/views/blog/',
                description: 'Blog view files installed.',
            ),
        );
    }

    /**
     * Return the actions to be processed on uninstall.
     *
     * @return ActionsInterface
     */    
    public function uninstall(): ActionsInterface
    {
        return new Actions(
            new FilesDelete(
                files: [
                    'dir/to/store/config/' => [
                        'blog.php',
                    ],
                ],
                description: 'Blog configuration files uninstalled.',
            ),
            new DirDelete(
                dir: 'dir/to/store/views/blog/',
                description: 'Blog view files uninstalled.',
            ),            
        );
    }
}

安装/卸载迁移

use Tobento\Service\Migration\Migrator;
use Tobento\Service\Migration\AutowiringMigrationFactory;
use Tobento\Service\Migration\MigrationJsonFileRepository;
use Tobento\Service\Migration\MigrationInstallException;
use Tobento\Service\Migration\MigrationUninstallException;
use Tobento\Service\Container\Container;

// Any PSR-11 container
$container = new Container();

// Create migrator.
$migrator = new Migrator(
    new AutowiringMigrationFactory($container),
    new MigrationJsonFileRepository(__DIR__.'/migrations/'),
);

// Install a migration.
try {
    $result = $migrator->install(\App\Blog\BlogMigration::class);
} catch (MigrationInstallException $e) {
    // Handle exception.
}

// Uninstall a migration.
if ($migrator->isInstalled(\App\Blog\BlogMigration::class))
{
    try {
        $result = $migrator->uninstall(\App\Blog\BlogMigration::class);
    } catch (MigrationUninstallException $e) {
        // Handle exception.
    } 
}

文档

迁移

创建迁移

您的迁移类必须实现MigrationInterface接口

use Tobento\Service\Migration\MigrationInterface;
use Tobento\Service\Migration\ActionsInterface;
use Tobento\Service\Migration\Actions;
use Tobento\Service\Migration\Action\FilesCopy;
use Tobento\Service\Migration\Action\FilesDelete;
use Tobento\Service\Migration\Action\DirCopy;
use Tobento\Service\Migration\Action\DirDelete;

/**
 * BlogMigration
 */
class BlogMigration implements MigrationInterface
{
    /**
     * Return a description of the migration.
     *
     * @return string
     */    
    public function description(): string
    {
        return 'Blog migration.';
    }
        
    /**
     * Return the actions to be processed on install.
     *
     * @return ActionsInterface
     */    
    public function install(): ActionsInterface
    {            
        return new Actions(
            new FilesCopy(
                files: [
                    'dir/to/store/config/' => [
                        'dir/blog/config/blog.php',
                    ],         
                ], 
                description: 'Blog configuration files installed.',
            ),           
            new DirCopy(
                dir: 'dir/blog/views/',
                destDir: 'dir/to/store/views/blog/',
                description: 'Blog view files installed.',
            ),
        );
    }

    /**
     * Return the actions to be processed on uninstall.
     *
     * @return ActionsInterface
     */    
    public function uninstall(): ActionsInterface
    {
        return new Actions(
            new FilesDelete(
                files: [
                    'dir/to/store/config/' => [
                        'blog.php',
                    ],
                ],
                description: 'Blog configuration files uninstalled.',
            ),
            new DirDelete(
                dir: 'dir/to/store/views/blog/',
                description: 'Blog view files uninstalled.',
            ),            
        );
    }
}

操作

可调用

CallableAction::class在处理过程中调用指定的可调用对象。

use Tobento\Service\Migration\Action\CallableAction;
use Tobento\Service\Migration\ActionFailedException;

$action = new CallableAction(
    callable: function ($name) {
        // do something on process
    },
    // you may set parameters passed to the callable:
    parameters: ['name' => 'value'],
    name: 'A unique name', // or null
    description: 'Some description.', // (optional)
    type: 'keyword', // (optional)
);

// Get the callable:
var_dump(is_callable($action->getCallable()));
// bool(true)

// Get the parameters:
var_dump($action->getParameters());
// array(1) { ["name"]=> string(5) "value" }

var_dump($action->description());
// string(17) "Some description."

var_dump($action->type());
// string(7) "keyword"

目录复制

使用DirCopy::class操作将目录复制到另一个目的地。

use Tobento\Service\Migration\Action\DirCopy;

$action = new DirCopy(
    dir: 'dir/blog/views/',
    destDir: 'dir/to/store/views/blog/',
    overwrite: true, // if to overwrite existing dir (default true)
    name: 'A unique name', // or null
    description: 'Blog view files installed.',
    type: 'keyword', // (optional)
);

var_dump($action->getDir());
// string(15) "dir/blog/views/"

var_dump($action->getDestDir());
// string(24) "dir/to/store/views/blog/"

var_dump($action->description());
// string(26) "Blog view files installed."

var_dump($action->type());
// string(7) "keyword"

目录删除

使用DirDelete::class操作删除目录。

use Tobento\Service\Migration\Action\DirDelete;

$action = new DirDelete(
    dir: 'dir/to/store/views/blog/',
    name: 'A unique name', // or null
    description: 'Blog view files uninstalled.',
    type: 'keyword', // (optional)
);

var_dump($action->getDir());
// string(24) "dir/to/store/views/blog/"

var_dump($action->description());
// string(28) "Blog view files uninstalled."

var_dump($action->type());
// string(7) "keyword"

失败

Fail::class在操作过程中总是失败,抛出ActionFailedException::class,这在某些情况下可能很有用。

use Tobento\Service\Migration\Action\Fail;
use Tobento\Service\Migration\ActionFailedException;

$action = new Fail(
    failMessage: 'message',
    name: 'A unique name', // or null
    description: 'Some description.', // (optional)
    type: 'keyword', // (optional)
);

$action->process();
// throws ActionFailedException with the specified fail message.

文件复制

使用FilesCopy::class操作将文件复制到另一个目录。

use Tobento\Service\Migration\Action\FilesCopy;

$action = new FilesCopy(
    files: [
        'dir/to/store/config/' => [
            'dir/blog/config/blog.php',
        ],         
    ],
    overwrite: true, // if to overwrite existing files (default true)
    name: 'A unique name', // or null
    description: 'Blog configuration files installed.',
    type: 'keyword', // (optional)
);

var_dump($action->getFiles());
// array(1) { ["dir/to/store/config/"]=> array(1) { [0]=> string(24) "dir/blog/config/blog.php" } }

// only available after processing the action.
var_dump($action->getCopiedFiles());
// array(0) { }

// only available after processing the action.
var_dump($action->getSkippedFiles());
// array(0) { }

var_dump($action->description());
// string(35) "Blog configuration files installed."

var_dump($action->type());
// string(7) "keyword"

文件删除

使用FilesDelete::class操作删除文件。

use Tobento\Service\Migration\Action\FilesDelete;

$action = new FilesDelete(
    files: [
        'dir/to/store/config/' => [
            'blog.php',
        ],
    ],
    name: 'A unique name', // or null
    description: 'Blog configuration files uninstalled.',
    type: 'keyword', // (optional)
);
            
var_dump($action->getFiles());
// array(1) { ["dir/to/store/config/"]=> array(1) { [0]=> string(8) "blog.php" } }

// only available after processing the action.
var_dump($action->getDeletedFiles());
// array(0) { }

var_dump($action->description());
// string(37) "Blog configuration files uninstalled."

var_dump($action->type());
// string(7) "keyword"

文件字符串替换器

使用FileStringReplacer::class替换文件中的字符串。

use Tobento\Service\Migration\Action\FileStringReplacer;

$action = new FileStringReplacer(
    file: 'dir/config/http.php',
    replace: [
        '{key1}' => 'value1',
        '{key2}' => 'value2',
    ],
    name: 'A unique name', // or null
    description: 'Strings replaced.',
    type: 'keyword', // (optional)
);
            
var_dump($action->getFile());
// string(19) "dir/config/http.php"

var_dump($action->getReplace());
// array(2) { ["{key1}"]=> string(6) "value1" ["{key2}"]=> string(6) "value2" }

var_dump($action->description());
// string(17) "Strings replaced."

var_dump($action->type());
// string(7) "keyword"

空值

NullAction::class根本不执行任何操作,这在某些情况下可能很有用。

use Tobento\Service\Migration\Action\NullAction;

$action = new NullAction(
    name: 'A unique name', // or null
    description: 'Some description.', // (optional)
    type: 'keyword', // (optional)
);

PDO执行

使用PdoExec::class执行pdo语句。

use Tobento\Service\Migration\Action\PdoExec;

$action = new PdoExec(
    pdo: $pdo,
    statements: [
        "CREATE TABLE IF NOT EXISTS blog (
            id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
            active tinyint(1) UNSIGNED NOT NULL DEFAULT '0',
            PRIMARY KEY (id)
        ) ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci AUTO_INCREMENT=0",        
    ],
    name: 'A unique name', // or null
    description: 'Blog database tables installed.',
    type: 'keyword', // (optional)
);
            
var_dump($action->getStatements());
// array

var_dump($action->description());
// string(31) "Blog database tables installed."

var_dump($action->type());
// string(7) "keyword"

自定义操作

以下是一个编写自定义操作的示例。

use Tobento\Service\Migration\ActionInterface;
use Tobento\Service\Migration\ActionFailedException;

class CustomAction implements ActionInterface
{        
    /**
     * Process the action.
     *
     * @return void
     * @throws ActionFailedException
     */    
    public function process(): void
    {
        // process the action
    }
    
    /**
     * Returns a name of the action.
     *
     * @return string
     */
    public function name(): string
    {
        return $this::class;
    }

    /**
     * Returns a description of the action.
     *
     * @return string
     */    
    public function description(): string
    {
        return 'Action Description';
    }
    
    /**
     * Returns the type of the action.
     *
     * @return string
     */
    public function type(): string
    {
        return 'keyword';
    }
    
    /**
     * Returns the processed data information.
     *
     * @return array<array-key, string>
     */
    public function processedDataInfo(): array
    {
        return [];
    }
}

处理操作

您可以在不使用迁移器的情况下处理操作。

use Tobento\Service\Migration\Action\DirCopy;
use Tobento\Service\Migration\ActionFailedException;

$action = new DirCopy(
    dir: 'dir/blog/views/',
    destDir: 'dir/to/store/views/blog/',
    description: 'Blog view files installed.',
);

try {
    $action->process();
} catch (ActionFailedException $e) {
    // Handle exception.
}

迁移器

创建迁移器

use Tobento\Service\Migration\Migrator;
use Tobento\Service\Migration\MigratorInterface;
use Tobento\Service\Migration\AutowiringMigrationFactory;
use Tobento\Service\Migration\MigrationJsonFileRepository;
use Tobento\Service\Container\Container;

// Any PSR-11 container
$container = new Container();

// Create migrator.
$migrator = new Migrator(
    new AutowiringMigrationFactory($container),
    new MigrationJsonFileRepository('private/dir/migrations/'),
);

var_dump($migrator instanceof MigratorInterface);
// bool(true)

安装迁移

use Tobento\Service\Migration\MigrationResultInterface;
use Tobento\Service\Migration\MigrationInstallException;

try {
    $result = $migrator->install(AnyMigration::class);
    
    var_dump($result instanceof MigrationResultInterface);
    // bool(true)
        
} catch (MigrationInstallException $e) {
    // Handle exception.
}

如果您安装失败,您可能想卸载。

use Tobento\Service\Migration\MigrationResultInterface;
use Tobento\Service\Migration\MigrationInstallException;
use Tobento\Service\Migration\MigrationUninstallException;

try {
    $result = $migrator->install(AnyMigration::class);        
} catch (MigrationInstallException $e) {
    try {
        $result = $migrator->uninstall(AnyMigration::class);
    } catch (MigrationUninstallException $e) {
        // Handle exception.
    }
}

卸载迁移

use Tobento\Service\Migration\MigrationResultInterface;
use Tobento\Service\Migration\MigrationUninstallException;

try {
    $result = $migrator->uninstall(AnyMigration::class);

    var_dump($result instanceof MigrationResultInterface);
    // bool(true)

} catch (MigrationUninstallException $e) {
    // Handle exception.
}

您可能只想在迁移之前已安装的情况下卸载。

use Tobento\Service\Migration\MigrationResultInterface;
use Tobento\Service\Migration\MigrationUninstallException;

if ($migrator->isInstalled(\App\Blog\AnyMigration::class))
{
    try {
        $result = $migrator->uninstall(AnyMigration::class);
    } catch (MigrationUninstallException $e) {
        // Handle exception.
    }
}

获取已安装的迁移

$migrations = $migrator->getInstalled();

// get only from specific.
$migrations = $migrator->getInstalled(namespace: 'App\Blog\');

迁移结果

迁移器安装和卸载方法在成功时返回迁移结果对象

use Tobento\Service\Migration\MigrationResultInterface;
use Tobento\Service\Migration\MigrationInterface;

$result = $migrator->install(AnyMigration::class);
var_dump($result instanceof MigrationResultInterface);
// bool(true)

$result = $migrator->uninstall(AnyMigration::class);
var_dump($result instanceof MigrationResultInterface);
// bool(true)

获取处理过的迁移

use Tobento\Service\Migration\MigrationInterface;

// Get the migration processed:
var_dump($result->migration() instanceof MigrationInterface);
// bool(true)

获取处理过的迁移操作

use Tobento\Service\Migration\ActionsInterface;

// Get the migration actions processed:
var_dump($result->actions() instanceof ActionsInterface);
// bool(true)

// You might want to iterate over the actions.
foreach($result->actions() as $action)
{
    $description = $action->description();
}

迁移结果

您可能想将结果存储起来,以便稍后查看已处理的迁移。

use Tobento\Service\Migration\Migrator;
use Tobento\Service\Migration\MigratorInterface;
use Tobento\Service\Migration\AutowiringMigrationFactory;
use Tobento\Service\Migration\MigrationJsonFileRepository;
use Tobento\Service\Migration\MigrationInstallException;
use Tobento\Service\Migration\MigrationResults;
use Tobento\Service\Migration\MigrationResultsInterface;
use Tobento\Service\Migration\MigrationResults;
use Tobento\Service\Container\Container;

// Any PSR-11 container
$container = new Container();

// Create migrator.
$migrator = new Migrator(
    new AutowiringMigrationFactory($container),
    new MigrationJsonFileRepository('private/dir/migrations/'),
);

// MigrationResults implementation.
$container->set(MigrationResultsInterface::class, function() {
    return new MigrationResults();
});

try {
    $result = $migrator->install(AnyMigration::class);
    
    // Add result.
    $container->get(MigrationResultsInterface::class)->add($result);

} catch (MigrationInstallException $e) {
    // Handle exception.
}

// Somewhere later, do something with the results.
$results = $container->get(MigrationResultsInterface::class)->all();

致谢