Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 139 additions & 15 deletions docs/en/seeding.rst
Original file line number Diff line number Diff line change
Expand Up @@ -181,14 +181,120 @@ The Run Method
==============

The run method is automatically invoked by Migrations when you execute the
``cake migration seed`` command. You should use this method to insert your test
``cake seeds run`` command. You should use this method to insert your test
data.

Seed Execution Tracking
========================

Seeds track their execution state in the ``cake_seeds`` database table. By default,
a seed will only run once. If you attempt to run a seed that has already been
executed, it will be skipped with an "already executed" message.

To re-run a seed that has already been executed, use the ``--force`` flag:

.. code-block:: bash

bin/cake seeds run Users --force

You can check which seeds have been executed using the status command:

.. code-block:: bash

bin/cake seeds status

To reset all seeds' execution state (allowing them to run again without ``--force``):

.. code-block:: bash

bin/cake seeds reset

.. note::

Unlike with migrations, seeds do not keep track of which seed classes have
been run. This means database seeds can be run repeatedly. Keep this in
mind when developing them.
When re-running seeds with ``--force``, be careful to ensure your seeds are
idempotent (safe to run multiple times) or they may create duplicate data.

Customizing the Seed Tracking Table
------------------------------------

By default, seed execution is tracked in a table named ``cake_seeds``. You can
customize this table name by configuring it in your ``config/app.php`` or
``config/app_local.php``:

.. code-block:: php

'Migrations' => [
'seed_table' => 'my_custom_seeds_table',
],

This is useful if you need to avoid table name conflicts or want to follow
a specific naming convention in your database.

Idempotent Seeds
================

Some seeds are designed to be run multiple times safely (idempotent), such as seeds
that update configuration or reference data. For these seeds, you can override the
``isIdempotent()`` method to skip tracking entirely:

.. code-block:: php

<?php
declare(strict_types=1);

use Migrations\BaseSeed;

