Over the last 20 months or so, I’ve been working on a tool for writing testable migrations for MongoDB, called Mongrate. Here’s a post on how to write one. Below, we will write a migration that adds two fields, width and height to documents in a collection called Images .

We can start by generating a new migration from a template, using the command mongrate generate AddImageDimensions (if you are using the Symfony bundle, the command is app/console mongrate:generate AddImageDimensions ). This will generate 5 files for us – one PHP class, and four YML test files.

Generated class structure

Below is a copy of what the command above has generated in Migration.php :

namespace Mongrate\Migrations; use Mongrate\Migration\Migration as MigrationHelper; use Mongrate\Exception\MigrationNotImplementedException; use Doctrine\MongoDB\Database; class AddImageDimensions_20160131 { use MigrationHelper; public function up(Database $db) { throw new MigrationNotImplementedException('"up" method not implemented in "AddImageDimensions_20160131".'); } public function down(Database $db) { throw new MigrationNotImplementedException('"down" method not implemented in "AddImageDimensions_20160131".'); } }

The line which imports a trait, use MigrationHelper; , is there to let the Mongrate library add features without you having to extend a base class (so you can create a base class of your own without any constraints from the library), and so features can be added to existing migrations without you having to change any code.

Writing the ‘up’ method

Our migration’s goal is to add two fields, width and height to documents in a collection called Images , skipping any documents that already have those properties set.

Let’s start:

public function up(Database $db) { $imagesCollection = $db->selectCollection('Images'); $images = $imagesCollection->find( ['width' => ['$exists' => false], 'height' => ['$exists' => false]], // Only select the fields we will need, and ignore any other fields. This reduces memory // usage and makes the migration run faster. ['url' => true] )->snapshot(); foreach ($images as $image) { list($width, $height) = getimagesize($image['url']); $imagesCollection->update( ['_id' => $image['_id']], ['$set' => ['width' => $width, 'height' => $height]] ); $this->output->writeln(sprintf('Set dimensions for image %s: %dx%d', $image['url'], $width, $height)); } }

Now we have a migration that we think will do what we want. But code can always have bugs, so the best thing to do now is write a unit test.

Mongrate makes writing tests easy.

Writing a unit test to verify the ‘up’ migration

One of the files generated by the command earlier was called up-input.yml and another was called up-verifier.yml .

Let’s put some sample input data into up-input.yml . The structure of the file is quite simple: collection names are the top-level keys, which contains arrays of documents.

Images: - url: http://mongrate.com/assets/logo.png - # This image already has a 'width' and 'height', and it should not be changed by our migration. url: http://www.google.com/logo.png width: 100 height: 80

And we can write what we expect the database content to be afterwards in up-verifier.yml :

Images: - url: http://mongrate.com/assets/logo.png # These properties should have been set: width: 200 height: 200 - url: http://www.google.com/logo.png width: 100 height: 80

Now if we run the command to test the migration, mongrate test AddImageDimensions_20160131 up , we will see this output:

Testing AddImageDimensions_20160131 going up. Set dimensions for image http://mongrate.com/assets/logo.png: 200x200 Test passed.

Summary

Above, we have achieved the following:

Wrote a migration that adds new fields to existing documents.

Logged the changes to output, so the changes can be sent to a log file for later reviewing.

Guaranteed our migration works with a unit test.

Wrote less than 100 lines of code.

For more documentation, like installation instructions, see the official website.