Skip to main content

Cloudsoft Zone Build, deploy and scale with practical guides to PHP, Node.js, React, DevOps and AI automation. Explore the guides

Cloudsoft Zone
CakePHPMySQLPHPTroubleshooting guide

CakePHP Migrations 5 upgrade: fix seeds, phinxlog and FK errors

Migrations 5 removed Phinx. Fix the common upgrade breakages: seeds run, cake_seeds tracking, fetchAll(), old base classes, foreign key names and phinxlog.

Cloudsoft Zone

Updated 6 min read

Two black Sun Microsystems server racks with cables running from their tops in a glass-walled server room
Photograph: Raysonho @ Open Grid Scheduler / Grid Engine / Public domain, via Wikimedia Commons
In this article

Most CakePHP Migrations 5 upgrade failures come from one change: Phinx is gone. Seeds now run with bin/cake seeds run and are tracked so they run only once, adapter queries return CakePHP statements, migration history can move from phinxlog to a single cake_migrations table, and unnamed foreign keys get new names. Fix those five areas and the upgrade is usually done.

Migrations 5.0.0 was released on 21 January 2026; the latest patch in October 2026 is 5.2.6 (29 July 2026). It requires PHP 8.2+ and CakePHP 5.3+, and the cakephp/app 5.4 skeleton now requires cakephp/migrations: ^5.0, so anyone creating or upgrading an app this year meets it. Below, each problem is listed with its symptom, cause and fix.

What changed at a glance

AreaMigrations 4.xMigrations 5.x
BackendPhinx or builtin (builtin default since 4.4)Builtin only; Phinx dependency removed
Run seedsbin/cake migrations seedbin/cake seeds run
Seed trackingNone; seeds re-run every timeRecorded in cake_seeds; run once by default
History tablephinxlog and {plugin}_phinxlogcake_migrations for new apps; legacy tables auto-detected
getAdapter()->query()Phinx resultCake\Database\StatementInterface
Unnamed foreign keys (MySQL)Named by MySQL, e.g. articles_ibfk_1Named {table}_{columns} by the plugin
RequirementsPHP 8.1+PHP 8.2+, CakePHP 5.3+

Before changing anything, upgrade the dependency and confirm the app still boots:

Shell
composer require cakephp/migrations:^5.2
bin/cake migrations status

Problem 1: “migrations seed” is not a command

Symptom: deploy scripts or CI jobs that call bin/cake migrations seed fail after the upgrade. Cause: the Phinx wrapper commands were removed. Fix: switch to the new command group.

Shell
bin/cake seeds run                 # all seeds
bin/cake seeds run Articles        # one seed (the "Seed" suffix is optional)
bin/cake seeds status              # which seeds have run
bin/cake seeds reset               # clear seed tracking

If you cannot update every script at once, the official guide shows an alias in Application::console():

PHP
public function console(CommandCollection $commands): CommandCollection
{
    $commands = $this->addConsoleCommands($commands);
    $commands->add('migrations seed', \Migrations\Command\SeedCommand::class);

    return $commands;
}

Problem 2: a seed says “already executed” and does nothing

Symptom: a development seed that used to refill data on every run is now skipped. Cause: seeds are tracked in the cake_seeds table and run once by default, which prevents accidental duplicates in production. Fix: choose deliberately.

  • Re-run one seed on purpose: bin/cake seeds run Users --force.
  • Make the run-once rule visible to the team: bin/cake seeds status.
  • For reference data that should always be applied, override isIdempotent() to return true, and make the seed safe to repeat.

The cleanest idempotent pattern uses insertOrSkip(), new in 5.x, which skips rows that would violate a unique constraint:

PHP
<?php
declare(strict_types=1);

use Migrations\BaseSeed;

class CurrenciesSeed extends BaseSeed
{
    public function isIdempotent(): bool
    {
        return true;
    }

    public function run(): void
    {
        $this->table('currencies')
            ->insertOrSkip([
                ['code' => 'USD', 'name' => 'US Dollar'],
                ['code' => 'EUR', 'name' => 'Euro'],
                ['code' => 'INR', 'name' => 'Indian Rupee'],
            ])
            ->saveData();
    }
}

