Gestion des migrations de votre BDD avec doctrine/migrations

Alexandre Balmes
5 min readAug 29, 2015

Tous les développeurs issus de l’écosystème Symfony connaissent depuis très longtemps l’outil de migration de Doctrine. Pour les autres, cette librairie apour objectif de vous faciliter la vie dans la création de vos tâches de maintenance sur la base de données d’un projet.

L’une de mes prestations de cette année avait pour but la migration progressive d’une application réalisée avec CodeIgniter. Ce framework, très populaire aux États-Unis pendant un long moment et est en fait plus digne du framework procédural que de l’objet (parce qu’initialement conçu pour PHP 4). J’en déconseille par contre l’utilisation pour tout nouveau projet. Si vous souhaitez rester les terres états-uniennes, préférez lui Laravel.

Il n’y a donc pas de composer, pas de répertoire src, un ActiveRecord du coté de la base de données et tout un tas de code que l’on n’a plus vraiment envie d’utiliser. Mais voilà, l’application est à migrer progressivement et tant qu’à faire, autant utiliser les outils d’aujourd’hui.

Commençons par ajouter doctrine/migrations au fichier composer.json :

"require": {
"doctrine/migrations": "1.0.*@dev"
},
"config": {
"bin-dir": "bin"
}

Et maintenant, l’habituel :

$ composer update

Les dépendances sont installées dans le répertoire vendor et un fichier doctrine-dbal est créé dans le répertoire bin.

Si vous exécutez la commande doctrine-dbal, vous obtiendrez l’erreur suivante :

$ doctrine-dbal migrations:statusYou are missing a “cli-config.php” or “config/cli-config.php” file in your
project, which is required to get the Doctrine-DBAL Console working. You can use the
following sample as a template:
<?php
use Doctrine\DBAL\Tools\Console\ConsoleRunner;
// replace with the mechanism to retrieve DBAL connection in your app
$connection = getDBALConnection();
// You can append new commands to $commands array, if neededreturn ConsoleRunner::createHelperSet($connection);

Donc autant le dire, le template conseillé n’est pas franchement à la page. Voici le bon fichier cli-config.php :

<?php

use
Doctrine\DBAL\DriverManager;
use Doctrine\DBAL\Migrations\Tools\Console\Command;
use Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Helper;

$params = [
'dbname' => 'my_database',
'user' => 'root',
'password' => '',
'host' => 'localhost',
'driver' => 'pdo_mysql'
];

$db = DriverManager::getConnection($params);

$helperSet = new Helper\HelperSet(array(
'db' => new ConnectionHelper($db),
'dialog' => new Helper\DialogHelper(),
));

$cli = new Application('Doctrine Command Line Interface', Doctrine\DBAL\Version::VERSION);
$cli->setCatchExceptions(true);
$cli->setHelperSet($helperSet);

$cli->addCommands(array(
// Migrations Commands
new Command\ExecuteCommand(),
new Command\GenerateCommand(),
new Command\MigrateCommand(),
new Command\StatusCommand(),
new Command\VersionCommand()
));

$cli->run();

Cela vous donnera le résultat suivant :

$ doctrine-dbal 
Doctrine Command Line Interface version 2.5.1
Usage:
[options] command [arguments]
Options:
—help (-h) Display this help message
—quiet (-q) Do not output any message
—verbose (-v|vv|vvv) Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
—version (-V) Display this application version
—ansi Force ANSI output
—no-ansi Disable ANSI output
—no-interaction (-n) Do not ask any interactive question
Available commands:
help Displays help for a command
list Lists commands
migrations
migrations:execute Execute a single migration version up or down manually.
migrations:generate Generate a blank migration class.
migrations:migrate Execute a migration to a specified version or the latest available version.
migrations:status View the status of a set of migrations.
migrations:version Manually add and delete migration versions from the version table.

Si vous exécutez maintenant la commande de check, vous obtiendrez à nouveau une erreur :

$ doctrine-dbal migrations:status[Doctrine\DBAL\Migrations\MigrationException] 
Migrations namespace must be configured in order to use Doctrine migrations.
migrations:status [—show-versions] [—configuration[=”…”]] [—db-configuration[=”…”]]

Il faut donc créer une fichier de configuration migrations.yml.

name: My Application Migrations
migrations_namespace: Infrastructure\DoctrineMigrations
table_name: doctrine_migration_versions
migrations_directory: src/Infrastructure/DoctrineMigrations

