If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeGoogle 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:
... 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.
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.
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
:
... lines 1 - 10 | |
class Genus | |
{ | |
... lines 13 - 34 | |
/** | |
* @ORM\Column(type="string", nullable=true) | |
*/ | |
private $funFact; | |
... lines 39 - 79 | |
} |
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.
// composer.json
{
"require": {
"php": ">=5.5.9",
"symfony/symfony": "3.1.*", // v3.1.4
"doctrine/orm": "^2.5", // v2.7.2
"doctrine/doctrine-bundle": "^1.6", // 1.6.4
"doctrine/doctrine-cache-bundle": "^1.2", // 1.3.0
"symfony/swiftmailer-bundle": "^2.3", // v2.3.11
"symfony/monolog-bundle": "^2.8", // 2.11.1
"symfony/polyfill-apcu": "^1.0", // v1.2.0
"sensio/distribution-bundle": "^5.0", // v5.0.22
"sensio/framework-extra-bundle": "^3.0.2", // v3.0.16
"incenteev/composer-parameter-handler": "^2.0", // v2.1.2
"composer/package-versions-deprecated": "^1.11", // 1.11.99
"knplabs/knp-markdown-bundle": "^1.4", // 1.4.2
"doctrine/doctrine-migrations-bundle": "^1.1" // 1.1.1
},
"require-dev": {
"sensio/generator-bundle": "^3.0", // v3.0.7
"symfony/phpunit-bridge": "^3.0", // v3.1.3
"nelmio/alice": "^2.1", // 2.1.4
"doctrine/doctrine-fixtures-bundle": "^2.3" // 2.3.0
}
}