kinetis / migrations
A thin database migration runner and migrate commands for Kinetis — raw SQL up()/down() migrations for the default connection and named ones, no fluent DDL builder, no schema-diffing.
Requires
- php: ^8.4
- kinetis/database-bridge: ^1.3.1
- kinetis/framework: ^1.19.1
- kinetis/persistence: ^1.4.3
Requires (Dev)
- infection/infection: ^0.35.0
- phpstan/phpstan: ^2.2.8
- phpunit/phpunit: ^13.3.3
- vimeo/psalm: ^6.17
Suggests
- ext-pdo_mysql: Required by the migrate* commands when DB_CONNECTION=mysql; migrations always hold one PDO session, whatever DB_DRIVER says.
- ext-pdo_pgsql: Required by the migrate* commands when DB_CONNECTION=pgsql; migrations always hold one PDO session, whatever DB_DRIVER says.
Provides
None
Conflicts
None
Replaces
None
README
kinetis/migrations
A thin database migration runner for Kinetis
Part of Kinetis, a non-blocking PHP framework for API-first applications, developed in the kinetis-dev/kinetis monorepo.
Raw SQL up()/down() migrations, tracked in a kinetis_migrations
table in each database, run through migrate* commands registered on
vendor/bin/kinetis. No fluent
DDL builder, no schema-diffing — the same "thin, not an ORM" shape as
kinetis/query-builder.
// migrations/20260810143000_create_orders_table.php use Kinetis\Persistence\Contract\MysqlLink; use Kinetis\Persistence\Contract\PostgresLink; use Kinetis\Migrations\Migration; return new class implements Migration { public function up(MysqlLink|PostgresLink $db): void { $db->execute(<<<'SQL' CREATE TABLE orders ( id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, customer_id BIGINT UNSIGNED NOT NULL, status VARCHAR(20) NOT NULL DEFAULT 'pending', created_at DATETIME NOT NULL ) SQL); } public function down(MysqlLink|PostgresLink $db): void { $db->execute('DROP TABLE orders'); } };
vendor/bin/kinetis migrate # runs every pending migration vendor/bin/kinetis migrate:rollback # rolls back the migration applied most recently vendor/bin/kinetis migrate:status # lists applied/pending migrations vendor/bin/kinetis migrate:make "create orders" # scaffolds a migration file
The files directly in migrations/ belong to the default connection, and
each directory directly inside it, such as migrations/reporting/, to the
named connection of that name, read from its DB_REPORTING_* keys.
migrate and migrate:status cover every connection, default first,
one database at a time and stopping at the first failure; with
connection directories present they print Connection: <name> before
each connection's lines. --connection=<name> narrows a command to one
connection, and migrate:rollback requires it once connection
directories exist. Give each connection its own database. See
Several databases
for the naming rules, the preflight check and the failure semantics.
The ledger holds one row per applied migration: its name, the SHA-256 of
the file that ran, and the order this database applied it in. Every
command verifies that against the connection's directory first — an
applied migration whose file is gone, moved to another connection's
directory, or whose contents no longer hash to what was recorded, throws
Exception\MigrationIntegrityException before any up(), down() or
ledger write, and restoring the deployed file is what clears it.
Provides
Installing this package is what opts it in — it registers the
following automatically, through the extra.kinetis declaration in its
composer.json (see
kinetis.dev/docs/cli.html):
- Commands:
migrate,migrate:rollback,migrate:status, andmigrate:makeonvendor/bin/kinetis. All four run without the application's bootstrap (bootstrap: false) — they readDB_*directly, throughkinetis/database-bridge's connection policy, so they work in bare contexts (CI, an init container) with nothing but environment variables. A run holds one PDO session, whateverDB_DRIVERsays, because its advisory lock lives in that session. - Events:
Kinetis\Migrations\Events\MigrationAppliedandMigrationRolledBack, dispatched once per migrationmigrate/migrate:rollbackactually runs, each with the migration'snameand theconnectionit ran on. See kinetis.dev/docs/events.html for the full list across every package.
That's the entire extra.kinetis surface — no service bindings,
routes, middleware, event listeners it registers itself, or MCP tools.
Configuration
The migrate* commands read the same DB_* keys
kinetis/database-bridge
documents (DB_CONNECTION/DB_HOST/DB_NAME/DB_USER/DB_PASSWORD/
DB_PORT, ...) from the environment or .env, plus one key of this
package's own:
| Key | Default | Purpose |
|---|---|---|
MIGRATE_CONNECTION_NAME |
— | When non-empty, narrows migrate, migrate:status and migrate:rollback to that one connection; the --connection=<name> flag wins over it. Unset, migrate and migrate:status cover every connection. |
Full reference across every package: kinetis.dev/docs/config.html.
Installation
composer require kinetis/migrations
Requires PHP 8.4+, kinetis/framework,
kinetis/persistence, and
kinetis/database-bridge.
The migrate* commands always hold one PDO session for their advisory
lock, whatever DB_DRIVER says. Install the matching PDO driver as well:
ext-pdo_mysql for DB_CONNECTION=mysql, or ext-pdo_pgsql for
DB_CONNECTION=pgsql. A worker application using the native driver still
needs that PDO extension for migrations.
For example, a PostgreSQL image whose request path uses native ext-pgsql
needs both extensions:
RUN docker-php-ext-install pgsql pdo_pgsql
Full documentation: kinetis.dev/docs/migrations.html.
License
MIT — see LICENSE.