Migrations For The Conscientious

Christopher Pitt
Laravel 4 Tutorials
3 min readApr 29, 2014

--

I learned much from reading Taylor Otwell’s brief, yet insightful, Laravel: From Apprentice To Artisan. The parts dealing with domain-specific folders were echoes of a talk by Robert C. Martin, Architecture The Lost Years.

The idea is that your application’s folder structure shouldn’t be defined in terms of the delivery mechanism that is HTTP. It shouldn’t be defined in terms of the organisational model that is Model-View-Controller. It should be defined in terms of the application structure and behavior.

So I’ve recently been thinking a lot about this idea, and trying to structure code which fits with it. Naturally most of my Laravel application code is easily relocated and brought under the applicable namespaces. Migrations, however…

The problem with migrations is that their names are significant for their behavior, to the point where they won’t function if they aren’t named correctly. This is not uncommon in migration schemes owing to the need for chronological ordering. Migrations need to happen in the order they were created. They need to unhappen in the the opposite order.

Laravel makes this painfully clear when trying to namespace migration classes. Sure, you can change their filesystem location, and use the path argument (or the bench argument, or the package argument). These are designed to allow running migrations from anywhere. What they don’t cater for is when those migrations belong to a namespace (which is not the global namespace).

You can run composer dump-autoload and use as many path arguments as you like; if your migrations are in a namespace then Laravel will not migrate them.

It’s not all doom and gloom though. Because Laravel is built on a strong IoC foundation, it’s easy to subclass and substitute a migration handler class which deals with this natural (and annoying) weakness. Observe…

The first step is to subclass and substitute the Migrator class. You can find the base class at Illuminate\Database\Migrations\Migrator. It’s registered in Illuminate\Database\MigrationServiceProvider with the key migrator, so it’s easy to substitute in out custom code:

<?php

namespace Acme;

use Illuminate\Database\Migrations\Migrator as Base;

class Migrator
extends Base
{
/**
* Resolve a migration instance from a file.
*
* @param string $file
* @return object
*/
public function resolve($file)
{
$file = implode("_", array_slice(explode("_", $file), 4));

$class = "Acme\\Migration\\" . studly_case($file);

return new $class;
}
}

The resolve() method is almost exactly the same as in the base Migrator class, except that we’re now able to prefix the resolved class name with a namespace. Now we just have to swap this for the base Migrator:

<?php

namespace Acme;

use Illuminate\Support\ServiceProvider;

class AcmeServiceProvider
extends ServiceProvider
{
public function register()
{
$this->app->bindShared(
"migrator",
function () {
return new Migrator(
$this->app->make("migration.repository"),
$this->app->make("db"),
$this->app->make("files")
);
}
);
}
}

This is exactly the same bindShared() call that happened in the base service provider. I’ve just cleaned it up a little…

We will still have to use the path argument, as Laravel expects the migration classes to be in app/database/migrations. We will still need to have the timestamps in the migration file names, as Laravel needs them to work out the running order.

Instead of seeing class not found exceptions, it’s business as usual:

❯ php artisan migrate --path="app/Acme/Migration"

If you need more info on running package migrations (the traditional approach), you can find it at: http://laravel.com/docs/packages#package-migrations.

If you need more info on how to write migrations, you can find it at: http://laravel.com/docs/migrations.

--

--