Skip to content

Migrations

Schema changes are Phinx migrations. There is no xPDO schema map to rebuild and no vehicle to regenerate — a migration is a PHP class that describes the change.

Two layers, two configs

This is the single most common source of confusion, so it comes first:

What you are changingMigrations live inConfig (-c)Applied log
The componentcore/components/pageblocks/src/Database/migrationscore/components/pageblocks/src/phinx.phppb_migrations
Your sitecore/App/Database/migrationscore/App/phinx.phppb_app_migrations

paths in each config points at the directory next to that config. The two do not see each other's migrations. Run with the wrong -c and your migration simply does not appear in status — no error, nothing. It looks exactly like a file that failed to deploy.

bash
# component layer
php core/components/pageblocks/vendor/bin/phinx status \
  -c core/components/pageblocks/src/phinx.php

# site layer
php core/components/pageblocks/vendor/bin/phinx status -c core/App/phinx.php

Plan before you write

status shows what is pending, migrate applies it. Always read the plan first and say out loud what you expect — a migration is rolled back by restoring a dump.

bash
phinx status  -c <config>     # plan
phinx migrate -c <config>     # apply

Never run PHP as root on the server

The site's PHP runs as the site user. A migration started by root leaves root-owned cache files that the site then cannot overwrite — and the manager starts serving a frozen lexicon. Use sudo -u <siteuser>.

The component's migrations are run for you

When the package is installed or upgraded, its migrations resolver runs the component layer. You only run it by hand when files were delivered without reinstalling the package.

Writing one

php
<?php

declare(strict_types=1);

use Phinx\Migration\AbstractMigration;

final class CreatePbExampleTable extends AbstractMigration
{
    public function change(): void
    {
        $this->table('pb_example', ['id' => true, 'primary_key' => ['id']])
            ->addColumn('name', 'string', ['limit' => 100, 'null' => false, 'default' => ''])
            ->addColumn('data', 'json', ['null' => false, 'default' => '{}'])
            ->addColumn('menuindex', 'integer', ['signed' => false, 'null' => false, 'default' => 0])
            ->addColumn('published_at', 'datetime', ['null' => true, 'default' => null])
            ->addTimestamps()
            ->addColumn('deleted_at', 'datetime', ['null' => true, 'default' => null])
            ->addIndex(['name'], ['name' => 'idx_name'])
            ->create();
    }
}

The table prefix is applied by Phinx — write pb_example, not modx_pb_example.

A table meant to hold constructor data wants the same tail as the built-in ones: data, menuindex, published_at, timestamps and deleted_at. That is what makes publishing, ordering and the basket work without extra code.

Two traps we have already hit

An index on a column the migration does not create. addIndex(['type']) where the column was renamed to placement passes review and passes on existing installs — the table is already there, so Phinx never re-runs the migration. On a fresh database it dies mid-run and leaves the component without half its tables. Create-migrations are only ever exercised by new installations, so they need a clean install to be tested at all.

Editing a migration that has already run. It changes nothing on installs that applied it — Phinx goes by the log, not by the file. If a fix has to reach existing databases, it needs a new migration, with hasColumn() / hasIndexByName() guards so it is a no-op where the change is already present.

Deleting migrations

A migration whose file is gone but whose row is still in the log shows up in status as ** MISSING MIGRATION FILE **. Phinx tolerates that and keeps working.

So folding old add_* migrations into the create_* ones is possible — but only while every installation has already applied them. After the component ships to someone else, their database may be at any point in the sequence, and a deleted migration becomes a silently broken install.

© PageBlocks 2019-present