This only works if currencies.code has a unique index. Without one, every run inserts duplicates. Use getDependencies() (returning seed names such as ['Users']) when a seed needs other seeds first; dependencies that have not yet run are now executed automatically.

Database diagram of tables such as stores, sales, titles, authors and publishers joined by relationship lines
Seeds fill tables like these with reference data; unique keys make them safe to repeat. Diagram: Cmiller1137, CC BY-SA 4.0.

Problem 3: fetchAll() returns the wrong shape

Symptom: a data migration that reads rows breaks with undefined index notices or numeric keys. Cause: getAdapter()->query() now returns a CakePHP StatementInterface. Fix: ask for associative rows explicitly.

PHP
$stmt = $this->getAdapter()->query('SELECT id, email FROM users WHERE email_verified IS NULL');
foreach ($stmt->fetchAll('assoc') as $row) {
    // $row['id'], $row['email']
}

$first = $this->getAdapter()->query('SELECT COUNT(*) AS n FROM users')->fetch('assoc');

Problem 4: Class “AbstractMigration” not found

Symptom: old migration or seed files fatal on load. Cause: files that extend Phinx\Migration\AbstractMigration, Migrations\AbstractMigration or Migrations\AbstractSeed reference classes that no longer exist; the 5.x source only ships BaseMigration and BaseSeed. Fix: change the parent class. The table API is the same for common operations, so the method bodies rarely need edits.

PHP
<?php
declare(strict_types=1);

use Migrations\BaseMigration;

class AddStatusToOrders extends BaseMigration
{
    public function change(): void
    {
        $this->table('orders')
            ->addColumn('status', 'string', ['limit' => 20, 'default' => 'pending', 'null' => false])
            ->addCheckConstraint('orders_status_valid', "status IN ('pending', 'paid', 'cancelled')")
            ->update();
    }
}

Find stragglers with grep -rn "AbstractMigration\|AbstractSeed\|Phinx" config/ plugins/. The check constraint above is another 5.x addition; MySQL enforces CHECK constraints from 8.0.16 onwards.

Problem 5: dropForeignKey() cannot find the constraint

Symptom: a rollback or a later migration fails when dropping a foreign key. Cause: when addForeignKey() is called without a name, 5.x now generates {table}_{columns} on every database. On MySQL the old migrations produced server-generated names such as articles_ibfk_1, so the names in your database and in new code may disagree. Fix: name constraints explicitly in new migrations, and drop old ones by the name that really exists.

SQL
SELECT CONSTRAINT_NAME, TABLE_NAME
FROM information_schema.REFERENTIAL_CONSTRAINTS
WHERE CONSTRAINT_SCHEMA = DATABASE();
PHP
$this->table('comments')
    ->addForeignKey('article_id', 'articles', 'id', [
        'delete' => 'CASCADE',
        'update' => 'NO_ACTION',
        'constraint' => 'comments_article_id_fk',
    ])
    ->update();

Generated names are truncated to fit database limits (61 characters on MySQL) and get a numeric suffix on conflict.

Coils of red, yellow, green, blue and grey network cables hanging in front of a server rack
Unnamed foreign keys are as hard to trace as unlabelled cables. Photo: ProjectManhattan, CC BY-SA 3.0.

Problem 6: two history tables, or migrations re-running

Symptom: you see both phinxlog and cake_migrations, or are unsure which one is in use. Cause: 5.x uses the unified cake_migrations table (with a plugin column) for new apps, but if any phinxlog table exists it keeps using the legacy tables. That is controlled by Migrations.legacyTables: null autodetects, false forces the new table, true forces legacy. Fix: migrate the history when you are ready, not during the dependency bump.

  1. Preview: bin/cake migrations upgrade --dry-run.
  2. Copy history: bin/cake migrations upgrade.
  3. Set 'Migrations' => ['legacyTables' => false] in config/app.php and deploy.
  4. Only after a few clean deploys, run bin/cake migrations upgrade --drop-tables. This step cannot be rolled back.

