Using Doctrine Migrations without the ORM

21 Aug 2014
Posted by jcfiala

Recently I was handed a PHP project at work which already existed but the owner wanted a variety of tweaks and improvements added to it. The project wasn't built with any framework I could name, and although it has a sort of MVC feel to it, is not strictly an MVC project.

One of the first challenges I ran into is that one of my fixes involves changing the structure of a table on the project, and another one involves creating a new table, both changes I wanted to set up in code so they would be moved along by source control. When I'm working in Drupal this is easily handled via hook_update_N, but I figured in this case the thing to do was to figure out something I could add using composer. Some quick looking around on http://www.phptherightway.com/ and Packagist caused me to settle on the 1.x branch of Doctrine Migrations. (The 2.x branch was marked as alpha as I write this, so I didn't want to use it.)

1) Installing with Composer

I'm not going to get into details about Composer, but if you're not familiar with it I suggest taking the time to do it. Here's the composer.json I ended up with when everything was working:

{
    "require": {
        "doctrine/migrations": "1.0.*@dev",
        "symfony/console": "2.6.*@dev",
        "symfony/yaml": "2.4.*@dev"
    },
    "autoload": {
        "psr-4": {"ClientName\\": "src/"}
    }

}

The doctrine/migrations pulls in the migration code (and it's dependencies), the symfony/console adds support to command line control of the migrations which for me was similar to Drupal's drush utility, and the symfony/yaml is needed for yaml config files for the migration. It's not strictly a dependency of doctrine/migrations because you could use other config files (such as xml), but if you're going to use yaml you need this.

2) Create Console.php for handling console commands.

The name of the file doesn't really matter in this case, it just needs to be an executible php file. I started out with this tutorial/introduction, which showed me how the basics fit together. Once this was working I went back and added more commands via $application->add(new ImportCommand);, looking through the migration code directories for the directories Tools/Console/Command and including them. Once this was done, I could enter
%>./console.php list

To get a list of the available doctrine commands.

3) Create migrations-db.php

I went down some false starts here, but I ended up creating a migrations-db.php file which returns the connection information for my database:

<?php
require_once 'dbconfig.php';
return array(
   
'dbname' => DATABASE_NAME,
   
'user' => DATABASE_USER,
   
'password' => DATABASE_PASS,
   
'host' => DATABASE_HOST,
   
'driver' => 'mysqli',
);
?>

As it happens, the already existing dbconfig.php file was populating these constants depending on the environment, so this ended up being pretty short and sweet. This file goes in the same directory as the console.php.

4) Create migrations.yml

This is one of the tasks that tripped me up due to using the 1.0 version of migrations, instead of the (currently alpha) 2.0. The new docs for 2.x say that this should be configuration.yml in one place, and migrations.yml in another. I'm not sure if that's a bug or not... in any case, naming the file to migrations.yml worked fine, and I just went with the defaults:

name: Client Name Migrations
migrations_namespace: DoctrineMigrations
table_name: doctrine_migration_versions
migrations_directory: /src/Migrations/DoctrineMigrations

The src directory is one I created for the new object oriented code that I'm adding to the project. That basically says "this is where we're putting the migration classes."

5) Create a Migration

Once you've got this all working, it's pretty simple from here on out. Create a migration on the command line:

%>./console.php migrations:generate

This will create a neat little file for you, already named and correctly built, with empty up and down functions. Now you can go in and add code in the up function to make the changes you want, and in the down function to undo the changes - so if you're creating a new table in the up function, you would drop it in the down.

The code you write uses the Schema object that is a parameter for the function and Doctrine's database abstraction & access layer (DBAL) to add and remove database objects. You can just use

<?php
$this
->addSql('query');
?>
to make database calls, but the DBAL works just fine.

6) Run the Migration

With that all done, you can now call
%>./console.php migrations:status
to see the waiting migration to run, and when you're ready you can call
%>./console.php migrations:execute [date] to run the migration in question - there's a good amount of info on running and backing out changes if you simply call
%>./console help migrations:execute

And that's that. Getting the code in place and configured properly took some skull-sweat, but once you've got the code to the point where you can generate a new migration file the rest of it is pretty simple to work out.

(I ended up writing another article about migrations.)

Tags: