effiana/migration-bundle 问题修复 & 功能扩展

解决BUG、新增功能、兼容多环境部署,快速响应你的开发需求

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

effiana/migration-bundle

Composer 安装命令:

composer require effiana/migration-bundle

包简介

Schema and data migration for BAP

README 文档

README

EffianaMigrationBundle extends DBAL (database abstraction layer) and provides the ability for developers to manage and deploy changes in the application database schema programmatically in a consistent, structured way using Migrations and Fixtures classes.

Database structure migrations

Each bundle can have migration files that allow to update database schema.

Migration files should be located in Migrations\Schema\version_number folder. A version number must be an PHP-standardized version number string, but with some limitations. This string must not contain "." and "+" characters as a version parts separator. More info about PHP-standardized version number string can be found in PHP manual.

Each migration class must implement Migration interface and must implement up method. This method receives a current database structure in schema parameter and queries parameter which can be used to add additional queries.

With schema parameter, you can create or update database structure without fear of compatibility between database engines. If you want to execute additional SQL queries before or after applying a schema modification, you can use queries parameter. This parameter represents a query bag and allows to add additional queries which will be executed before (addPreQuery method) or after (addQuery or addPostQuery method). A query can be a string or an instance of a class implements MigrationQuery interface. There are several ready to use implementations of this interface:

If you need to create own implementation of MigrationQuery the ConnectionAwareInterface can be helpful. Just implement this interface in your migration query class if you need a database connection. Also you can use ParametrizedMigrationQuery class as a base class for your migration query.

If you have several migration classes within the same version and you need to make sure that they will be executed in a specified order you can use OrderedMigrationInterface interface.

Example of migration file:

<?php

namespace Acme\Bundle\TestBundle\Migrations\Schema\v1_0;

use Doctrine\DBAL\Schema\Schema;
use Effiana\MigrationBundle\Migration\Migration;
use Effiana\MigrationBundle\Migration\QueryBag;
use Effiana\MigrationBundle\Migration\Extension\RenameExtension;
use Effiana\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",
        );
    }
}

Each bundle can have an installation file as well. This migration file replaces running multiple migration files. Install migration class must implement Installation interface and must implement up and getMigrationVersion methods. The getMigrationVersion method must return max migration version number that this installation file replaces.

During an install process (it means that you installs a system from a scratch), if install migration file was found, it will be loaded first and then migration files with versions greater then a version returned by getMigrationVersion method will be loaded.

For example. We have v1_0, v1_1, v1_2, v1_3 migrations. And additionally, we have install migration class. This class returns v1_2 as a migration version. So, during an install process the install migration file will be loaded and then only v1_3 migration file will be loaded. Migrations from v1_0 to v1_2 will not be loaded.

Example of install migration file:

<?php

namespace Acme\Bundle\TestBundle\Migrations\Schema;

use Doctrine\DBAL\Schema\Schema;
use Effiana\MigrationBundle\Migration\Installation;
use Effiana\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']);
    }
}

To run migrations, there is effiana:migration:load command. This command collects migration files from bundles, sorts them by version number and applies changes.

This command supports some additional options:

  • force - Causes the generated by migrations SQL statements to be physically executed against your database;
  • dry-run - Outputs list of migrations without apply them;
  • show-queries - Outputs list of database queries for each migration file;
  • bundles - A list of bundles to load data from. If option is not set, migrations will be taken from all bundles;
  • exclude - A list of bundle names which migrations should be skipped.

Also there is effiana:migration:dump command to help in creation installation files. This command outputs current database structure as a plain sql or as Doctrine\DBAL\Schema\Schema queries.

This command supports some additional options:

  • plain-sql - Out schema as plain sql queries
  • bundle - Bundle name for which migration wll be generated
  • migration-version - Migration version number. This option will set the value returned by getMigrationVersion method of generated installation file.

Good practice for bundle is to have installation file for current version and migration files for migrating from previous versions to current.

Next algorithm may be used for new versions of your bundle:

  • Create new migration
  • Apply it with effiana:migration:load
  • Generate fresh installation file with effiana:migration:dump
  • If required - add migration extensions calls to generated installation.

Extensions for database structure migrations

Sometime you cannot use standard Doctrine methods for database structure modification. For example Schema::renameTable does not work because it drops existing table and then creates a new table. To help you to manage such case and allow to add some useful functionality to any migration a extensions mechanism was designed. The following example shows how RenameExtension can be used:

<?php

namespace Acme\Bundle\TestBundle\Migrations\Schema\v1_0;

use Doctrine\DBAL\Schema\Schema;
use Effiana\MigrationBundle\Migration\Migration;
use Effiana\MigrationBundle\Migration\QueryBag;
use Effiana\MigrationBundle\Migration\Extension\RenameExtension;
use Effiana\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'
        );
    }
}

