Skip to content

Migrations ​

The schema of your own tables is changed by Phinx migrations: a migration is a PHP class describing a change. No xPDO maps to rebuild.

You do not have to write them by hand — the Migrations tab in the constructor does the same thing and writes both the file and the model for you. That tab is PRO; writing the files yourself is not gated at all. This page is for when the change is easier to express in code — or when you are on the free edition.

Where they live ​

Filescore/App/Database/migrations
Configcore/App/phinx.php
Log of what ranpb_app_migrations

This is the site layer — yours. A component update does not touch it.

Which cuts both ways

Because the resolver never overwrites anything under core/App/, a fix shipped for one of those files does not arrive on its own — core/App/phinx.php included. When the changelog says a file there was corrected, apply the edit by hand on an existing install.

The component's own migrations are a separate set with a separate log (pb_migrations), run by the package installer rather than by you. When a set has files the log does not account for, every manager page shows a banner — it covers PageBlocks, pbShop, the repository and your own core/App migrations. The install no longer reports success when its migrations fail, but the banner is the thing to trust.

Plan before you write ​

status shows what is pending, migrate applies it. Read the plan first and say the expected result out loud: a migration is undone by restoring a dump.

bash
# the plan
php core/components/pageblocks/vendor/bin/phinx status -c core/App/phinx.php

# the write
php core/components/pageblocks/vendor/bin/phinx migrate -c core/App/phinx.php

Never run PHP as root on the server

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

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();
    }
}

Phinx adds the table prefix — write pb_example, not modx_pb_example.

Two rules about version numbers ​

The file name carries the version — 20261011120000_CreatePbExampleTable.php — and phinx identifies a migration by that version, not by its name or its contents. Hence two rules, and breaking either fails silently:

  • Always take the next free number. Reuse one that is already in some install's log and phinx skips your file while still reporting itself up to date. A recycled version is the one thing down() cannot repair.
  • Never edit a migration that has already run. The log remembers the version, so your change will not be replayed — that install keeps the old schema forever. Write a new migration instead.

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

© PageBlocks 2019-present