Creating and Applying Migrations
Creating and Applying Migrations
~13 Min. Lesezeit Zuletzt aktualisiert am August 8, 2026
The Project entity from chapter 19 only exists as PHP code so far – the database still knows NOTHING of a project table. Migrations close this gap in a TRACEABLE and VERSIONED way.
Why migrations instead of a direct schema update?
Doctrine offers TWO ways to bring the database schema in line with the entities – both produce the same result, but with one decisive difference:
| Approach | Properties |
|---|---|
| doctrine:schema:update --force | Changes the database IMMEDIATELY and DIRECTLY, with NO record – fast for local experimentation, but NOT traceable and NOT reproducible on other environments. |
| Migrations (make:migration) | Generates a PHP file with explicit SQL for UP (apply) and DOWN (revert) – versioned, traceable in the Git repository, runs IDENTICALLY on every environment. |
Achtung: doctrine:schema:update --force is meant ONLY for local development/prototyping. For ANY application running in production (i.e. practically every real application), migrations are the only sensible way – they let you reproduce the schema on the production server EXACTLY as it evolved locally.
Creating a migration
php bin/console make:migrationDoctrine compares the CURRENT entity definitions with the CURRENT database schema and automatically generates the SQL needed for the difference:
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20260806120000 extends AbstractMigration
{
public function up(Schema $schema): void
{
$this->addSql('CREATE TABLE project (
id SERIAL NOT NULL,
name VARCHAR(255) NOT NULL,
description TEXT DEFAULT NULL,
created_at TIMESTAMP(0) NOT NULL,
PRIMARY KEY(id)
)');
}
public function down(Schema $schema): void
{
$this->addSql('DROP TABLE project');
}
}up() APPLIES the change, down() REVERTS it – both should ALWAYS match each other, so a migration can be rolled back cleanly in an emergency.
Applying the migration
php bin/console doctrine:migrations:migrateAsks for confirmation (skipped with -n, useful for automated deployments in chapter 48). Doctrine runs ALL not-yet-applied migrations in chronological order and records the state in an internal doctrine_migration_versions table.
Checking the current migration status
php bin/console doctrine:migrations:statusReverting a migration
php bin/console doctrine:migrations:migrate prevRuns down() of the MOST RECENTLY applied migration – useful when a new migration turns out to have an issue, BEFORE it goes to production.
The standard workflow from now on
- Create or change an entity (as in chapter 19).
php bin/console make:migration– Doctrine generates the SQL automatically.- READ THROUGH the generated migration file – Doctrine is usually reliable, but especially with more complex changes (e.g. renames), a manual check pays off.
php bin/console doctrine:migrations:migrate– apply it.- Commit the migration AND the entity change TOGETHER – they belong together inseparably.
Tipp: This five-step workflow accompanies us through the ENTIRE rest of block 4 – every new entity or relationship follows exactly this pattern.