Skip to content

Sync 6.x with current 5.next - #1122

Closed
josbeir wants to merge 91 commits into
6.xfrom
sync/6.x-5.next
Closed

josbeir wants to merge 91 commits into
6.xfrom
sync/6.x-5.next

Conversation

@josbeir

@josbeir josbeir commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

Bring the published 6.x branch up to current 5.next before applying the CakePHP 6 compatibility changes in #1121.

This preserves the existing 6.x history, including the nullable modified-column fix from #1026, and merges 5.next rather than replaying that history. The single conflict is the serialized MySQL default-schema fixture; it uses the current 5.next fixture. The resulting files match the previously prepared sync snapshot exactly.

The branch includes the separate 5.next CI repair in #1120. Merge order:

  1. Fix 5.next migration dispatch analysis and snapshot expectation #1120 into 5.next.
  2. This sync PR into 6.x, preserving its merge ancestry.
  3. Retarget Prepare Migrations 6.x for CakePHP 6 #1121 from sync/6.x-5.next to 6.x, then merge its two CakePHP 6 compatibility/code-standard commits.

The long commit history in this PR is the intervening 5.next development. The CakePHP 6 dependency, command, schema and code-standard changes are reviewed separately in #1121.

Validation in an isolated installation using this sync snapshot's CakePHP 5 dependencies on PHP 8.5.11:

  • Full SQLite suite: 1,784 tests, 3,139 assertions, no failures.
  • Full PHPCS and PHPStan 2.3 pass; the PHPStan baseline is unchanged.
  • Existing deprecation notices and database-specific skips remain.

The published 6.x branch is unchanged until this PR is merged.

dereuromark and others added 30 commits March 6, 2026 23:00
Add support for the `fixed` attribute on binary columns to distinguish
between fixed-length BINARY and variable-length VARBINARY types.
This mirrors cakephp/cakephp#19207.

* Add test cases for fixed option on binary columns

Tests cover:
- Column class getter/setter for fixed option
- Column::toArray() including fixed in output
- Column::setOptions() accepting fixed option
- MigrationHelper::getColumnOption() including/excluding fixed
- MysqlAdapter creating BINARY vs VARBINARY based on fixed option

* Add column type assertions to binary fixed test
Only show 'Set legacyTables => false' step after tables have been
dropped, as the upgrade command becomes unavailable once this config
is set.

Fixes #1036
* Fix upgrade command not matching plugins with slashes

When upgrading from legacy phinxlog tables, the plugin name was being
derived by camelizing the table prefix (e.g. cake_d_c_users -> CakeDCUsers).
This didn't work for plugins with slashes in their names like CakeDC/Users
since the slash was replaced with underscore during table name generation.

This fix builds a map of loaded plugin names to their expected table
prefixes, allowing proper matching of plugins like CakeDC/Users.

Fixes #1038

* Fix test for SQL Server compatibility
* Add TYPE_BIT constant to AdapterInterface

Adds the TYPE_BIT constant mapping to TableSchemaInterface::TYPE_BIT
for consistency with the core BIT type support added in cakephp/cakephp#19223.

This allows migration users to reference AdapterInterface::TYPE_BIT
when defining BIT columns in MySQL migrations.

* Bump cakephp/database constraint to ^5.3.2 for TYPE_BIT support
When a foreign key is added without an explicit name, the MysqlAdapter
and SqliteAdapter now generate a name following the pattern
'tablename_columnname' (e.g., 'articles_user_id' for a FK on the
user_id column in the articles table).

This matches the behavior of PostgresAdapter and SqlserverAdapter,
which already auto-generate FK names. This ensures constraint names
are always strings and prevents issues with constraint lookup methods
that expect string names.
Update the expected FK names in comparison files and schema dumps
to match the new auto-generated naming pattern (tablename_columnname).
The FK constraint name change from auto-generated (ibfk_N) to
explicit (table_column) also affects the implicit index MySQL
creates for FKs. Update comparison files to reflect the index
rename from user_id to articles_user_id.
With the FK naming changes, both the lock file and database have
the index named articles_user_id, so no diff is needed for this
index. Remove the index rename operations that were incorrectly
added.
* Add conflict resolution for auto-generated FK constraint names