As you can see to use the RenameExtension your migration class should implement RenameExtensionAwareInterface and setRenameExtension method. Also there is some additional useful interfaces you can use in your migration class:

Create own extensions for database structure migrations

To create your own extension you need too do the following simple steps:

  • Create an extension class in YourBundle/Migration/Extension directory. Using YourBundle/Migration/Extension directory is not mandatory, but highly recommended. For example:
<?php

namespace Acme\Bundle\TestBundle\Migration\Extension;

use Doctrine\DBAL\Schema\Schema;
use Effiana\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);
    }
}
  • Create *AwareInterface in the same namespase. It is important that the interface name should be {ExtensionClass}AwareInterface and set method should be set{ExtensionClass}({ExtensionClass} ${extensionName}). For example:
<?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);
}
  • Register an extension in dependency container. For example
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: effiana_migration.extension, extension_name: test /*, priority: -10 - priority attribute is optional an can be helpful if you need to override existing extension */ }

If you need an access to the database platform or the name generator you extension class should implement DatabasePlatformAwareInterface or NameGeneratorAwareInterface appropriately. Also if you need to use other extension in your extension the extension class should just implement *AwareInterface of the extension you need.

Data fixtures

Symfony allows to load data using data fixtures. But these fixtures are run each time when doctrine:fixtures:load command is executed.

To avoid loading the same fixture several time, effiana:migration:data:load command was created. This command guarantees that each data fixture will be loaded only once.

This command supports two types of migration files: main data fixtures and demo data fixtures. During an installation, user can select to load or not demo data.

Data fixtures for this command should be put in Migrations/Data/ORM or in Migrations/Data/Demo/ORM directory.

Fixtures order can be changed with standard Doctrine ordering or dependency functionality. More information about fixture ordering can be found in doctrine data fixtures manual.

Versioned fixtures

There are fixtures which need to be executed time after time. An example is a fixture which uploads countries data. Usually, if you add new countries list, you need to create new data fixture which will upload this data. To avoid this you can use versioned data fixtures.

To make fixture versioned, this fixture must implement VersionedFixtureInterface and getVersion method which returns a version of fixture data.

Example:

<?php

namespace Acme\DemoBundle\Migrations\DataFixtures\ORM;

use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\Persistence\ObjectManager;

use Effiana\MigrationBundle\Fixture\VersionedFixtureInterface;

class LoadSomeDataFixture extends AbstractFixture implements VersionedFixtureInterface
{
    /**
     * {@inheritdoc}
     */
    public function getVersion()
    {
        return '1.0';
    }

    /**
     * {@inheritdoc}
     */
    public function load(ObjectManager $manager)
    {
        // Here we can use fixture data code which will be run time after time
    }
}

In this example, if the fixture was not loaded yet, it will be loaded and version 1.0 will be saved as current loaded version of this fixture.

To have possibility to load this fixture again, the fixture must return a version greater then 1.0, for example 1.0.1 or 1.1. A version number must be an PHP-standardized version number string. More info about PHP-standardized version number string can be found in PHP manual.

If a fixture need to know the last loaded version, it must implement LoadedFixtureVersionAwareInterface and setLoadedVersion method:

<?php

namespace Acme\DemoBundle\Migrations\DataFixtures\ORM;

use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\Persistence\ObjectManager;

use Effiana\MigrationBundle\Fixture\VersionedFixtureInterface;
use Effiana\MigrationBundle\Fixture\RequestVersionFixtureInterface;

class LoadSomeDataFixture extends AbstractFixture implements VersionedFixtureInterface, LoadedFixtureVersionAwareInterface
{
    /**
     * @var $currendDBVersion string
     */
    protected $currendDBVersion = null;

    /**
     * {@inheritdoc}
     */
    public function setLoadedVersion($version = null)
    {
        $this->currendDBVersion = $version;
    }

    /**
     * {@inheritdoc}
     */
    public function getVersion()
    {
        return '2.0';
    }

    /**
     * {@inheritdoc}
     */
    public function load(ObjectManager $manager)
    {
        // Here we can check last loaded version and load data data difference between last
        // uploaded version and current version
    }
}

effiana/migration-bundle 适用场景与选型建议

effiana/migration-bundle 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 324 次下载、GitHub Stars 达 0, 最近一次更新时间为 2019 年 01 月 03 日, 在 PHP 生态内属于活跃度较高的组件。

它主要适用于以下技术方向: 「fixtures」 「data」 「schema」 「migration」 「BAP」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。

我们在过去多个企业项目中使用过 effiana/migration-bundle 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 effiana/migration-bundle 我们能提供哪些服务?
定制开发 / 二次开发

基于 effiana/migration-bundle 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 324
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 0
  • 点击次数: 0
  • 依赖项目数: 3
  • 推荐数: 0

GitHub 信息

  • Stars: 0
  • Watchers: 0
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2019-01-03