Do this one environment at a time, starting with a copy of production data.

Problem 7: deploys continue with pending migrations

Migrations 5.2 can act as a deploy gate. bin/cake migrations status --all checks the app and every loaded plugin, prints a short summary and exits with 0 when everything is applied, 3 when migrations are pending and 2 when the history table lists migrations whose files are missing:

Shell
bin/cake migrations migrate
bin/cake migrations status --all || { echo "Schema not in sync"; exit 1; }

MISSING entries usually mean someone deleted or renamed a migration file. bin/cake migrations status --cleanup removes them from history, but check why they vanished first.

A safe upgrade order

  1. On 4.x, confirm you are on the builtin backend (the default since 4.4) and that bin/cake migrations status is clean.
  2. Bump to cakephp/migrations:^5.2 and fix the parent classes of migrations and seeds.
  3. Replace migrations seed in scripts, or add the alias.
  4. Search data migrations for fetchAll() and fetch() and add 'assoc'.
  5. Run the full migration set against an empty test database, then roll back one step and re-apply, to flush out foreign key naming issues.
  6. Later, as a separate change, move history to cake_migrations.

Gotchas

  • Tests use the same plugin. The skeleton’s tests/bootstrap.php builds the test schema with (new Migrator())->run(), so a broken migration breaks the whole test suite.
  • Seeds are not fixtures. Production seeds should be idempotent; demo data belongs in a seed you never run in production.
  • MySQL online DDL. 5.x adds algorithm and lock options on table updates (for example INPLACE and NONE); MySQL raises an error if it cannot honour the combination, which is better than an unexpected table lock.

Frequently asked questions

  • Does CakePHP Migrations 5 still use Phinx?

    No. Phinx was removed as a dependency in 5.0; the builtin backend, built on CakePHP’s database layer, is the only backend.

  • How do I run seeds in Migrations 5?

    Use bin/cake seeds run, or bin/cake seeds run Name for one seed. bin/cake migrations seed was removed, though you can alias it in Application::console().

  • Why does my seed only run once?

    Seed execution is now tracked in the cake_seeds table. Use --force, bin/cake seeds reset, or make the seed idempotent by overriding isIdempotent().

  • Do I have to migrate from phinxlog to cake_migrations?

    No. If a phinxlog table exists, 5.x keeps using it. You can move later with bin/cake migrations upgrade and set Migrations.legacyTables to false.

  • Which versions does Migrations 5 require?

    PHP 8.2 or later and CakePHP 5.3 or later, according to the plugin’s upgrade guide and composer.json.

  • Can I use migration status as a CI check?

    Yes. bin/cake migrations status --all exits with 3 when migrations are pending and 2 when history entries have no matching file.

Advertise on CloudSoftZone

Your next customer could start right here.

Introduce your tool, product or business to readers exploring technology.

Advertise with us

Explore placements and request a private quote

Sources

  1. Upgrading from 4.x to 5.x — CakePHP Book (Migrations 5.x)
  2. Upgrading to the builtin backend — CakePHP Book (Migrations 5.x)
  3. Database Seeding — CakePHP Book (Migrations 5.x)
  4. Running and Managing Migrations — CakePHP Book (Migrations 5.x)
  5. Release 5.0.0 — cakephp/migrations on GitHub, 2026-01-21

Written with substantial AI assistance. Facts checked against cited sources. Editorial policy.

Comments

No comments yet. Be the first to join the conversation.

Leave a comment

Every comment is read by a moderator before it appears. Your email address is never published; it is used only to contact you about this comment.

Never published. Used only to contact you about this comment.

Loading the security check…

Comments are moderated before publication.

  • Rows of server racks with cabling in a data centre aisle

    Resource guideWordPress

    WordPress security hardening checklist for 2026 (WordPress 7.1)

    A practical WordPress 7.1 hardening checklist based on the official handbook: updates, two-step logins, wp-config.php constants, file permissions, database privileges, firewalls, WP-CLI checksum checks and tested backups.

    By Cloudsoft Zone