class ConfigSeed extends BaseSeed
{
/**
* Mark this seed as idempotent.
* It will run every time without being tracked.
*/
public function isIdempotent(): bool
{
return true;
}
Comment on lines +253 to +256
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if a non-idempotent seed is run with the 'old' command? It seems like the result would be incorrect behavior.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what old command? you mean migrations v4?
this is a new major, so any of this functionality is only available v5+.

PS: Thats already the case anway right now, it blindly runs everything all the time.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

his is a new major, so any of this functionality is only available v5+.

Sure, but couldn't we retain at least a help message for the old command, or retain it with warnings? People likely have scripts, tooling and muscle memory that we'll be breaking.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer we provide the guides I included and a clear release note for the major.
People dont accidentally upgrade a major afaik, so once they do, they can read and quickly enable if thats really something they need. Most can quickly adjust their tooling and will be happy not to have any additional noise and baggage on the CLI output for a year to come.


public function run(): void
{
// This seed will run every time, so make it safe to run multiple times
$this->execute("
INSERT INTO settings (setting_key, setting_value)
VALUES ('app_version', '2.0.0')
ON DUPLICATE KEY UPDATE setting_value = '2.0.0'
");

// Or check before inserting
$exists = $this->fetchRow(
"SELECT COUNT(*) as count FROM settings WHERE setting_key = 'maintenance_mode'"
);

if ($exists['count'] == 0) {
$this->table('settings')->insert([
'setting_key' => 'maintenance_mode',
'setting_value' => 'false',
])->save();
}
}
}

When ``isIdempotent()`` returns ``true``:

- The seed will **not** be tracked in the ``cake_seeds`` table
- The seed will run **every time** you execute ``seeds run``
- You must ensure the seed's ``run()`` method handles duplicate executions safely

This is useful for:

- Configuration seeds that should always reflect current values
- Reference data that may need periodic updates
- Seeds that use ``INSERT ... ON DUPLICATE KEY UPDATE`` or similar patterns
- Development/testing seeds that need to run repeatedly

.. warning::

Only mark a seed as idempotent if you've verified it's safe to run multiple times.
Otherwise, you may create duplicate data or other unexpected behavior.

The Init Method
===============
Expand Down Expand Up @@ -246,10 +352,28 @@ You can also use the full seed name including the ``Seed`` suffix:

Both forms are supported and work identically.

Automatic Dependency Execution
-------------------------------

When you run a seed that has dependencies, the system will automatically check if
those dependencies have been executed. If any dependencies haven't run yet, they
will be executed automatically before the current seed runs. This ensures proper
execution order and prevents foreign key constraint violations.

For example, if you run:

.. code-block:: bash

bin/cake seeds run ShoppingCartSeed

And ``ShoppingCartSeed`` depends on ``UserSeed`` and ``ShopItemSeed``, the system
will automatically execute those dependencies first if they haven't been run yet.

.. note::

Dependencies are only considered when executing all seed classes (default behavior).
They won't be considered when running specific seed classes.
Dependencies that have already been executed (according to the ``cake_seeds``
table) will be skipped, unless you use the ``--force`` flag which will
re-execute all seeds including dependencies.


Calling a Seed from another Seed
Expand Down Expand Up @@ -371,37 +495,37 @@ SQL `TRUNCATE` command:
Executing Seed Classes
======================

This is the easy part. To seed your database, simply use the ``migrations seed`` command:
This is the easy part. To seed your database, simply use the ``seeds run`` command:

.. code-block:: bash

$ bin/cake migrations seed
$ bin/cake seeds run

By default, Migrations will execute all available seed classes. If you would like to
run a specific class, simply pass in the name of it using the ``--seed`` parameter.
run a specific seed, simply pass in the seed name as an argument.
You can use either the short name (without the ``Seed`` suffix) or the full name:

.. code-block:: bash

$ bin/cake migrations seed --seed User
$ bin/cake seeds run User
# or
$ bin/cake migrations seed --seed UserSeed
$ bin/cake seeds run UserSeed

Both commands work identically.

You can also run multiple seeds:
You can also run multiple seeds by separating them with commas:

.. code-block:: bash

$ bin/cake migrations seed --seed User --seed Permission --seed Log
$ bin/cake seeds run User,Permission,Log
# or with full names
$ bin/cake migrations seed --seed UserSeed --seed PermissionSeed --seed LogSeed
$ bin/cake seeds run UserSeed,PermissionSeed,LogSeed

You can also use the `-v` parameter for more output verbosity:

.. code-block:: bash

$ bin/cake migrations seed -v
$ bin/cake seeds run -v

The Migrations seed functionality provides a simple mechanism to easily and repeatably
insert test data into your database, this is great for development environment
Expand Down
62 changes: 61 additions & 1 deletion docs/en/upgrading-to-builtin-backend.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,66 @@ changes outlined below, please open an issue.
What is different?
==================

Command Structure Changes
-------------------------

As of migrations 5.0, the command structure has changed. The old phinx wrapper
commands have been removed and replaced with new command names:

**Seeds:**

.. code-block:: bash

# Old (4.x and earlier)
bin/cake migrations seed
bin/cake migrations seed --seed Articles

# New (5.x and later)
bin/cake seeds run
bin/cake seeds run Articles

The new commands are:

- ``bin/cake seeds run`` - Run seed classes
- ``bin/cake seeds status`` - Show seed execution status
- ``bin/cake seeds reset`` - Reset seed execution tracking
- ``bin/cake bake seed`` - Generate new seed classes

Maintaining Backward Compatibility
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

If you need to maintain the old command structure for existing scripts or CI/CD
pipelines, you can add command aliases in your application. In your
``src/Application.php`` file, add the following to the ``console()`` method:

.. code-block:: php

public function console(CommandCollection $commands): CommandCollection
{
// Add your application's commands
$commands = $this->addConsoleCommands($commands);

// Add backward compatibility aliases for migrations 4.x commands
$commands->add('migrations seed', \Migrations\Command\SeedCommand::class);

return $commands;
}

For multiple aliases, you can add them all together:

.. code-block:: php

// Add multiple backward compatibility aliases
$commands->add('migrations seed', \Migrations\Command\SeedCommand::class);
$commands->add('migrations seed:run', \Migrations\Command\SeedCommand::class);
$commands->add('migrations seed:status', \Migrations\Command\SeedStatusCommand::class);

This allows gradual migration of scripts and documentation without modifying the
migrations plugin or creating wrapper command classes.

API Changes
-----------

If your migrations are using the ``AdapterInterface`` to fetch rows or update
rows you will need to update your code. If you use ``Adapter::query()`` to
execute queries, the return of this method is now
Expand Down Expand Up @@ -45,5 +105,5 @@ Similar changes are for fetching a single row::
Problems with the builtin backend?
==================================

If your migrations contain errors when run with the builtin backend, please
If your migrations contain errors when run with the builtin backend, please
open `an issue <https://github.com/cakephp/migrations/issues/new>`_.
8 changes: 8 additions & 0 deletions src/BaseSeed.php
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,14 @@ public function shouldExecute(): bool
return true;
}