When auto-generating FK constraint names, check if the name already
exists and append a counter suffix (_2, _3, etc.) if needed.

This prevents duplicate constraint name errors when multiple FKs
are created on the same columns with different references.

* Remove unused variable

* Truncate FK constraint names to max 128 characters

Limit auto-generated foreign key constraint names to 125 characters
to ensure the final name (including potential _XX counter suffix)
stays within 128 characters. This prevents identifier length errors
on databases with strict limits (MySQL: 64, PostgreSQL: 63).

* Use database-specific identifier length limits

- MySQL: 61 chars (64 limit - 3 for _XX suffix)
- PostgreSQL: 60 chars (63 limit - 3 for _XX suffix)
- SQL Server: 125 chars (128 limit - 3 for _XX suffix)
- SQLite: No limit needed

* Use IDENTIFIER_MAX_LENGTH class constant for clarity

Each adapter now defines its database-specific identifier length limit
as a class constant, making the code more self-documenting.
When generating migrations, the 'fixed' option could be set to null for
binary columns, which causes an error when running the migration because
Column::setFixed() expects a bool, not null.

Fixes #1046
Add upgrade documentation explaining the new auto-generated FK constraint
naming behavior introduced in #1041 and #1042, including:
- New consistent naming pattern across all adapters
- Potential impact on existing migrations with rollbacks
- Conflict resolution with counter suffixes
- Database-specific name length limits
* add using when changing column type to json (#1031)

* Fix CI failures on MySQL/MariaDB (#1034)

1. Handle uninitialized Column::$fixed property in BakeMigrationDiffCommand

   When TableSchema::getColumn() is called on cached/serialized schema
   objects, the Column::$fixed property may not be initialized, causing
   an Error. Added safeGetColumn() helper that catches this error and
   uses reflection to initialize uninitialized properties before retry.

2. Fix CURRENT_TIMESTAMP test assertion for MySQL/MariaDB

   Different versions of MySQL and MariaDB return CURRENT_TIMESTAMP
   in different formats (CURRENT_TIMESTAMP, current_timestamp(),
   CURRENT_TIMESTAMP()). Changed the test to use a regex that accepts
   all valid formats case-insensitively.

Fixes #1033

* Fix TEXT column variants not round-tripping correctly (#1032)

When using migration_diff, TEXT column variants (TINYTEXT, MEDIUMTEXT,
LONGTEXT) were not being properly mapped back from the database. The
BLOB type handling already used rawType to distinguish BLOB variants,
but TEXT variants were missing equivalent handling.

This adds similar rawType-based mapping for TEXT columns in
mapColumnType() and includes round-trip tests.

Fixes #1029

* Add fixed option for binary column type (#1014)

Add support for the `fixed` attribute on binary columns to distinguish
between fixed-length BINARY and variable-length VARBINARY types.
This mirrors cakephp/cakephp#19207.

* Add test cases for fixed option on binary columns

Tests cover:
- Column class getter/setter for fixed option
- Column::toArray() including fixed in output
- Column::setOptions() accepting fixed option
- MigrationHelper::getColumnOption() including/excluding fixed
- MysqlAdapter creating BINARY vs VARBINARY based on fixed option

* Add column type assertions to binary fixed test

* Fix misleading next steps message in upgrade command (#1037)

Only show 'Set legacyTables => false' step after tables have been
dropped, as the upgrade command becomes unavailable once this config
is set.

Fixes #1036

* Fix upgrade command not matching plugins with slashes (#1039)

* Fix upgrade command not matching plugins with slashes

When upgrading from legacy phinxlog tables, the plugin name was being
derived by camelizing the table prefix (e.g. cake_d_c_users -> CakeDCUsers).
This didn't work for plugins with slashes in their names like CakeDC/Users
since the slash was replaced with underscore during table name generation.

This fix builds a map of loaded plugin names to their expected table
prefixes, allowing proper matching of plugins like CakeDC/Users.

Fixes #1038

* Fix test for SQL Server compatibility

* Add TYPE_BIT constant to AdapterInterface (#1013)

* Add TYPE_BIT constant to AdapterInterface

Adds the TYPE_BIT constant mapping to TableSchemaInterface::TYPE_BIT
for consistency with the core BIT type support added in cakephp/cakephp#19223.

This allows migration users to reference AdapterInterface::TYPE_BIT
when defining BIT columns in MySQL migrations.

* Bump cakephp/database constraint to ^5.3.2 for TYPE_BIT support

* Bump PHPStan level +1

* Make props non-nullable

* Add null guards for getConstraint() in BakeMigrationDiffCommand

* Fix nullable return types in Column and ForeignKey

Column::getNull() now coalesces the nullable bool property to
false. ForeignKey::getOnDelete()/getOnUpdate() guard against null
from getDelete()/getUpdate() before calling mapAction().

* Fix null safety in Db adapters and domain classes

Cast preg_replace results to string, coalesce nullable getColumns()
returns, cast nullable getReferencedTable()/getName() at call sites,
and use null-safe operator for optional ConsoleIo.

* Fix remaining PHPStan level 8 null safety issues

Add null guards for constraint diff, fail-fast for null column type,
fix import order, and remaining null safety in BaseSeed, ColumnParser,
TableFinder, and MigrationHelper.

* Replace assert with RuntimeException in AbstractAdapter::getConnection()

Assertions can be disabled in production, which would allow a null
connection to slip through silently. Throw a RuntimeException instead
to fail fast in all environments.

* Use truthiness checks for optional nullable getters

Replace (string) casts with assign-then-check pattern for optional
values that can legitimately be null: Index::getWhere(),
ForeignKey::getName(), Index::getName(), Column::getGenerated().
This avoids silently converting null to empty string.

* Throw on null for required Column::getName() and FK::getReferencedTable()

Replace (string) casts with explicit null checks and
InvalidArgumentException throws for values that must be present:
Column::getName() in SQL generation and ForeignKey::getReferencedTable()
in FK definition builders. A column without a name or a foreign key
without a referenced table are always programming errors that should
fail fast rather than silently produce broken SQL.

* Narrow ForeignKey::getColumns() return type and remove null guards

Override getColumns() in Migrations ForeignKey to return array instead
of the parent's ?array. The $columns property is always initialized as
[] in the constructor, making null unreachable. This is a covariant
return type narrowing, same pattern used for Column::getNull().

Removes the now-unnecessary ?? [] fallbacks from all 6 call sites
across the adapter and plan classes.

* Narrow Column::getName() return type and remove dead null checks

Override getName() in Migrations Column to return string instead of the
parent's ?string. The $name property is typed as string (not ?string)
in the constructor, making null unreachable. This is a covariant return
type narrowing, same pattern used for ForeignKey::getColumns().

Removes now-unnecessary null checks across adapter/action files and
the dead null guard in Table::setPrimaryKey().

* Add null guards for column diff loop in BakeMigrationDiffCommand

Use early-continue pattern to narrow nullable safeGetColumn() return
types before array operations, fixing PHPStan level 8 errors.

* Cast nullable getName() to string for drop FK/index instructions

* Fix phpcs coding standard violations

Remove unused InvalidArgumentException import from RecordingAdapter,
extract assignment out of if condition in PostgresAdapter, and remove
blank line before closing brace in ForeignKeyTest.

* Consolidate null guard conditions in BakeMigrationDiffCommand

* Fix inverted condition in ChangeColumn name fallback

* Accept null in Column::setFixed() for snapshot compatibility

Generated migration snapshots can emit 'fixed' => null for binary
columns, which causes a TypeError since setFixed() only accepted
bool. Widen the parameter to ?bool to match the property and getter
types. Fixes #1046.

---------

Co-authored-by: Matthias Wirtz <matthias.wirtz@hotmail.de>
Co-authored-by: Mark Scherer <dereuromark@users.noreply.github.com>
Co-authored-by: Jamison Bryant <jbryant@ticketsauce.com>
Update bake templates and internal code to use the non-deprecated
setDelete() and setUpdate() methods instead of the deprecated
setOnDelete() and setOnUpdate() methods.

Fixes #1045
* Auto-generate foreign key constraint names when not provided

When a foreign key is added without an explicit name, the MysqlAdapter
and SqliteAdapter now generate a name following the pattern
'tablename_columnname' (e.g., 'articles_user_id' for a FK on the
user_id column in the articles table).

This matches the behavior of PostgresAdapter and SqlserverAdapter,
which already auto-generate FK names. This ensures constraint names
are always strings and prevents issues with constraint lookup methods
that expect string names.

* Update test comparison files for new FK naming convention

Update the expected FK names in comparison files and schema dumps
to match the new auto-generated naming pattern (tablename_columnname).

* Update test comparison files with index rename operations

The FK constraint name change from auto-generated (ibfk_N) to
explicit (table_column) also affects the implicit index MySQL
creates for FKs. Update comparison files to reflect the index
rename from user_id to articles_user_id.

* Remove unnecessary index operations from comparison file

With the FK naming changes, both the lock file and database have
the index named articles_user_id, so no diff is needed for this
index. Remove the index rename operations that were incorrectly
added.

* Add conflict resolution for auto-generated FK constraint names (#1042)

* Add conflict resolution for auto-generated FK constraint names

When auto-generating FK constraint names, check if the name already
exists and append a counter suffix (_2, _3, etc.) if needed.

This prevents duplicate constraint name errors when multiple FKs
are created on the same columns with different references.

* Remove unused variable

* Truncate FK constraint names to max 128 characters

Limit auto-generated foreign key constraint names to 125 characters
to ensure the final name (including potential _XX counter suffix)
stays within 128 characters. This prevents identifier length errors
on databases with strict limits (MySQL: 64, PostgreSQL: 63).

* Use database-specific identifier length limits

- MySQL: 61 chars (64 limit - 3 for _XX suffix)
- PostgreSQL: 60 chars (63 limit - 3 for _XX suffix)
- SQL Server: 125 chars (128 limit - 3 for _XX suffix)
- SQLite: No limit needed

* Use IDENTIFIER_MAX_LENGTH class constant for clarity

Each adapter now defines its database-specific identifier length limit
as a class constant, making the code more self-documenting.
Document foreign key constraint naming changes
* Add support for database views and triggers (#347)

This implements support for creating and managing database views and
triggers through CakePHP migrations, addressing issue #347.

- Create and drop database views in migrations
- Support for OR REPLACE syntax (MySQL, PostgreSQL)
- Materialized views support (PostgreSQL only)
- Database-agnostic API with adapter-specific implementations

- Create and drop database triggers in migrations
- Support for BEFORE/AFTER/INSTEAD OF timing
- Support for INSERT/UPDATE/DELETE events
- Support for multiple events per trigger
- FOR EACH ROW vs FOR EACH STATEMENT options

**Value Objects:**
- `Migrations\Db\Table\View` - Represents a database view
- `Migrations\Db\Table\Trigger` - Represents a database trigger

**Actions:**
- `Migrations\Db\Action\CreateView` - Action for creating views
- `Migrations\Db\Action\DropView` - Action for dropping views
- `Migrations\Db\Action\CreateTrigger` - Action for creating triggers
- `Migrations\Db\Action\DropTrigger` - Action for dropping triggers

**Core:**
- `AbstractAdapter` - Added abstract methods for view/trigger support
- `Table` - Added createView(), dropView(), createTrigger(), dropTrigger()
- `BaseMigration` - Added convenience methods for easy migration usage

**Adapters:**
- `MysqlAdapter` - MySQL-specific view/trigger SQL generation
- `PostgresAdapter` - PostgreSQL implementation with materialized views
- `SqliteAdapter` - SQLite-specific syntax handling
- `SqlserverAdapter` - SQL Server implementation

```php
// Create a view
$this->createView(
    'active_users',
    'SELECT * FROM users WHERE status = "active"'
);

// Create a materialized view (PostgreSQL)
$this->createView(
    'user_stats',
    'SELECT user_id, COUNT(*) FROM posts GROUP BY user_id',
    ['materialized' => true]
);

// Create a trigger
$this->createTrigger(
    'users',
    'log_changes',
    'INSERT',
    "INSERT INTO audit_log VALUES (NEW.id, NOW())",
    ['timing' => 'AFTER']
);

// Drop view/trigger
$this->dropView('active_users');
$this->dropTrigger('users', 'log_changes');
```

Added comprehensive tests for view and trigger functionality in
MysqlAdapterTest covering creation, querying, and deletion.

Added example migration file demonstrating various view and trigger
scenarios with database-specific considerations.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Fix AbstractAdapterTest by adding stub implementations for view/trigger methods

The anonymous test class extending AbstractAdapter needs to implement
the new abstract methods for view and trigger support.

* Fixes.

* Fixes.

* Fix missing imports and add return types after merge

- Add missing `use Exception` and `use Migrations\Db\Table` imports to AbstractAdapter
- Add `: void` return types to algorithm/lock test methods in MysqlAdapterTest
- Fix alphabetical ordering of use statements
The issue was a mismatch between CakePHP's LENGTH_LONG constant (4294967295)
and migrations' TEXT_LONG constant (2147483647).

When using `bake migration_diff`, CakePHP's schema reflection returns
LENGTH_LONG for LONGTEXT columns, but MysqlAdapter expected TEXT_LONG.

This fix:
1. MigrationHelper: Convert LENGTH_LONG to TEXT_LONG when generating migrations
2. MysqlAdapter: Accept both constants for backward compatibility with existing
   migrations that have the wrong value

Fixes #1029
* Add fluent Table API for check constraints

Add addCheckConstraint() and dropCheckConstraint() methods to the Table
class, allowing users to manage check constraints using the same fluent
API pattern used for indexes and foreign keys.

The adapter already had the underlying implementation, this adds:
- AddCheckConstraint action class
- DropCheckConstraint action class
- Table::addCheckConstraint() method
- Table::dropCheckConstraint() method
- Table::hasCheckConstraint() method
- Plan.php updated to handle check constraint actions
- Unit tests for the new methods

* docs: Fix check constraint API examples

Update examples to use the correct API signature:
- addCheckConstraint($expression, $options) not addCheckConstraint($name, $expression)
- Replace non-existent checkConstraint() fluent builder with CheckConstraint object
- Fix all examples throughout the check constraints section
# Conflicts:
#	src/Db/Adapter/MysqlAdapter.php
#	src/Db/Adapter/SqliteAdapter.php
* Add migrations reset command for development workflow

Implements the "nuclear option" for migrations as discussed in #972:
- Drops all application tables (excluding migration tracking tables)
- Re-runs all migrations from scratch
- Requires interactive Y/N confirmation for safety (default: N)
- Supports --dry-run mode to preview changes
- Dispatches Migration.beforeReset and Migration.afterReset events

This is useful during development when you want a fresh database
without manually rolling back or dropping tables.

Refs #972

* Fix foreign key handling for PostgreSQL and SQL Server

- PostgreSQL: Use CASCADE in DROP TABLE statement
- SQL Server: Drop foreign key constraints first before dropping tables
- MySQL/SQLite: Continue using session-level FK check toggle

* Update completion test to include reset command

* Improve reset command logic

- Remove sessions from protected tables (true nuclear option)
- Rename protectedTables to trackingTables for clarity
- Clear seed records (cake_seeds) along with migration records
- Rename clearMigrationRecords to clearTrackingRecords

* Simplify reset command to drop all tables

Instead of preserving tracking tables and clearing their records,
just drop everything and let migrations recreate the tables.

* Use instanceof for driver type checking and extract helper method

- Replace string operations on class names with instanceof checks
- Extract runMigrationsAndDispatch() to reduce code duplication

* Move FK constraint handling to adapter layer

- Add disableForeignKeyConstraints/enableForeignKeyConstraints to AdapterInterface
- Implement FK methods in MysqlAdapter (SET FOREIGN_KEY_CHECKS)
- Implement FK methods in SqliteAdapter (PRAGMA foreign_keys)
- Implement FK methods in SqlserverAdapter (drop all FK constraints)
- PostgresAdapter uses no-op methods with CASCADE in dropTable
- Add FK methods to AdapterWrapper for delegation
- Refactor ResetCommand to use adapter methods instead of dialect-specific code

* Add FK constraint methods to test trait
When using the unified `cake_migrations` table, `BakeMigrationDiffCommand::checkSync()`
was comparing the last migrated version (from ALL contexts including plugins) against
the last migration file in the current context (app or specific plugin).

This caused false "not in sync" errors when a plugin had more recent migrations
than the app being baked.

The fix filters migrated versions to only consider those that have corresponding
files in the current context before comparing.

Fixes #1060
* initial vitepress conversion

* clean up version switcher

* add Dockerfile for docs build step

* docs: Add anonymous migration classes documentation (#1056)

Document the --style anonymous option for bake migration including:
- Benefits of anonymous migration classes
- Command usage example
- Generated code structure
- Global Migrations.style configuration option

* Fix prev verion urls
Use shorthand npm package syntax

* Don't run CI on doc changes

* adjust docs deploy

---------

Co-authored-by: Kevin Pfeifer <kevin.pfeifer@sunlime.at>
Co-authored-by: Mark Scherer <dereuromark@users.noreply.github.com>
# Conflicts:
#	.github/workflows/ci.yml
#	docs/en/writing-migrations.rst
dereuromark and others added 28 commits May 13, 2026 16:46
The MySQL adapter built `DROP FOREIGN KEY <name>` without quoting the
constraint identifier. That produces invalid SQL whenever the constraint
name is not a bare identifier, e.g. names containing whitespace, or the
numeric names ("1", "2", ...) that MariaDB 12 auto-assigns to foreign
keys created without an explicit name.

Quote the identifier via quoteColumnName(), matching the Postgres and
SQL Server adapters, which already quote the dropped constraint name.
* update stan

* fix rector issues
Avoids duplicating version support info that is already
maintained in the wiki, reducing maintenance burden.
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 6 to 7.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](codecov/codecov-action@v6...v7)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [actions/checkout](https://github.com/actions/checkout) from 6 to 7.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](actions/checkout@v6...v7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [actions/cache](https://github.com/actions/cache) from 5 to 6.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](actions/cache@v5...v6)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…#1095)

Baking a migration with multiple integer primary key columns, e.g. a join
table:

    bin/cake bake migration CreateArticlesTags article_id:integer:primary tag_id:integer:primary

generated `autoIncrement => true` on every integer primary key column. MySQL
then rejects the table:

    SQLSTATE[42000]: Syntax error or access violation: 1075 Incorrect table
    definition; there can be only one auto column and it must be defined as a key

By convention only a single integer primary key auto-increments; a composite
primary key (typical for join tables) must not. ColumnParser now only emits
autoIncrement when there is exactly one primary key column.
`AddCheckConstraint::build()` defaults a missing name to an empty string,
but the adapters only auto-generated a name when it was `null`. Since
`Cake\Database\Schema\Constraint` types the name as a non-nullable string,
`getName()` never returns `null`, so the auto-generation branch was dead
code and an unnamed check constraint produced invalid SQL, e.g.
`ADD CONSTRAINT  CHECK (...)` which PostgreSQL rejects with
`SQLSTATE[42601]: Syntax error at or near "CHECK"`.

Treat an empty string the same as `null` in the Postgres, MySQL and SQLite
adapters so a name is generated. Add regression tests for the empty-name
path and tighten the MySQL/MariaDB expectation now that migrations supplies
the generated name explicitly instead of relying on the database fallback.
The adapter was only set on the migration/seed by the Environment, right
before running the migration body - after the shouldExecute() gate in the
Manager. As a result shouldExecute() could not query the database (calling
getAdapter() threw "Adapter not set."), so eligibility could only depend on
local application state, not the state of the database.

Set the environment's adapter on the migration and seed before the
shouldExecute() check. getEnvironment()->getAdapter() is memoized, so this
adds no extra connection, and the Environment still (re)sets and wraps the
adapter before executing the body, leaving migrate/rollback behavior
unchanged.
#1086)

* Add ReversibleMigrationInterface and DirectionalMigrationInterface

Introduce two marker interfaces so migrations can declare their style
explicitly:

- ReversibleMigrationInterface for migrations defining a single change()
  method (handled with the recording adapter for the down direction).
- DirectionalMigrationInterface for migrations defining separate up() and
  down() methods.

Environment now dispatches via instanceof first and keeps the existing
method_exists fallback for migrations that have not adopted either
interface yet, so the change is backwards compatible. The interfaces
declare their method contracts via PHPDoc method tags only (not as real
abstract methods), so adopting them is a single-line implements addition
with no signature validation pressure.

The 6.x release is expected to promote the PHPDoc method tags to real
abstract method declarations and drop the method_exists fallback.

* Emit capability interface in bake templates

Bake-generated migrations now declare implements ReversibleMigrationInterface
(change-style) or implements DirectionalMigrationInterface (up/down-style)
out of the box.

Updated templates:

- skeleton.twig (reversible)
- skeleton-anonymous.twig (reversible)
- diff.twig (directional)
- snapshot.twig (reversible or directional based on useChange)

All bake comparison fixtures under tests/comparisons/ are updated to match
the new output so the bake tests stay green.

* Add AddMigrationCapabilityInterfaceRector

Ship a rector rule that retrofits the capability interfaces onto existing
migrations during the 5.x upgrade. For every class that extends BaseMigration
(directly or transitively):

- If the class defines change(), add implements ReversibleMigrationInterface.
- If the class defines up() or down(), add implements DirectionalMigrationInterface.
- Classes already implementing either interface are skipped.
- Classes defining both styles are left alone, since the choice is a deliberate
  one.

Abstract migration bases and anonymous migration classes are both supported
so the interface propagates through inheritance and through bake's anonymous
migration shape.

* Document capability interfaces and the upgrade path

Add a dedicated upgrade guide at docs/en/upgrades/upgrading-to-capability-interfaces.md
covering motivation, per-app before/after examples for both styles, the
rector-driven automatic upgrade, the manual residuals (Phinx-style bases,
dynamically generated classes, third-party plugins), and the 6.x forward
direction.

Wire the new page into the VitePress sidebar (toc_en.json) and update the
Migration Methods guide so the inline examples already include the
implements clause and link to the upgrade guide.

* Exclude src/Rector/ from PHPStan analysis

The rector rule extends rector/rector base classes which are installed
on-demand through the rector-setup composer script rather than as a
permanent dev dependency. PHPStan runs before that script in CI and
therefore cannot resolve AbstractRector or the Symplify value objects.
The rule is type-checked by rector itself when invoked, and downstream
apps that wire it into their own rector.php have rector installed
locally, so excluding the directory here is the minimal fix that keeps
the existing on-demand dependency pattern intact.

* Update docs/en/upgrades/upgrading-to-capability-interfaces.md

Co-authored-by: Mark Story <mark@mark-story.com>

* Match parent visibility of buildOptionParser()

The buildOptionParser() overrides in BakeMigrationCommand and the
CustomBakeMigrationDiffCommand test command were public, while the parent
Command::buildOptionParser() and every other command in the package declare
it protected. Rector's MakeInheritedMethodVisibilitySameAsParentRule flagged
the mismatch, failing the cs-stan CI job. Align both to protected.

* Fix capability-interface Rector rule edge cases

Two correctness gaps in AddMigrationCapabilityInterfaceRector surfaced in review:

- The rule only checked the target interface, so a class already implementing
  DirectionalMigrationInterface that also defined change() had
  ReversibleMigrationInterface added on top, producing a class implementing both
  mutually exclusive interfaces. Now skip any class already implementing either
  capability interface, keeping the rule idempotent and honoring the documented
  "skip if already annotated" behavior.

- Directional adoption used "up() OR down()", so a one-way migration (only up()
  or only down()) had DirectionalMigrationInterface added. Environment calls the
  matching direction unconditionally for interface-based migrations, so rolling
  back such a migration turned a previous method_exists() no-op into a fatal
  undefined-method error. Only adopt the directional interface when both up() and
  down() exist; one-way migrations stay on the runtime fallback. Mixed-style
  detection (change() plus any directional method) is preserved.

Docblock and the upgrade guide updated to match.

---------

Co-authored-by: Mark Story <mark@mark-story.com>
CakePHP reflects `ON UPDATE CURRENT_TIMESTAMP` on datetime/timestamp
columns as an `onUpdate` column attribute, but MigrationHelper dropped
it in two places: the valid-options list in attributes() and the wanted-
options list in getColumnOption(). The clause never reached the template,
so any column declaring it lost it when snapshotted.

Databases rebuilt from a snapshot then silently stopped auto-updating
those columns on writes that bypass the ORM, while existing databases
kept the real schema.

Let the attribute through both filters and translate it to Phinx's
`update` option, mirroring the existing collate/collation handling.
Both filters drop the attribute when empty, since Column::toArray()
always emits an onUpdate key and Column::setUpdate() is not nullable.

* Cover the attributes() half of the ON UPDATE fix

The two existing tests fed getColumnOption() a hand-built array, which
bypasses attributes() entirely. Removing 'onUpdate' from the valid-options
list in attributes() therefore broke snapshot output with zero test
failures, even though it is one of the two changes the fix depends on.

Add a test that drives attributes() directly and one that walks the whole
snapshot path (columns() -> getColumnOption()). Both build the schema by
hand rather than reflecting it, so they run on every driver.

* Add snapshot regression test for ON UPDATE

The MigrationHelper tests build their schema by hand, so they prove the
helper emits the option but not that a baked snapshot keeps it. Cover the
end-to-end path: reflect a real ON UPDATE column, bake a snapshot, and
compare the generated migration against a checked-in file.

Gated on MySQL because only MysqlSchemaDialect reflects onUpdate; the
other drivers never produce the attribute. Mirrors the existing
non-default-collation test, which faces the same constraint.

The comparison file differs from the plain snapshot by a single
'update' => 'CURRENT_TIMESTAMP' line on users.updated, while users.created
stays bare, so the test also pins down that the option is not applied to
columns that never declared it.
EventDispatcherTrait is no longer generic, so the use tag annotations are
reported as errors by PHPStan.

Unknown subcommands are rejected before they reach EntryCommand, so the error
message differs between 5.3 and 5.4. Only the parts both messages have in
common are asserted.
Seed classes are not namespaced, so deriving the plugin from the class name
never matched and plugin seeds were logged with a null plugin. The plugin of
the current run is used instead, matching how migrations are tracked.

Because of the null plugin, plugin seeds were also never detected as executed,
so they ran again on every seeds run and were skipped by seeds reset.

Seed log entries written before this fix are still matched for plugin seeds so
that they are not executed a second time.

* Use instanceof check for the seed config

* Assert command output in plugin seed status and reset tests
This improves the output of `cake` with no parameters.
Bumps [postcss](https://github.com/postcss/postcss) from 8.5.16 to 8.5.25.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](postcss/postcss@8.5.16...8.5.25)

---
updated-dependencies:
- dependency-name: postcss
  dependency-version: 8.5.25
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Pin the image revision used for dokku deployments and reduce permissions
for deploy jobs. Pinning the image revision reduces the chances of
supply chain attacks on images with access to real credentials.
Migrator::runMany() decided whether to drop tables once per migration
set. A migration history is keyed by connection and plugin only, never
by source, so two sets differing only in their source write to one
history while each of them sees just its own directory on disk. Every
set therefore reported its siblings' applied migrations as missing, and
the connection was wiped on every run even when the database was
already up to date.

Sets are now grouped by the history they share, and a logged migration
found on disk in any set of the group no longer counts as missing.

testRunManyMultipleSkip relied on that spurious drop to trigger its
failure, so it now forgets an applied migration to give the second run
a genuine reason to drop. It no longer has to be skipped when the
unified table is in use.
Co-authored-by: Kevin Pfeifer <kevin@KP-MBP16M4-2025.local>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@josbeir josbeir closed this Oct 7, 2026
@josbeir
josbeir deleted the sync/6.x-5.next branch October 7, 2026 07:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants