tobento / service-migration
A mirgation manager for any PHP application.
Requires
- php: >=8.0
- psr/container: ^2.0
- tobento/service-autowire: ^1.0
- tobento/service-file-creator: ^1.0
- tobento/service-filesystem: ^1.0
Requires (Dev)
- phpunit/phpunit: ^9.5
- tobento/service-container: ^1.0
- vimeo/psalm: ^4.0
README
The Migration Service provides a flexible way for handling migrations for PHP applications.
Table of Contents
Getting started
Add the latest version of the Migration service project running this command.
composer require tobento/service-migration
Requirements
- PHP 8.0 or greater
Highlights
- Framework-agnostic, will work with any project
- Decoupled design
Simple Example
Create a migration
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.', ), ); } }
Install / Uninstall migration
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. } }
Documentation
Migration
Create Migration
Your migration class must implement the 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.', ), ); } }
Actions
Callable
The CallableAction::class
calls the specified callable on process.
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"
Dir Copy
Use the DirCopy::class action to copy a directory to another destination.
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"
Dir Delete
Use the DirDelete::class action to delete a directory.
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
The Fail::class
does always fail on action process, throwing a ActionFailedException::class
, which might be useful in some cases.
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.
Files Copy
Use the FilesCopy::class action to copy files to another directory.
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"
Files Delete
Use the FilesDelete::class action to delete files.
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"
File String Replacer
Use the FileStringReplacer::class to replace strings from a file.
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"
Null
The NullAction::class
does nothing at all, which might be useful in some cases.
use Tobento\Service\Migration\Action\NullAction; $action = new NullAction( name: 'A unique name', // or null description: 'Some description.', // (optional) type: 'keyword', // (optional) );
PDO Exec
Use the PdoExec::class to execute pdo statements.
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"
Custom Actions
Below is an example of writing a custom action.
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 []; } }
Process Actions
You might process an action without using the migrator.
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. }
Migrator
Create Migrator
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)
Install Migration
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. }
You might want to uninstall if the install has failed.
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. } }
Uninstall Migration
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. }
You might want to uninstall only if the migration has been installed before.
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. } }
Get Installed Migrations
$migrations = $migrator->getInstalled(); // get only from specific. $migrations = $migrator->getInstalled(namespace: 'App\Blog\');
Migration Result
The migrator install and uninstall method returns the migration result object on success:
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)
Get the migration processed
use Tobento\Service\Migration\MigrationInterface; // Get the migration processed: var_dump($result->migration() instanceof MigrationInterface); // bool(true)
Get the migration actions processed
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(); }
Migration Results
You might want to store your results as to get it later to show the migrations processed.
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();