Buy Access to Course
05.

Database Migrations

Share this awesome video!

|

Keep on Learning!

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Google for DoctrineMigrationsBundle. To install it, copy the composer require line. But again, we don't need to have the version - Composer will find the best version for us:

Tip

If you are on Symfony 3.2 or higher, you don't have to specify the bundle's version

composer require doctrine/doctrine-migrations-bundle:1.1

While Jordi is preparing that for us, let's keep busy. Copy the new statement from the docs and paste that into the AppKernel class:

56 lines | app/AppKernel.php
// ... lines 1 - 5
class AppKernel extends Kernel
{
public function registerBundles()
{
$bundles = array(
// ... lines 11 - 20
new Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle(),
// ... lines 22 - 23
);
// ... lines 25 - 33
}
// ... lines 35 - 54
}

Beautiful!

We already know that the main job of a bundle is to give us new services. But this bundle primarily gives us something different: a new set of console commands. Run bin/console with no arguments:

./bin/console

Hiding in the middle is a whole group starting with doctrine:migrations. These are our new best friend.

The Migrations Workflow

Our goal is to find a way to safely update our database schema both locally and on production.

To do this right, drop the database entirely to remove all the tables: like we have a new project.

./bin/console doctrine:database:drop --force

This is the only time you'll need to do this. Now, re-create the database:

./bin/console doctrine:database:create

Now, instead of running doctrine:schema:update, run:

./bin/console doctrine:migrations:diff

This created a new file in app/DoctrineMigrations. Go open that up:

// ... lines 1 - 10
class Version20160207083131 extends AbstractMigration
{
public function up(Schema $schema)
{
// this up() migration is autogenerated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() != "mysql");
$this->addSql("CREATE TABLE genus (id INT AUTO_INCREMENT NOT NULL, name VARCHAR(255) NOT NULL, sub_family VARCHAR(255) NOT NULL, species_count INT NOT NULL, fun_fact VARCHAR(255) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB");
}
public function down(Schema $schema)
{
// this down() migration is autogenerated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() != "mysql");
$this->addSql("DROP TABLE genus");
}
}

Check this out: the up() method executes the exact SQL that we would have gotten from the doctrine:schema:update command. But instead of running it, it saves it into this file. This is our chance to look at it and make sure it's perfect.

When you're ready, run the migration with:

./bin/console doctrine:migrations:migrate

Done! Obviously, when you deploy, you'll also run this command. But here's the really cool part: this command will only run the migration files that have not been executed before. Behind the scenes, this bundle creates a migrations_versions table that keep strack of which migration files it has already executed. This means you can safely run doctrine:migrations:migrate on every deploy: the bundle will take care of only running the new files.

Tip

You can run migration in reverse in case something fails. Personally, I never do this and I never worry about down() being correct. If you have a migration failure, it's a bad thing and it's better to diagnose and fix it manually.

Making Columns nullable

In newAction(), I'll add some code that sets fake data on the subFamily and speciesCount properties. But, I'll keep funFact blank: maybe some genuses just aren't very fun:

// ... lines 1 - 11
class GenusController extends Controller
{
// ... lines 14 - 16
public function newAction()
{
$genus = new Genus();
$genus->setName('Octopus'.rand(1, 100));
$genus->setSubFamily('Octopodinae');
$genus->setSpeciesCount(rand(100, 99999));
// ... lines 23 - 28
}
// ... lines 30 - 74
}

Ok, head over to /genus/new to try it out! Woh, a huge explosion!

Integrity constraint violation: 1048 Column fun_fact cannot be null

Here's the deal: Doctrine configures all columns to be required in the database by default. If you do want a column to be "nullable", find the column and add nullable=true:

81 lines | src/AppBundle/Entity/Genus.php
// ... lines 1 - 10
class Genus
{
// ... lines 13 - 34
/**
* @ORM\Column(type="string", nullable=true)
*/
private $funFact;
// ... lines 39 - 79
}

Creating Another Migration

Of course, just because we made this change doesn't mean that our table was automatically updated behind the scenes. Nope: we need another migration. No problem! Go back to the terminal and run:

./bin/console doctrine:migrations:diff

Open up the new migration file: ALTER TABLE genus CHANGE fun_fact to have a default of null. This look perfect. Run it with:

./bin/console doctrine:migrations:migrate

So easy! Refresh the page again: no errors. Migrations are awesome.