/**
* {@inheritDoc}
*/
public function isIdempotent(): bool
{
return false;
}

/**
* {@inheritDoc}
*/
Expand Down
29 changes: 19 additions & 10 deletions src/Command/SeedCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class SeedCommand extends Command
*/
public static function defaultName(): string
{
return 'migrations seed';
return 'seeds run';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about the old command? How does that continue working?

Copy link
Member Author

@dereuromark dereuromark Nov 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They dont
It is simple enough to clarify in a migration guide that the API changed.

It is also simple enough IMO to understand. Those are simple commands that people can overwrite locally if they want to retain the old commands.
Thats why we do a major, to improve and clean up the API, that really does need some overhaul, after like 2 decades.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a migration guide and a guide on how to easily restore the old way if needed.

}

/**
Expand All @@ -55,10 +55,10 @@ public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionPar
'',
'Runs a seeder script that can populate the database with data, or run mutations:',
'',
'<info>migrations seed Posts</info>',
'<info>migrations seed Users,Posts</info>',
'<info>migrations seed --plugin Demo</info>',
'<info>migrations seed --connection secondary</info>',
'<info>seeds run Posts</info>',
'<info>seeds run Users,Posts</info>',
'<info>seeds run --plugin Demo</info>',
'<info>seeds run --connection secondary</info>',
'',
'Runs all seeds if no seed names are specified. When running all seeds',
'in an interactive terminal, a confirmation prompt is shown.',
Expand Down Expand Up @@ -87,6 +87,11 @@ public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionPar
'short' => 's',
'default' => ConfigInterface::DEFAULT_SEED_FOLDER,
'help' => 'The folder where your seeds are.',
])
->addOption('force', [
'short' => 'f',
'help' => 'Force re-running seeds that have already been executed',
'boolean' => true,
]);

return $parser;
Expand Down Expand Up @@ -184,9 +189,13 @@ protected function executeSeeds(Arguments $args, ConsoleIo $io): ?int
$io->out(' - ' . $seedName);
}
$io->out('');
$io->out('<warning>Note:</warning> Seeds do not track execution state. They will run');
$io->out('regardless of whether they have been executed before. Ensure your');
$io->out('seeds are idempotent or manually verify they should be (re)run.');
if (!(bool)$args->getOption('force')) {
$io->out('<info>Note:</info> Seeds that have already been executed will be skipped.');
$io->out('Use --force to re-run seeds.');
} else {
$io->out('<warning>Warning:</warning> Running with --force will re-execute all seeds,');
$io->out('potentially creating duplicate data. Ensure your seeds are idempotent.');
}
$io->out('');

// Ask for confirmation
Expand All @@ -199,11 +208,11 @@ protected function executeSeeds(Arguments $args, ConsoleIo $io): ?int
}

// run all the seed(ers)
$manager->seed();
$manager->seed(null, (bool)$args->getOption('force'));
} else {
// run seed(ers) specified as arguments
foreach ($seeds as $seed) {
$manager->seed(trim($seed));
$manager->seed(trim($seed), (bool)$args->getOption('force'));
}
}
$end = microtime(true);
Expand Down
Loading
Loading