Multi-namespace migrations with Doctrine

 

Multi-installation applications (such as example Wordpress, Magento, Drupal or similar) have in common a mechanism to define optional Modules/Plugins/Extensions.

Modules can defines migrations, but when modules have dependencies to other modules, then the execution order depends on those dependencies and not only on the chronological order.

This package solves this issue by providing a pre-configured Symfony 5 application with doctrine/migrations 3.0 enabled. The application is able to automatically discover and run migrations provided by bundles.

The example application has the following structure:

               +---------------+                                  
               |               |                                  
        +----- |      App      |-----+                            
        |      |               |     |                            
        |      +---------------+     |                            
        |                            |                            
        |                            |                            
        |                            |                            
        |                            |                            
        |                            |                            
+---------------+            +---------------+                    
|               |            |               |                    
|   Customer    |            |     Order     |                    
|               |            |               |                    
+---------------+            +---------------+                    
        |                                                         
        |                                                         
        |                                                         
        |                                                         
+-------+-------+                                                 
|               |                                                 
|   Invoices    |                                                 
|               |                                                 
+---------------+                                                 
  • there is the main application App
  • there is a Order module (bundle) that depends on App
  • there is a Customer module (bundle) that depends on App
  • there is a Invoice module (bundle) that depends on Customer

Dependencies are inferred from the composer.json file that each bundle has.

All the modules provide some doctrine migrations but each module owns only its migrations. When migrations are executed the dependencies are resolved and migrations are executed accordingly.

Each bundle auto-registers the migrations to run, without the need to hardcode any directory location or dependency.

This solution for simplicity uses sqlite as database, but obviously can be applied to any other RDBMS supported by doctrine.

Preview

Here some previews of the content for this solution.

File structure:

.
├── bin
│   └── console
├── public
│   └── index.php
├── config
│   ├── bootstrap.php
│   ├── bundles.php
│   ├── packages
│   │   ├── cache.yaml
│   │   ├── doctrine_migrations.yaml
│   │   ├── doctrine.yaml
│   │   ├── framework.yaml
│   │   ├── prod
│   │   │   ├── doctrine.yaml
│   │   │   └── routing.yaml
│   │   ├── routing.yaml
│   │   └── test
│   │       └── framework.yaml
│   ├── routes
│   │   ├── annotations.yaml
│   │   └── dev
│   │       └── framework.yaml
│   ├── routes.yaml
│   └── services.yaml
├── migrations # App migrations
│   └── Version20200519061824.php
├── src
│   ├── Controller
│   ├── DependencyInjection
│   │   └── **.php
│   ├── DoctrineMigrations
│   │   └── **.php
│   └── Kernel.php
├── var
│   ├── cache/
│   └── log/
├── plugins # Plugins folder (can be any composer package)
│   ├── customer
│   │   ├── composer.json
│   │   ├── migrations
│   │   │   └── Version20200519061904.php
│   │   └── src
│   │       └── GoetasShopCustomerBundle.php
│   ├── customer-invoices
│   │   ├── composer.json
│   │   ├── migrations
│   │   │   └── Version20200519061858.php
│   │   └── src
│   │       └── GoetasShopCustomerInvoicesBundle.php
│   └── order
│       ├── composer.json
│       ├── migrations
│       │   └── Version20200519061909.php
│       └── src
│           └── GoetasShopOrderBundle.php
├── composer.json
├── composer.lock
├── symfony.lock
├── vendor
└── README.md

Migration output:

$ bin/console doctrine:migrations:migrate  -n -vv

[notice] Migrating up to GoetasShop\DoctrineMigrationsDemo\Order\DoctrineMigrations\Version20200519061909
[info]     ++ migrating App\DoctrineMigrations\Version20200519061824
[debug]       SELECT 'Migrate UP App' 
[info]   Migration App\DoctrineMigrations\Version20200519061824 migrated (took 22.6ms, used 12M memory)
[info]     ++ migrating GoetasShop\DoctrineMigrationsDemo\Customer\DoctrineMigrations\Version20200519061904
[debug]       SELECT 'Migrate UP Customer' 
[info]   Migration GoetasShop\DoctrineMigrationsDemo\Customer\DoctrineMigrations\Version20200519061904 migrated (took 0.2ms, used 12M memory)
[info]     ++ migrating GoetasShop\DoctrineMigrationsDemo\CustomerInvoices\DoctrineMigrations\Version20200519061858
[debug]       SELECT 'Migrate UP CustomerInvoices' 
[info]   Migration GoetasShop\DoctrineMigrationsDemo\CustomerInvoices\DoctrineMigrations\Version20200519061858 migrated (took 0.3ms, used 12M memory)
[info]     ++ migrating GoetasShop\DoctrineMigrationsDemo\Order\DoctrineMigrations\Version20200519061909
[debug]       SELECT 'Migrate UP Order' 
[info]   Migration GoetasShop\DoctrineMigrationsDemo\Order\DoctrineMigrations\Version20200519061909 migrated (took 1.5ms, used 12M memory)
[notice] finished in 66.9ms, used 12M memory, 4 migrations executed, 4 sql queries

php, database, doctrine, symfony

Want more info?