Et bien entendu le répertoire correspondant dans src/Infrastructure puis à nouveau :

$ doctrine-dbal migrations:status== Configuration>> Name: My Application Migrations
>> Database Driver: pdo_mysql
>> Database Name: my_database
>> Configuration Source: /var/www/project/migrations.yml
>> Version Table Name: doctrine_migration_versions
>> Migrations Namespace: Infrastructure\DoctrineMigrations
>> Migrations Directory: /var/www/projet/src/Infrastructure/DoctrineMigrations
>> Previous Version: Already at first version
>> Current Version: 0
>> Next Version: 2015–03–05 22:05:47 (20150305220547)
>> Latest Version: 2015–03–05 22:05:47 (20150305220547)
>> Executed Migrations: 0
>> Executed Unavailable Migrations: 0
>> Available Migrations: 0
>> New Migrations: 0

Vous pouvez maintenant créer votre première migration avec la commande suivante :

$ doctrine-dbal migrations:generate
Generated new migration class to “/var/www/project/src/Infrastructure/DoctrineMigrations/Version20150305220547.php”

Le contenu du fichier généré étant le suivant :

<?php

namespace
Infrastructure\DoctrineMigrations;

use Doctrine\DBAL\Migrations\AbstractMigration;
use Doctrine\DBAL\Schema\Schema;

/**
* Auto-generated Migration: Please modify to your needs!
*/
class Version20150305220547 extends AbstractMigration
{
/**
*
@param Schema $schema
*/
public function up(Schema $schema)
{
// this up() migration is auto-generated, please modify it to your needs

}

/**
*
@param Schema $schema
*/
public function down(Schema $schema)
{
// this down() migration is auto-generated, please modify it to your needs

}
}

Quelques explications concernant le fichier :

  • Le nom de classe est un timestamp qui sera stocké en base dans une table doctrine_migrations. C’est ce timestamp qui permettra à doctrine de connaitre le nombre de migrations à jouer, la migration précédente, la suivante etc,
  • La méthode up vous permet, lors de l’exécution de la commande doctrine-dbal migrations:migrate d’exécuter vos requêtes de migrations vers un nouvel état,
  • La méthode down vous permet l’inverse et donc de revenir à l’état précédent.

Un exemple volontairement inutile :

<?php

namespace
Infrastructure\DoctrineMigrations;

use Doctrine\DBAL\Migrations\AbstractMigration;
use Doctrine\DBAL\Schema\Schema;

/**
* Native SQL request
*/
class Version20150305220547 extends AbstractMigration
{
/**
*
@param Schema $schema
*/
public function up(Schema $schema)
{
$this->addSql('UPDATE faq SET state = 0 WHERE id = 10');
}

/**
*
@param Schema $schema
*/
public function down(Schema $schema)
{
$this->addSql('UPDATE faq SET state = 1 WHERE id = 10');
}
}

Mais vous pouvez aller plus loin :

<?php

namespace
Infrastructure\DoctrineMigrations;

use Doctrine\DBAL\Migrations\AbstractMigration;
use Doctrine\DBAL\Schema\Schema;

/**
* Have fun with $schema !
*/
class Version20150306220547 extends AbstractMigration
{
/**
*
@param Schema $schema
*/
public function up(Schema $schema)
{
$table = $schema->createTable('test');
$table->addColumn('id', 'integer', ['autoincrement' => true]);
$table->addColumn('name', 'string');
$table->addColumn('status', 'boolean');
$table->addColumn('created_at', 'datetime');
$table->setPrimaryKey(['id']);
$table->addUniqueIndex(['id', 'serial']);
}

/**
*
@param Schema $schema
*/
public function down(Schema $schema)
{
$schema->dropTable('test');
}
}

Conclusion

And voilà. Vous pouvez maintenant jouer vos migrations de façon simple et sécurisé, avec un versioning et la possibilité de rollback en cas de problèmes sans avoir (ou moins) la trouille lors du déploiement.

N’hésitez donc pas à vous en servir, que vous soyez sur CodeIgniter, Wordpress, Prestashop ou n’importe quoi d’autre !

Je m’appelle Alexandre, je suis consultant web indépendant et développeur PHP.
Ma spécialité, traduire votre métier en ligne de code.

--

--

Alexandre Balmes

Consultant indépendant. J’aime le web et il me le rend bien.