Skip to content

Commit 93a55eb

Browse files
Add seed tracking. (#939)
* Add seed logs. * Adjust public API on commands. * Auto create table if not exists. * Fix tests. * Allow adding seeds as idempotent. * Update docs/en/seeding.rst Co-authored-by: Kevin Pfeifer <[email protected]>
1 parent 9fe4430 commit 93a55eb

20 files changed

+1276
-71
lines changed

docs/en/seeding.rst

Lines changed: 139 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -181,14 +181,120 @@ The Run Method
181181
==============
182182

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

187+
Seed Execution Tracking
188+
========================
189+
190+
Seeds track their execution state in the ``cake_seeds`` database table. By default,
191+
a seed will only run once. If you attempt to run a seed that has already been
192+
executed, it will be skipped with an "already executed" message.
193+
194+
To re-run a seed that has already been executed, use the ``--force`` flag:
195+
196+
.. code-block:: bash
197+
198+
bin/cake seeds run Users --force
199+
200+
You can check which seeds have been executed using the status command:
201+
202+
.. code-block:: bash
203+
204+
bin/cake seeds status
205+
206+
To reset all seeds' execution state (allowing them to run again without ``--force``):
207+
208+
.. code-block:: bash
209+
210+
bin/cake seeds reset
211+
187212
.. note::
188213

189-
Unlike with migrations, seeds do not keep track of which seed classes have
190-
been run. This means database seeds can be run repeatedly. Keep this in
191-
mind when developing them.
214+
When re-running seeds with ``--force``, be careful to ensure your seeds are
215+
idempotent (safe to run multiple times) or they may create duplicate data.
216+
217+
Customizing the Seed Tracking Table
218+
------------------------------------
219+
220+
By default, seed execution is tracked in a table named ``cake_seeds``. You can
221+
customize this table name by configuring it in your ``config/app.php`` or
222+
``config/app_local.php``:
223+
224+
.. code-block:: php
225+
226+
'Migrations' => [
227+
'seed_table' => 'my_custom_seeds_table',
228+
],
229+
230+
This is useful if you need to avoid table name conflicts or want to follow
231+
a specific naming convention in your database.
232+
233+
Idempotent Seeds
234+
================
235+
236+
Some seeds are designed to be run multiple times safely (idempotent), such as seeds
237+
that update configuration or reference data. For these seeds, you can override the
238+
``isIdempotent()`` method to skip tracking entirely:
239+
240+
.. code-block:: php
241+
242+
<?php
243+
declare(strict_types=1);
244+
245+
use Migrations\BaseSeed;
246+
247+
class ConfigSeed extends BaseSeed
248+
{
249+
/**
250+
* Mark this seed as idempotent.
251+
* It will run every time without being tracked.
252+
*/
253+
public function isIdempotent(): bool
254+
{
255+
return true;
256+
}
257+
258+
public function run(): void
259+
{
260+
// This seed will run every time, so make it safe to run multiple times
261+
$this->execute("
262+
INSERT INTO settings (setting_key, setting_value)
263+
VALUES ('app_version', '2.0.0')
264+
ON DUPLICATE KEY UPDATE setting_value = '2.0.0'
265+
");
266+
267+
// Or check before inserting
268+
$exists = $this->fetchRow(
269+
"SELECT COUNT(*) as count FROM settings WHERE setting_key = 'maintenance_mode'"
270+
);
271+
272+
if ($exists['count'] == 0) {
273+
$this->table('settings')->insert([
274+
'setting_key' => 'maintenance_mode',
275+
'setting_value' => 'false',
276+
])->save();
277+
}
278+
}
279+
}
280+
281+
When ``isIdempotent()`` returns ``true``:
282+
283+
- The seed will **not** be tracked in the ``cake_seeds`` table
284+
- The seed will run **every time** you execute ``seeds run``
285+
- You must ensure the seed's ``run()`` method handles duplicate executions safely
286+
287+
This is useful for:
288+
289+
- Configuration seeds that should always reflect current values
290+
- Reference data that may need periodic updates
291+
- Seeds that use ``INSERT ... ON DUPLICATE KEY UPDATE`` or similar patterns
292+
- Development/testing seeds that need to run repeatedly
293+
294+
.. warning::
295+
296+
Only mark a seed as idempotent if you've verified it's safe to run multiple times.
297+
Otherwise, you may create duplicate data or other unexpected behavior.
192298

193299
The Init Method
194300
===============
@@ -246,10 +352,28 @@ You can also use the full seed name including the ``Seed`` suffix:
246352
247353
Both forms are supported and work identically.
248354

355+
Automatic Dependency Execution
356+
-------------------------------
357+
358+
When you run a seed that has dependencies, the system will automatically check if
359+
those dependencies have been executed. If any dependencies haven't run yet, they
360+
will be executed automatically before the current seed runs. This ensures proper
361+
execution order and prevents foreign key constraint violations.
362+
363+
For example, if you run:
364+
365+
.. code-block:: bash
366+
367+
bin/cake seeds run ShoppingCartSeed
368+
369+
And ``ShoppingCartSeed`` depends on ``UserSeed`` and ``ShopItemSeed``, the system
370+
will automatically execute those dependencies first if they haven't been run yet.
371+
249372
.. note::
250373

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

254378

255379
Calling a Seed from another Seed
@@ -371,37 +495,37 @@ SQL `TRUNCATE` command:
371495
Executing Seed Classes
372496
======================
373497

374-
This is the easy part. To seed your database, simply use the ``migrations seed`` command:
498+
This is the easy part. To seed your database, simply use the ``seeds run`` command:
375499

376500
.. code-block:: bash
377501
378-
$ bin/cake migrations seed
502+
$ bin/cake seeds run
379503
380504
By default, Migrations will execute all available seed classes. If you would like to
381-
run a specific class, simply pass in the name of it using the ``--seed`` parameter.
505+
run a specific seed, simply pass in the seed name as an argument.
382506
You can use either the short name (without the ``Seed`` suffix) or the full name:
383507

384508
.. code-block:: bash
385509
386-
$ bin/cake migrations seed --seed User
510+
$ bin/cake seeds run User
387511
# or
388-
$ bin/cake migrations seed --seed UserSeed
512+
$ bin/cake seeds run UserSeed
389513
390514
Both commands work identically.
391515

392-
You can also run multiple seeds:
516+
You can also run multiple seeds by separating them with commas:
393517

394518
.. code-block:: bash
395519
396-
$ bin/cake migrations seed --seed User --seed Permission --seed Log
520+
$ bin/cake seeds run User,Permission,Log
397521
# or with full names
398-
$ bin/cake migrations seed --seed UserSeed --seed PermissionSeed --seed LogSeed
522+
$ bin/cake seeds run UserSeed,PermissionSeed,LogSeed
399523
400524
You can also use the `-v` parameter for more output verbosity:
401525

402526
.. code-block:: bash
403527
404-
$ bin/cake migrations seed -v
528+
$ bin/cake seeds run -v
405529
406530
The Migrations seed functionality provides a simple mechanism to easily and repeatably
407531
insert test data into your database, this is great for development environment

docs/en/upgrading-to-builtin-backend.rst

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,66 @@ changes outlined below, please open an issue.
1818
What is different?
1919
==================
2020

21+
Command Structure Changes
22+
-------------------------
23+
24+
As of migrations 5.0, the command structure has changed. The old phinx wrapper
25+
commands have been removed and replaced with new command names:
26+
27+
**Seeds:**
28+
29+
.. code-block:: bash
30+
31+
# Old (4.x and earlier)
32+
bin/cake migrations seed
33+
bin/cake migrations seed --seed Articles
34+
35+
# New (5.x and later)
36+
bin/cake seeds run
37+
bin/cake seeds run Articles
38+
39+
The new commands are:
40+
41+
- ``bin/cake seeds run`` - Run seed classes
42+
- ``bin/cake seeds status`` - Show seed execution status
43+
- ``bin/cake seeds reset`` - Reset seed execution tracking
44+
- ``bin/cake bake seed`` - Generate new seed classes
45+
46+
Maintaining Backward Compatibility
47+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
48+
49+
If you need to maintain the old command structure for existing scripts or CI/CD
50+
pipelines, you can add command aliases in your application. In your
51+
``src/Application.php`` file, add the following to the ``console()`` method:
52+
53+
.. code-block:: php
54+
55+
public function console(CommandCollection $commands): CommandCollection
56+
{
57+
// Add your application's commands
58+
$commands = $this->addConsoleCommands($commands);
59+
60+
// Add backward compatibility aliases for migrations 4.x commands
61+
$commands->add('migrations seed', \Migrations\Command\SeedCommand::class);
62+
63+
return $commands;
64+
}
65+
66+
For multiple aliases, you can add them all together:
67+
68+
.. code-block:: php
69+
70+
// Add multiple backward compatibility aliases
71+
$commands->add('migrations seed', \Migrations\Command\SeedCommand::class);
72+
$commands->add('migrations seed:run', \Migrations\Command\SeedCommand::class);
73+
$commands->add('migrations seed:status', \Migrations\Command\SeedStatusCommand::class);
74+
75+
This allows gradual migration of scripts and documentation without modifying the
76+
migrations plugin or creating wrapper command classes.
77+
78+
API Changes
79+
-----------
80+
2181
If your migrations are using the ``AdapterInterface`` to fetch rows or update
2282
rows you will need to update your code. If you use ``Adapter::query()`` to
2383
execute queries, the return of this method is now
@@ -45,5 +105,5 @@ Similar changes are for fetching a single row::
45105
Problems with the builtin backend?
46106
==================================
47107

48-
If your migrations contain errors when run with the builtin backend, please
108+
If your migrations contain errors when run with the builtin backend, please
49109
open `an issue <https://github.com/cakephp/migrations/issues/new>`_.

src/BaseSeed.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,14 @@ public function shouldExecute(): bool
215215
return true;
216216
}
217217

218+
/**
219+
* {@inheritDoc}
220+
*/
221+
public function isIdempotent(): bool
222+
{
223+
return false;
224+
}
225+
218226
/**
219227
* {@inheritDoc}
220228
*/

src/Command/SeedCommand.php

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ class SeedCommand extends Command
3939
*/
4040
public static function defaultName(): string
4141
{
42-
return 'migrations seed';
42+
return 'seeds run';
4343
}
4444

4545
/**
@@ -55,10 +55,10 @@ public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionPar
5555
'',
5656
'Runs a seeder script that can populate the database with data, or run mutations:',
5757
'',
58-
'<info>migrations seed Posts</info>',
59-
'<info>migrations seed Users,Posts</info>',
60-
'<info>migrations seed --plugin Demo</info>',
61-
'<info>migrations seed --connection secondary</info>',
58+
'<info>seeds run Posts</info>',
59+
'<info>seeds run Users,Posts</info>',
60+
'<info>seeds run --plugin Demo</info>',
61+
'<info>seeds run --connection secondary</info>',
6262
'',
6363
'Runs all seeds if no seed names are specified. When running all seeds',
6464
'in an interactive terminal, a confirmation prompt is shown.',
@@ -87,6 +87,11 @@ public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionPar
8787
'short' => 's',
8888
'default' => ConfigInterface::DEFAULT_SEED_FOLDER,
8989
'help' => 'The folder where your seeds are.',
90+
])
91+
->addOption('force', [
92+
'short' => 'f',
93+
'help' => 'Force re-running seeds that have already been executed',
94+
'boolean' => true,
9095
]);
9196

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

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

201210
// run all the seed(ers)
202-
$manager->seed();
211+
$manager->seed(null, (bool)$args->getOption('force'));
203212
} else {
204213
// run seed(ers) specified as arguments
205214
foreach ($seeds as $seed) {
206-
$manager->seed(trim($seed));
215+
$manager->seed(trim($seed), (bool)$args->getOption('force'));
207216
}
208217
}
209218
$end = microtime(true);

0 commit comments

Comments
 (0)