I read a nice post on porting c# code to php a while back via hacker news and found it quite interesting. This week I've found myself in a similar situation, only reversed, I'm porting from a language I'm not familiar with, and I'm porting code that I don't necessarily understand! I've written a couple of rails apps, but they were very basic, and I have no real clue with the delicacies of the ruby language.

We've recently switched to git and in order to help facilitate the flexibility that git brings, I thought I'd replace the database migrations system we currently use, predominantly to allow for 'interleaved' migrations, but the flexibility for running code as well as sql would be beneficial.

We evaluated some of the existing solutions out there and unsuprisingly we settled on rails' migrations feature set as being the best match for us. Now, I read about standalone migrations, but didn't really want to force our project to be dependant on another language, so decided to port ActiveRecord::Migration to php.

Update 02/11/2011: I've since moved on from this method of running migrations, checkout phpmig on github. It's similar to the tool I describe here, but not tied to a particular project, library or framework.

First I had to fathom out the file structure, everything seemed to be jammed into one file, so I converted that to a file structure that more closely resembled that of our application library. I also decided at this point that I wasn't going to bother with the automated reversal feature, that seems a little overkill for us. I also aren't really interested in abstracting the DDL at this point, SQL is all we need.

Spencer/
|-- Db
|   |-- Exception.php
|   |-- Migration
|   |   |-- Exception
|   |   |   |-- DuplicateName.php
|   |   |   |-- DuplicateVersion.php
|   |   |   |-- IllegalName.php
|   |   |   |-- Irreversible.php
|   |   |   `-- UnknownVersion.php
|   |   |-- Exception.php
|   |   `-- Migrator.php
|   |-- Migration.php
|   `-- MigrationProxy.php

Next I wrote the exception classes, converting something like this:

class DuplicateMigrationVersionError < ActiveRecordError#:nodoc:
  def initialize(version)
    super("Multiple migrations have the version number #{version}")
  end
end

to something like this:

class Spencer_Db_Migration_Exception_DuplicateVersion extends Spencer_Db_Migration_Exception
{
    public function __construct($version)
    {
        parent::__construct("Multiple migrations have the version number $version");
    }
}

That was simple enough, so then I moved on to the Migration class. This is where I hit my first roadblock and had to go learn some things about ruby meta-programming, specifically the singleton class. Now, I'll admit I still don't fully understand it, but worked out enough to see that the main use of the singleton class was to declare class methods. However, I was quickly stumped again by this piece of code:

class < < self
  attr_accessor :delegate # :nodoc:
end

def self.method_missing(name, *args, &block) # :nodoc:
  (delegate || superclass.delegate).send(name, *args, &block)
end

>>>> snip <<<<

# instantiate the delegate object after initialize is defined
self.verbose = true
self.delegate = new

def up
  self.class.delegate = self
  return unless self.class.respond_to?(:up)
  self.class.up
end

def down
  self.class.delegate = self
  return unless self.class.respond_to?(:down)
  self.class.down
end

Here's my very uneducated take on it.

  1. We declare delegate as a class member.
  2. We then override ruby's built in method_missing method, which will handle any messages sent to this class that it doesn't understand.
  3. Set the verbose class member to true and the delegate member to a new instance of ActiveRecord::Migration, although I'm not too sure on the latter
  4. Define two method instance methods, that set the delegate class member to the current instance, then call the same method on the class, which will delegate it to the instance we set as the delegate? - This just doesn't make sense to me, but maybe it's not supposed to? These are the two methods that the migrations themselves override

So anyway, I simply skipped that bit. I figured it would dawn on me what it was all for when I came to need it!

The next point of interest was the little Benchmark::measure method, which uses ruby's yield statement to measure the time taken to run a method. The method itself is passed as an argument to the method:

time = Benchmark.measure { send(direction) }

The Benchmark.measure method then records some times, runs the passed method by calling the yield statement, then records the finish times and returns them.

# File benchmark.rb, line 291
def measure(label = "") # :yield:
  t0, r0 = Benchmark.times, Time.now
  yield
  t1, r1 = Benchmark.times, Time.now
  Benchmark::Tms.new(t1.utime  - t0.utime, 
                     t1.stime  - t0.stime, 
                     t1.cutime - t0.cutime, 
                     t1.cstime - t0.cstime, 
                     r1.to_f - r0.to_f,
                     label)
end

Now I couldn't replicate this exactly using PHP, but the new closures available in PHP 5.3 made it relatively neat. I don't know of any way to measure cpu/user/real time in PHP, so I ended up simply with:

public static function measure(Closure $function)
{
    $time = microtime(1);
    $function();
    return new Spencer_Benchmark_Tms(microtime(1) - time());
}

The code to call it needs a little more explaining, the use keyword specifies variables from the parent scope that the closure can access.

$th = $this;
$time = Spencer_Benchmark::measure(function() use ($th, $direction) { $th->$direction(); });

Mimicking the Activerecord::MigrationProxy class was easy enough, then I moved on to the Activerecord::Migrator class. Now, the first problem I had was the use of method names in this class. Because of the aforementioned singleton class, ruby classes can effectively have both a class method and an instance method with the same name, which PHP can't. To keep track, I prefixed the instance methods with 'really' (not the best choice, but once I had started I thought it best to stick with it). Although it worked, it proved very difficult to keep track of as I was porting the code over. The next thing I noticed about ruby was how nice it was to be working with objects all the time. Using methods like Enumerable.map and Enumerable.detect just seems a little nicer than my own versions mangled up in PHP. Take the following code, that fetches a set of records from the database, uses map to convert the array of objects returned into an array of integers and then sorts it.

def get_all_versions
  table = Arel::Table.new(schema_migrations_table_name)
  Base.connection.select_values(table.project(table['version']).to_sql).map{ |v| v.to_i }.sort
end

My PHP equivalent gets the job done, but simply doesn't look as elegant, despite me cheating and using the SQL to sort the data. The SPL Iterators can run sorts with a user defined function, but I can't see map and detect etc.

public static function getAllVersions()
{
    try {
        $table = self::getDbTable();
        $rows = $table->fetchAll($table->select()->from($table, array('version'))->order('version ASC'))->toArray();
        return array_map(function($r) { return $r['version'];}, $rows);
    } catch (Exception $e) {
        return array();
    }
}

This seemed like a recurring theme as I ported the rest of Migrator, with the use of closures/blocks at every corner. Other than that, I didn't really have any problems. I skipped the DDL transactions, as, as far as I know, MySQL doesn't support them.

I'd then finished the library, so I took a look inside the rails rake tasks that are used to run the migrations. Thankfully, they were nice and simple and mostly called the nice class methods I'd already ported over to PHP. I've previously used phing for tasks like this, but I thought I'd give Zend_Tool a go. First, I'm not so keen on the way Zend_Tool integrates into a project. It needs a big fat XML file to work nicely, but that xml file can't be generated from existing code, so doesn't play well with a team of developers. Secondly, the zf command line program doesn't really tie in to your codebase at all. Ideally for me, the migration classes shouldn't need to worry about bootstrapping the application, so I copied the zf.php file to our codebase and customised it to bootstrap our application and also set the include path up for the custom provider I was about to write. An hour later and I had a nice Zend_Tool provider, that nicely generate, ran and provided status on migrations.

davem@CSL2717:[develop]sos$ ./zf ? migration
Zend Framework Command Line Console Tool v1.10.7
Actions supported by provider "Migration"
  Migration
    zf status migration
    zf migrate migration version
    zf redo migration verison
    zf up migration version
    zf down migration version
    zf forward migration steps[=1]
    zf rollback migration steps[=1]
    zf generate migration name


davem@CSL2717:[develop]sos$ ./zf generate migration FirstMigration
Created migration at 20101129205655_first_migration.php

davem@CSL2717:[develop]sos$ vim db/migrations/20101129205655_first_migration.php 
davem@CSL2717:[develop]sos$ ./zf status migration

 Status   Migration ID    Migration Name
--------------------------------------------------
   down  20101129205655  FirstMigration


davem@CSL2717:[develop]sos$ ./zf migrate migration
== 20101129205655 FirstMigration: migrating ===================================
== 20101129205655 FirstMigration: migrated 0.3273s ============================

davem@CSL2717:[develop]sos$ ./zf status migration

 Status   Migration ID    Migration Name
--------------------------------------------------
     up  20101129205655  FirstMigration


davem@CSL2717:[develop]sos$ 

The code isn't really in any state to be published, needs a lot of polishing, but once that's done, I'll have a word with my bosses and maybe sling it up on github.

In conclusion, I can see while all the ruby fanboys become fanboys, but while it doesn't always look as pretty, php still gets things done for me.