From 73b4bb9c91bdc21152ca279a186c4dd000c42a55 Mon Sep 17 00:00:00 2001 From: Kevin Pfeifer Date: Fri, 17 Apr 2026 13:37:46 +0200 Subject: [PATCH] rework docs to have more smaller pages --- docs/.vitepress/toc_en.json | 36 +- .../en/advanced/integration-and-deployment.md | 229 ++ docs/en/contents.md | 8 - .../en/getting-started/creating-migrations.md | 314 ++ .../installation-and-overview.md | 101 + .../running-and-managing-migrations.md | 135 + .../en/getting-started/snapshots-and-diffs.md | 116 + docs/en/{ => guides}/executing-queries.md | 57 - docs/en/guides/inserting-data.md | 56 + docs/en/{ => guides}/seeding.md | 0 .../{ => guides}/using-the-query-builder.md | 0 .../columns-and-table-operations.md | 388 +++ docs/en/guides/writing-migrations/index.md | 21 + .../indexes-and-constraints.md | 148 + .../writing-migrations/migration-methods.md | 135 + ...-introspection-and-platform-limitations.md | 105 + docs/en/index.md | 859 +----- docs/en/index.rst | 955 ------- docs/en/seeding.rst | 616 ---- .../upgrading-from-4-x.md} | 4 +- .../upgrading-to-builtin-backend.md | 0 docs/en/upgrading.rst | 174 -- docs/en/writing-migrations.md | 2527 ----------------- docs/en/writing-migrations.rst | 0 24 files changed, 1846 insertions(+), 5138 deletions(-) create mode 100644 docs/en/advanced/integration-and-deployment.md delete mode 100644 docs/en/contents.md create mode 100644 docs/en/getting-started/creating-migrations.md create mode 100644 docs/en/getting-started/installation-and-overview.md create mode 100644 docs/en/getting-started/running-and-managing-migrations.md create mode 100644 docs/en/getting-started/snapshots-and-diffs.md rename docs/en/{ => guides}/executing-queries.md (60%) create mode 100644 docs/en/guides/inserting-data.md rename docs/en/{ => guides}/seeding.md (100%) rename docs/en/{ => guides}/using-the-query-builder.md (100%) create mode 100644 docs/en/guides/writing-migrations/columns-and-table-operations.md create mode 100644 docs/en/guides/writing-migrations/index.md create mode 100644 docs/en/guides/writing-migrations/indexes-and-constraints.md create mode 100644 docs/en/guides/writing-migrations/migration-methods.md create mode 100644 docs/en/guides/writing-migrations/schema-introspection-and-platform-limitations.md delete mode 100644 docs/en/index.rst delete mode 100644 docs/en/seeding.rst rename docs/en/{upgrading.md => upgrades/upgrading-from-4-x.md} (97%) rename docs/en/{ => upgrades}/upgrading-to-builtin-backend.md (100%) delete mode 100644 docs/en/upgrading.rst delete mode 100644 docs/en/writing-migrations.md delete mode 100644 docs/en/writing-migrations.rst diff --git a/docs/.vitepress/toc_en.json b/docs/.vitepress/toc_en.json index 788a3039e..a50f01a4e 100644 --- a/docs/.vitepress/toc_en.json +++ b/docs/.vitepress/toc_en.json @@ -1,21 +1,43 @@ { "/": [ { - "text": "Migrations", + "text": "Getting Started", "collapsed": false, "items": [ - { "text": "Writing Migrations", "link": "/writing-migrations" }, - { "text": "Using the Query Builder", "link": "/using-the-query-builder" }, - { "text": "Executing Queries", "link": "/executing-queries" }, - { "text": "Database Seeding", "link": "/seeding" } + { "text": "Migrations 5.x", "link": "/" }, + { "text": "Installation and Overview", "link": "/getting-started/installation-and-overview" }, + { "text": "Creating Migrations", "link": "/getting-started/creating-migrations" }, + { "text": "Snapshots and Diffs", "link": "/getting-started/snapshots-and-diffs" }, + { "text": "Running and Managing Migrations", "link": "/getting-started/running-and-managing-migrations" } + ] + }, + { + "text": "Guides", + "collapsed": false, + "items": [ + { "text": "Integration and Deployment", "link": "/advanced/integration-and-deployment" }, + { + "text": "Writing Migrations", + "link": "/guides/writing-migrations/migration-methods", + "items": [ + { "text": "Migration Methods", "link": "/guides/writing-migrations/migration-methods" }, + { "text": "Columns and Table Operations", "link": "/guides/writing-migrations/columns-and-table-operations" }, + { "text": "Indexes and Constraints", "link": "/guides/writing-migrations/indexes-and-constraints" }, + { "text": "Schema Introspection and Platform Limitations", "link": "/guides/writing-migrations/schema-introspection-and-platform-limitations" } + ] + }, + { "text": "Using the Query Builder", "link": "/guides/using-the-query-builder" }, + { "text": "Executing Queries", "link": "/guides/executing-queries" }, + { "text": "Inserting Data", "link": "/guides/inserting-data" }, + { "text": "Database Seeding", "link": "/guides/seeding" } ] }, { "text": "Upgrade Guides", "collapsed": true, "items": [ - { "text": "Upgrading from 4.x to 5.x", "link": "/upgrading" }, - { "text": "Upgrading to the Builtin Backend", "link": "/upgrading-to-builtin-backend" } + { "text": "Upgrading from 4.x to 5.x", "link": "/upgrades/upgrading-from-4-x" }, + { "text": "Upgrading to the Builtin Backend", "link": "/upgrades/upgrading-to-builtin-backend" } ] } ] diff --git a/docs/en/advanced/integration-and-deployment.md b/docs/en/advanced/integration-and-deployment.md new file mode 100644 index 000000000..35be96e63 --- /dev/null +++ b/docs/en/advanced/integration-and-deployment.md @@ -0,0 +1,229 @@ +# Integration and Deployment + +This guide covers test integration, plugin usage, programmatic execution, and +deployment-related operational concerns. + +## Using Migrations for Tests + +If you are using migrations for your application schema, you can also use those +same migrations to build schema in your tests. In your application's +`tests/bootstrap.php` file you can use the `Migrator` class to build schema +when tests are run. The `Migrator` will use existing schema if it is current, +and if the migration history in the database differs from what is in the +filesystem, all tables will be dropped and migrations will be rerun from the +beginning: + +```php +// in tests/bootstrap.php +use Migrations\TestSuite\Migrator; + +$migrator = new Migrator(); + +// Simple setup with no plugins +$migrator->run(); + +// Run a non-test database +$migrator->run(['connection' => 'test_other']); + +// Run migrations for plugins +$migrator->run(['plugin' => 'Contacts']); + +// Run the Documents migrations on the test_docs connection +$migrator->run(['plugin' => 'Documents', 'connection' => 'test_docs']); +``` + +If you need to run multiple sets of migrations, use `runMany()`: + +```php +$migrator->runMany([ + ['plugin' => 'Contacts'], + ['plugin' => 'Documents', 'connection' => 'test_docs'], +]); +``` + +If your database also contains tables that are not managed by your application, +such as those created by PostGIS, you can exclude those tables from drop and +truncate behavior using the `skip` option: + +```php +$migrator->run(['connection' => 'test', 'skip' => ['postgis*']]); +``` + +The `skip` option accepts an `fnmatch()` compatible pattern. + +## Using Migrations in Plugins + +Plugins can also provide migration files. All commands in the Migrations plugin +support the `--plugin` or `-p` option to scope execution to the migrations +relative to that plugin: + +```bash +bin/cake migrations status -p PluginName +bin/cake migrations migrate -p PluginName +``` + +## Running Migrations in a Non-shell Environment + +While typical usage of migrations is from the command line, you can also run +migrations from a non-shell environment by using the `Migrations\Migrations` +class. This can be handy when you are developing a plugin installer for a CMS, +for instance. + +The `Migrations` class allows you to run the following commands: + +- `migrate` +- `rollback` +- `markMigrated` +- `status` +- `seed` + +Each of these commands has a corresponding method defined in the +`Migrations\Migrations` class. + +```php +use Migrations\Migrations; + +$migrations = new Migrations(); + +$status = $migrations->status(); +$migrate = $migrations->migrate(); +$rollback = $migrations->rollback(); +$markMigrated = $migrations->markMigrated(20150804222900); +$seeded = $migrations->seed(); +``` + +The methods can accept an array of parameters that match the shell command +options: + +```php +use Migrations\Migrations; + +$migrations = new Migrations(); +$status = $migrations->status([ + 'connection' => 'custom', + 'source' => 'MyMigrationsFolder', +]); +``` + +The only exception is `markMigrated()`, which expects the version number as the +first argument and the options array as the second. + +Optionally, you can pass default parameters in the constructor: + +```php +use Migrations\Migrations; + +$migrations = new Migrations([ + 'connection' => 'custom', + 'source' => 'MyMigrationsFolder', +]); + +$status = $migrations->status(); +$migrate = $migrations->migrate(); +``` + +If you need to override one or more default parameters for one call, pass them +to the method: + +```php +use Migrations\Migrations; + +$migrations = new Migrations([ + 'connection' => 'custom', + 'source' => 'MyMigrationsFolder', +]); + +$status = $migrations->status(); +$migrate = $migrations->migrate(['connection' => 'default']); +``` + +## Feature Flags + +Migrations offers a few feature flags for compatibility. These features are +disabled by default but can be enabled if required: + +- `unsigned_primary_keys`: Should Migrations create primary keys as unsigned + integers? Default: `false` +- `unsigned_ints`: Should Migrations create all integer columns as unsigned? + Default: `false` +- `column_null_default`: Should Migrations create columns as nullable by + default? Default: `false` +- `add_timestamps_use_datetime`: Should Migrations use `DATETIME` type columns + for the columns added by `addTimestamps()`? + +Set them via `Configure`, for example in `config/app.php`: + +```text +'Migrations' => [ + 'unsigned_primary_keys' => true, + 'unsigned_ints' => true, + 'column_null_default' => true, +], +``` + +> [!NOTE] +> The `unsigned_primary_keys` and `unsigned_ints` options only affect MySQL +> databases. When generating migrations with `bake migration_snapshot` or +> `bake migration_diff`, the `signed` attribute will only be included in the +> output for unsigned columns as `'signed' => false`. + +## Skipping the `schema.lock` File Generation + +In order for the diff feature to work, a `.lock` file is generated every time +you migrate, roll back, or bake a snapshot, to keep track of the state of your +database schema at any given point in time. You can skip this file generation, +for instance when deploying to production, by using the `--no-lock` option: + +```bash +bin/cake migrations migrate --no-lock +bin/cake migrations rollback --no-lock +bin/cake bake migration_snapshot MyMigration --no-lock +``` + +## Deployment + +You should update your deployment scripts to run migrations when new code is +deployed. Ideally, run migrations after the code is on your servers, but before +the application code becomes active. + +After running migrations, remember to clear the ORM cache so it renews the +column metadata of your tables. Otherwise, you might end up with errors about +columns not existing when performing operations on those new columns. The +CakePHP core includes a [Schema Cache Shell](https://book.cakephp.org/5/en/console-and-shells/schema-cache.html) that you can use: + +```bash +bin/cake migration migrate +bin/cake schema_cache clear +``` + +## Alert of Missing Migrations + +You can use the `Migrations.PendingMigrations` middleware in local development +to alert developers about new migrations that have not been applied: + +```php +use Migrations\Middleware\PendingMigrationsMiddleware; + +$config = [ + 'plugins' => [ + // Optionally include a list of plugins with migrations to check. + ], +]; + +$middlewareQueue + // ErrorHandler middleware + ->add(new PendingMigrationsMiddleware($config)); +``` + +You can add the `'app'` config key set to `false` if you are only interested in +checking plugin migrations. + +You can temporarily disable the migration check by adding +`skip-migration-check=1` to the URL query string. + +## IDE Autocomplete Support + +The [IdeHelper plugin](https://github.com/dereuromark/cakephp-ide-helper) can +help you get more IDE support for tables, their column names, and possible +column types. Specifically, PHPStorm understands the meta information and can +help you autocomplete those. diff --git a/docs/en/contents.md b/docs/en/contents.md deleted file mode 100644 index 18fe41d4c..000000000 --- a/docs/en/contents.md +++ /dev/null @@ -1,8 +0,0 @@ -### CakePHP Migrations - -- [Migrations 5.x](index) -- [Writing Migrations](writing-migrations) -- [Using the Query Builder](using-the-query-builder) -- [Executing Queries](executing-queries) -- [Database Seeding](seeding) -- [Upgrading to the builtin backend](upgrading-to-builtin-backend) diff --git a/docs/en/getting-started/creating-migrations.md b/docs/en/getting-started/creating-migrations.md new file mode 100644 index 000000000..f99f26087 --- /dev/null +++ b/docs/en/getting-started/creating-migrations.md @@ -0,0 +1,314 @@ +# Creating Migrations + +Migration files are stored in the `config/Migrations` directory of your +application. The names of the migration files are prefixed with the date in +which they were created, in the format +`YYYYMMDDHHMMSS_MigrationName.php`. + +Examples: + +- `20160121163850_CreateProducts.php` +- `20160210133047_AddRatingToProducts.php` + +The easiest way to create a migration file is by using `bin/cake bake migration`: + +```bash +bin/cake bake migration CreateProducts +``` + +This creates an empty migration that you can edit to add any columns, indexes, +and foreign keys you need. See [Writing Migrations](../guides/writing-migrations) +for more information on using `Table` objects to define schema changes. + +> [!NOTE] +> Migrations need to be applied using `bin/cake migrations migrate` after they +> have been created. + +## Migration File Names + +When generating a migration, you can follow one of the following patterns to +have additional skeleton code generated: + +- `/^(Create)(.*)/` Creates the specified table. +- `/^(Drop)(.*)/` Drops the specified table and ignores specified field arguments. +- `/^(Add).*(?:To)(.*)/` Adds fields to the specified table. +- `/^(Remove).*(?:From)(.*)/` Removes fields from the specified table. +- `/^(Alter)(.*)/` Alters the specified table. An alias for create-table and + add-field generation. +- `/^(Alter).*(?:On)(.*)/` Alters fields from the specified table. + +You can also use the `underscore_form` as the name for your migrations, such as +`create_products`. + +> [!WARNING] +> Migration names are used as class names, and thus may collide with other +> migrations if the class names are not unique. In that case, you may need to +> rename the migration manually. + +## Creating a Table + +You can use `bake migration` to create a table: + +```bash +bin/cake bake migration CreateProducts name:string description:text created modified +``` + +The command above will generate a migration file that resembles: + +```php +table('products'); + $table->addColumn('name', 'string', [ + 'default' => null, + 'limit' => 255, + 'null' => false, + ]); + $table->addColumn('description', 'text', [ + 'default' => null, + 'null' => false, + ]); + $table->addColumn('created', 'datetime', [ + 'default' => null, + 'null' => false, + ]); + $table->addColumn('modified', 'datetime', [ + 'default' => null, + 'null' => false, + ]); + $table->create(); + } +} +``` + +## Column Syntax + +The `bake migration` command provides a compact syntax to define columns when +generating a migration: + +```bash +bin/cake bake migration CreateProducts name:string description:text created modified +``` + +You can use the column syntax when creating tables and adding columns. You can +also edit the generated migration afterwards to customize the columns further. + +Columns on the command line follow this pattern: + +```text +fieldName:fieldType?[length]:default[value]:indexType:indexName +``` + +Examples of valid email field definitions: + +- `email:string?` +- `email:string:unique` +- `email:string?[50]` +- `email:string:unique:EMAIL_INDEX` +- `email:string[120]:unique:EMAIL_INDEX` + +While defining decimal columns, the `length` can include precision and scale: + +- `amount:decimal[5,2]` +- `amount:decimal?[5,2]` + +Columns with a question mark after the field type are nullable. + +The `length` part is optional and should always be written between brackets. + +The `default[value]` part is optional and sets the default value for the +column. Supported value types include: + +- Booleans: `true` or `false` such as `active:boolean:default[true]` +- Integers: `0`, `123`, `-456` such as `count:integer:default[0]` +- Floats: `1.5`, `-2.75` such as `rate:decimal:default[1.5]` +- Strings: `'hello'` or `"world"` such as + `status:string:default['pending']` +- Null: `null` or `NULL` such as `description:text?:default[null]` +- SQL expressions: `CURRENT_TIMESTAMP` such as + `created_at:datetime:default[CURRENT_TIMESTAMP]` + +Fields named `created` and `modified`, as well as any field with an `_at` +suffix, will automatically be set to the type `datetime`. + +There are some heuristics for choosing field types when left unspecified or set +to an invalid value. The default field type is `string`: + +- `id`: `integer` +- `created`, `modified`, `updated`: `datetime` +- `latitude`, `longitude`, `lat`, `lng`: `decimal` + +Additionally, you can create an empty migration file if you want full control +over what needs to be executed by omitting column definitions: + +```bash +bin/cake migrations create MyCustomMigration +``` + +## Adding Columns to an Existing Table + +If the migration name is of the form `AddXXXToYYY` and is followed by a list of +column names and types, a migration file containing the code for creating the +columns will be generated: + +```bash +bin/cake bake migration AddPriceToProducts price:decimal[5,2] +``` + +This generates: + +```php +table('products'); + $table->addColumn('price', 'decimal', [ + 'default' => null, + 'null' => false, + 'precision' => 5, + 'scale' => 2, + ]); + $table->update(); + } +} +``` + +## Adding a Column with an Index + +It is also possible to add indexes to columns: + +```bash +bin/cake bake migration AddNameIndexToProducts name:string:index +``` + +This will generate: + +```php +table('products'); + $table->addColumn('name', 'string') + ->addColumn('email', 'string') + ->addIndex(['name']) + // add a unique index: + ->addIndex('email', ['unique' => true]) + ->update(); + } +} +``` + +## Adding a Column with a Default Value + +You can specify default values for columns using the `default[value]` syntax: + +```bash +bin/cake bake migration AddActiveToUsers active:boolean:default[true] +``` + +This will generate: + +```php +table('users'); + $table->addColumn('active', 'boolean', [ + 'default' => true, + 'null' => false, + ]); + $table->update(); + } +} +``` + +You can combine default values with other options like nullable and indexes: + +```bash +bin/cake bake migration AddStatusToOrders status:string:default['pending']:unique +``` + +## Altering a Column + +In the same way, you can generate a migration to alter a column if the +migration name is of the form `AlterXXXOnYYY`: + +```bash +bin/cake bake migration AlterPriceOnProducts name:float +``` + +This will generate: + +```php +table('products'); + $table->changeColumn('name', 'float'); + $table->update(); + } +} +``` + +> [!WARNING] +> Changing the type of a column can result in data loss if the current and +> target column type are not compatible. For example, converting a varchar to a +> float. + +## Removing a Column + +In the same way, you can generate a migration to remove a column if the +migration name is of the form `RemoveXXXFromYYY`: + +```bash +bin/cake bake migration RemovePriceFromProducts price +``` + +This creates: + +```php +table('products'); + $table->removeColumn('price') + ->save(); + } +} +``` + +> [!NOTE] +> `removeColumn()` is not reversible, so it must be called in the `up()` +> method. Add a corresponding `addColumn()` call to the `down()` method. diff --git a/docs/en/getting-started/installation-and-overview.md b/docs/en/getting-started/installation-and-overview.md new file mode 100644 index 000000000..940a197ac --- /dev/null +++ b/docs/en/getting-started/installation-and-overview.md @@ -0,0 +1,101 @@ +# Installation and Overview + +This guide covers installation, plugin loading, and the basic migration model +used by the Migrations plugin. + +## Installation + +By default Migrations is installed with the application skeleton. If you've +removed it and want to re-install it, run the following from your application's +root directory: + +```bash +php composer.phar require cakephp/migrations "@stable" + +# Or if composer is installed globally +composer require cakephp/migrations "@stable" +``` + +To use the plugin, load it in your application's `config/bootstrap.php` file: + +```bash +bin/cake plugin load Migrations +``` + +Or load the plugin in `src/Application.php`: + +```php +$this->addPlugin('Migrations'); +``` + +Additionally, configure the default database connection in `config/app.php` as +explained in the [Database Configuration section](https://book.cakephp.org/5/en/orm/database-basics.html#database-configuration). + +## Overview + +A migration is a PHP file that describes the changes to apply to your database. +A migration file can add, change, or remove tables, columns, indexes, and +foreign keys. + +If we wanted to create a table, we could use a migration similar to this: + +```php +table('products'); + $table->addColumn('name', 'string', [ + 'default' => null, + 'limit' => 255, + 'null' => false, + ]); + $table->addColumn('description', 'text', [ + 'default' => null, + 'null' => false, + ]); + $table->addColumn('created', 'datetime', [ + 'default' => null, + 'null' => false, + ]); + $table->addColumn('modified', 'datetime', [ + 'default' => null, + 'null' => false, + ]); + $table->create(); + } +} +``` + +When applied, this migration will add a table to your database named +`products` with the following column definitions: + +- `id` column of type `integer` as primary key. This column is added + implicitly, but you can customize the name and type if necessary. +- `name` column of type `string` +- `description` column of type `text` +- `created` column of type `datetime` +- `modified` column of type `datetime` + +> [!NOTE] +> Migrations are not automatically applied. Use the CLI commands to apply and +> roll back migrations. + +Once the file has been created in the `config/Migrations` folder, you can apply +it: + +```bash +bin/cake migrations migrate +``` + +## Next Steps + +- Use [Creating Migrations](creating-migrations) to generate migration files + with `bake` +- Use [Running and Managing Migrations](running-and-managing-migrations) to + apply, roll back, and inspect migrations +- Use [Writing Migrations](../guides/writing-migrations) for the full Table API and + migration authoring reference diff --git a/docs/en/getting-started/running-and-managing-migrations.md b/docs/en/getting-started/running-and-managing-migrations.md new file mode 100644 index 000000000..f3c98fda8 --- /dev/null +++ b/docs/en/getting-started/running-and-managing-migrations.md @@ -0,0 +1,135 @@ +# Running and Managing Migrations + +This guide covers the commands you use after writing or generating migration +files. + +## Applying Migrations + +Once you have generated or written your migration file, apply the changes to +your database: + +```bash +# Run all the migrations +bin/cake migrations migrate + +# Migrate to a specific version using the --target option +bin/cake migrations migrate -t 20150103081132 + +# Run migrations from a custom source directory +bin/cake migrations migrate -s Alternate + +# Run migrations against a different connection +bin/cake migrations migrate -c my_custom_connection + +# Run migrations for a plugin +bin/cake migrations migrate -p MyAwesomePlugin +``` + +## Reverting Migrations + +The `rollback` command undoes previously executed migrations: + +```bash +# Roll back to the previous migration +bin/cake migrations rollback + +# Roll back to a specific version +bin/cake migrations rollback -t 20150103081132 +``` + +You can also use the `--source`, `--connection`, and `--plugin` options just +like for the `migrate` command. + +## Viewing Migration Status + +The `status` command prints a list of all migrations, along with their current +status: + +```bash +bin/cake migrations status +``` + +You can also output the results as JSON: + +```bash +bin/cake migrations status --format json +``` + +You can also use the `--source`, `--connection`, and `--plugin` options just +like for the `migrate` command. + +### Cleaning Up Missing Migrations + +Sometimes migration files may be deleted from the filesystem but still exist in +the migrations tracking table. These migrations will be marked as `MISSING` in +the status output. You can remove these entries using the `--cleanup` option: + +```bash +bin/cake migrations status --cleanup +``` + +This will remove all migration entries from the tracking table that no longer +have corresponding migration files in the filesystem. + +## Marking a Migration as Migrated + +It can sometimes be useful to mark a set of migrations as migrated without +actually running them. In order to do this, use the `mark_migrated` command. + +You can mark all migrations as migrated: + +```bash +bin/cake migrations mark_migrated +``` + +You can also mark all migrations up to a specific version using the `--target` +option: + +```bash +bin/cake migrations mark_migrated --target=20151016204000 +``` + +If you do not want the targeted migration to be marked as migrated during the +process, use the `--exclude` flag: + +```bash +bin/cake migrations mark_migrated --target=20151016204000 --exclude +``` + +If you wish to mark only the targeted migration as migrated, use the `--only` +flag: + +```bash +bin/cake migrations mark_migrated --target=20151016204000 --only +``` + +You can also use the `--source`, `--connection`, and `--plugin` options just +like for the `migrate` command. + +> [!NOTE] +> When you bake a snapshot with `cake bake migration_snapshot`, the created +> migration will automatically be marked as migrated. To prevent this behavior, +> for example for unit test migrations, use the `--generate-only` flag. + +This command also accepts the migration version number as a positional +argument: + +```bash +bin/cake migrations mark_migrated 20150420082532 +``` + +If you wish to mark all migrations as migrated, you can use the `all` special +value: + +```bash +bin/cake migrations mark_migrated all +``` + +## Seeding Your Database + +Seed classes are a good way to populate your database with default or starter +data. They are also useful for generating data for development environments. + +By default, seeds are looked for in the `config/Seeds/` directory of your +application. See [Database Seeding](../guides/seeding) for how to build and use seed +classes. diff --git a/docs/en/getting-started/snapshots-and-diffs.md b/docs/en/getting-started/snapshots-and-diffs.md new file mode 100644 index 000000000..a5b8f1d9c --- /dev/null +++ b/docs/en/getting-started/snapshots-and-diffs.md @@ -0,0 +1,116 @@ +# Snapshots and Diffs + +This guide covers bootstrapping migrations from an existing database and +generating migration diffs from schema changes. + +## Generating Migration Snapshots from an Existing Database + +If you have a pre-existing database and want to start using migrations, or want +to version-control the initial schema of your application, run +`bake migration_snapshot`: + +```bash +bin/cake bake migration_snapshot Initial +``` + +It will generate a migration file called `YYYYMMDDHHMMSS_Initial.php` +containing all the create statements for all tables in your database. + +By default, the snapshot will be created by connecting to the database defined +in the `default` connection configuration. If you need to bake a snapshot from +a different datasource, use the `--connection` option: + +```bash +bin/cake bake migration_snapshot Initial --connection my_other_connection +``` + +You can also make sure the snapshot includes only the tables for which you have +defined the corresponding model classes by using the `--require-table` flag: + +```bash +bin/cake bake migration_snapshot Initial --require-table +``` + +When using `--require-table`, the shell will look through your application's +`Table` classes and will only add the model tables to the snapshot. + +If you want to generate a snapshot without marking it as migrated, for example +for use in unit tests, use the `--generate-only` flag: + +```bash +bin/cake bake migration_snapshot Initial --generate-only +``` + +This will create the migration file but will not add an entry to the migrations +tracking table, allowing you to move the file to a different location without +causing `MISSING` status issues. + +To bake a snapshot for a plugin, use the `--plugin` option: + +```bash +bin/cake bake migration_snapshot Initial --plugin MyPlugin +``` + +Only the tables that have a `Table` object model class defined will be added to +the snapshot of your plugin. + +> [!NOTE] +> When baking a snapshot for a plugin, the migration files will be created in +> your plugin's `config/Migrations` directory. + +Be aware that when you bake a snapshot, it is automatically added to the +migrations log table as migrated unless you use `--generate-only`. + +## Generating a Diff + +As migrations are applied and rolled back, the migrations plugin will generate +a dump file of your schema. If you make manual changes to your database schema +outside of migrations, you can use `bake migration_diff` to generate a +migration file that captures the difference between the current schema dump file +and the database schema: + +```bash +bin/cake bake migration_diff NameOfTheMigrations +``` + +By default, the diff will be created by connecting to the database defined in +the `default` connection configuration. If you need to bake a diff from a +different datasource, use the `--connection` option: + +```bash +bin/cake bake migration_diff NameOfTheMigrations --connection my_other_connection +``` + +If you want to use the diff feature on an application that already has a +migrations history, you need to manually create the dump file that will be used +as comparison: + +```bash +bin/cake migrations dump +``` + +The database state must be the same as it would be if you had just migrated all +your migrations before you create a dump file. Once the dump file is generated, +you can start doing changes in your database and use +`bake migration_diff` whenever you need to capture them. + +> [!NOTE] +> Migration diff generation cannot detect column renamings. + +## Generating a Dump File + +The dump command creates a file to be used with `bake migration_diff`: + +```bash +bin/cake migrations dump +``` + +Each generated dump file is specific to the connection it is generated from, +and is suffixed as such. This allows `bake migration_diff` to properly compute +diffs when your application is dealing with multiple databases, possibly from +different vendors. + +Dump files are created in the same directory as your migration files. + +You can also use the `--source`, `--connection`, and `--plugin` options just +like for the `migrate` command. diff --git a/docs/en/executing-queries.md b/docs/en/guides/executing-queries.md similarity index 60% rename from docs/en/executing-queries.md rename to docs/en/guides/executing-queries.md index 3c6c3edd3..6f3d605ce 100644 --- a/docs/en/executing-queries.md +++ b/docs/en/guides/executing-queries.md @@ -76,60 +76,3 @@ class MyNewMigration extends BaseMigration } } ``` - -## Inserting Data - -Migrations makes it easy to insert data into your tables. Whilst this feature is -intended for the [seed feature](seeding), you are also free to use the -insert methods in your migrations: - -```php -table('status'); - - // inserting only one row - $singleRow = [ - 'id' => 1, - 'name' => 'In Progress' - ]; - - $table->insert($singleRow)->saveData(); - - // inserting multiple rows - $rows = [ - [ - 'id' => 2, - 'name' => 'Stopped' - ], - [ - 'id' => 3, - 'name' => 'Queued' - ] - ]; - - $table->insert($rows)->saveData(); - } - - /** - * Migrate Down. - */ - public function down(): void - { - $this->execute('DELETE FROM status'); - } -} -``` - -> [!NOTE] -> You cannot use the insert methods inside a change() method. Please use the -> up() and down() methods. diff --git a/docs/en/guides/inserting-data.md b/docs/en/guides/inserting-data.md new file mode 100644 index 000000000..d9661c035 --- /dev/null +++ b/docs/en/guides/inserting-data.md @@ -0,0 +1,56 @@ +# Inserting Data + +Migrations makes it easy to insert data into your tables. Whilst this feature +is intended for the [seed feature](seeding), you are also free to use the +insert methods in your migrations: + +```php +table('status'); + + // inserting only one row + $singleRow = [ + 'id' => 1, + 'name' => 'In Progress', + ]; + + $table->insert($singleRow)->saveData(); + + // inserting multiple rows + $rows = [ + [ + 'id' => 2, + 'name' => 'Stopped', + ], + [ + 'id' => 3, + 'name' => 'Queued', + ], + ]; + + $table->insert($rows)->saveData(); + } + + /** + * Migrate Down. + */ + public function down(): void + { + $this->execute('DELETE FROM status'); + } +} +``` + +> [!NOTE] +> You cannot use insert methods inside `change()`. Use `up()` and `down()` +> instead. diff --git a/docs/en/seeding.md b/docs/en/guides/seeding.md similarity index 100% rename from docs/en/seeding.md rename to docs/en/guides/seeding.md diff --git a/docs/en/using-the-query-builder.md b/docs/en/guides/using-the-query-builder.md similarity index 100% rename from docs/en/using-the-query-builder.md rename to docs/en/guides/using-the-query-builder.md diff --git a/docs/en/guides/writing-migrations/columns-and-table-operations.md b/docs/en/guides/writing-migrations/columns-and-table-operations.md new file mode 100644 index 000000000..c8bd82baf --- /dev/null +++ b/docs/en/guides/writing-migrations/columns-and-table-operations.md @@ -0,0 +1,388 @@ +# Columns and Table Operations + + + +## Adding Columns + +Column types are specified as strings and can be one of: + +- binary +- boolean +- char +- date +- datetime +- decimal +- float +- double +- smallinteger +- integer +- biginteger +- string +- text +- time +- timestamp +- uuid +- binaryuuid +- nativeuuid + +In addition, the MySQL adapter supports `enum`, `set`, `blob`, `tinyblob`, +`mediumblob`, `longblob`, `bit` and `json` column types (`json` in MySQL 5.7 +and above). When providing a limit value and using `binary`, `varbinary` or +`blob` and its subtypes, the retained column type will be based on required +length (see [Limit Option and MySQL](#limit-option-and-mysql) for details). + +With most adapters, the `uuid` and `nativeuuid` column types are aliases, +however with the MySQL adapter + MariaDB, the `nativeuuid` type maps to a +native UUID column instead of `CHAR(36)` like `uuid` does. + +In addition, the Postgres adapter supports `interval`, `json`, `jsonb`, `uuid`, +`cidr`, `inet` and `macaddr` column types (PostgreSQL 9.3 and above). + +### Valid Column Options + +The following are valid column options. + +For any column type: + +| Option | Description | +|---------|----------------------------------------------------------------------------| +| limit | set maximum length for strings, also hints column types in adapters | +| length | alias for `limit` | +| default | set default value or action | +| null | allow `NULL` values, defaults to `true` | +| after | specify the column that a new column should be placed after *(MySQL only)* | +| comment | set a text comment on the column | + +For `decimal` and `float` columns: + +| Option | Description | +|-----------|--------------------------------------------------------| +| precision | total number of digits | +| scale | number of digits after the decimal point | +| signed | enable or disable the `unsigned` option *(MySQL only)* | + +> [!NOTE] +> Migrations follows the SQL standard where `precision` is the total number of +> digits and `scale` is digits after the decimal point. + +For `enum` and `set` columns: + +| Option | Description | +|--------|--------------------------------------------| +| values | comma separated list or an array of values | + +For `smallinteger`, `integer` and `biginteger` columns: + +| Option | Description | +|----------|--------------------------------------------------------| +| identity | enable or disable automatic incrementing | +| signed | enable or disable the `unsigned` option *(MySQL only)* | + +For `date`, `time`, `datetime`, and `timestamp` columns, default/timezone/update +options are supported as appropriate for the adapter. For `string` and `text`, +MySQL also supports `collation` and `encoding`. + +You can add `created` and `updated` timestamps using `addTimestamps()` or +`addTimestampsWithTimezone()`: + +```php +$this->table('users')->addTimestamps()->create(); +$this->table('users')->addTimestampsWithTimezone()->create(); +``` + +### Limit Option and MySQL + +When using the MySQL adapter, there are a couple things to consider when +working with limits: + +- When using a `string` primary key or index on MySQL 5.7 or below, or the + MyISAM storage engine, and the default charset of `utf8mb4_unicode_ci`, you + must specify a limit less than or equal to 191, or use a different charset. +- Additional hinting of database column type can be made for `integer`, `text`, + `blob`, `tinyblob`, `mediumblob`, `longblob` columns. + +```php +table('cart_items'); +$table->addColumn('user_id', 'integer') + ->addColumn('product_id', 'integer', ['limit' => MysqlAdapter::INT_BIG]) + ->addColumn('subtype_id', 'integer', ['limit' => MysqlAdapter::INT_SMALL]) + ->addColumn('quantity', 'integer', ['limit' => MysqlAdapter::INT_TINY]) + ->create(); +``` + +### Default Values with Expressions + +If you need to set a default to an expression, you can use a `Literal` to have +the column's default value used without quoting or escaping: + +```php +use Migrations\BaseMigration; +use Migrations\Db\Literal; + +class AddSomeColumns extends BaseMigration +{ + public function change(): void + { + $this->table('users') + ->addColumn('uniqid', 'uuid', [ + 'default' => Literal::from('uuid_generate_v4()'), + ]) + ->create(); + } +} +``` + +## Creating a Table + +Creating a table is straightforward using the Table object: + +```php +table('users'); + $users->addColumn('username', 'string', ['limit' => 20]) + ->addColumn('password', 'string', ['limit' => 40]) + ->addColumn('email', 'string', ['limit' => 100]) + ->addColumn('created', 'datetime') + ->addColumn('updated', 'datetime', ['null' => true]) + ->addIndex(['username', 'email'], ['unique' => true]) + ->create(); + } +} +``` + +Migrations automatically creates an auto-incrementing primary key column called +`id` for every table unless configured otherwise. + +You can disable the automatic `id` column and define a custom primary key: + +```php +$table = $this->table('followers', [ + 'id' => false, + 'primary_key' => ['user_id', 'follower_id'], +]); +``` + +The MySQL adapter also supports table options such as `comment`, `collation`, +`row_format`, `engine`, `signed`, and `limit`. + +To set unsigned primary keys, pass the `signed` option with `false`, or enable +the `Migrations.unsigned_primary_keys` and `Migrations.unsigned_ints` feature +flags together. See [Feature Flags](../../advanced/integration-and-deployment#feature-flags). + +## MySQL ALTER TABLE Options + +When modifying tables in MySQL, you can control how the `ALTER TABLE` +operation is performed using the `algorithm` and `lock` options: + +```php +$table->update([ + 'algorithm' => 'INPLACE', + 'lock' => 'NONE', +]); +``` + +Not all operations support all `algorithm` and `lock` combinations, and MySQL +will raise an error if the requested combination is not possible. + +## Table Partitioning + +Migrations supports table partitioning for MySQL and PostgreSQL. Partitioning +helps manage large tables by splitting them into smaller, more manageable +pieces. + +Supported strategies include: + +- `RANGE` +- `RANGE COLUMNS` +- `LIST` +- `LIST COLUMNS` +- `HASH` +- `KEY` *(MySQL only)* + +You can also partition by expressions using `Literal::from(...)`, and add or +drop partitions on existing tables. + +## Saving Changes + +When working with the Table object, Migrations stores certain operations in a +pending changes cache. Once you have made the changes you want to the table, +you must save them. Migrations provides three methods: + +- `create()` creates the table and runs pending changes +- `update()` runs pending changes on an existing table +- `save()` creates or updates depending on whether the table exists + +When using `change()`, you should always use `create()` or `update()`, and +never `save()`. + +## Renaming a Column + +To rename a column, call `renameColumn()`: + +```php +$this->table('users') + ->renameColumn('bio', 'biography') + ->save(); +``` + +## Adding a Column After Another Column + +When adding a column with the MySQL adapter, you can dictate its position using +the `after` option: + +```php +$this->table('users') + ->addColumn('city', 'string', ['after' => 'email']) + ->update(); +``` + +The `\Migrations\Db\Adapter\MysqlAdapter::FIRST` constant can be used to place +the new column first. + +## Dropping a Column + +To drop a column, use `removeColumn()`: + +```php +$this->table('users') + ->removeColumn('short_name') + ->save(); +``` + +## Specifying a Column Limit + +You can limit the maximum length of a column using the `limit` option: + +```php +$this->table('tags') + ->addColumn('short_name', 'string', ['limit' => 30]) + ->update(); +``` + +## Changing Column Attributes + +There are two methods for modifying existing columns: + +### Updating Columns + +To modify specific column attributes while preserving others, use +`updateColumn()`: + +```php +$this->table('users') + ->updateColumn('email', null, ['null' => true]) + ->save(); +``` + +This automatically preserves unspecified attributes such as defaults, +nullability, limits, comments, signedness, collation, and enum/set values. + +### Changing Columns + +To completely replace a column definition, use `changeColumn()` and specify all +desired attributes: + +```php +$this->table('users') + ->changeColumn('email', 'string', [ + 'limit' => 255, + 'null' => true, + 'default' => null, + ]) + ->save(); +``` + +You can enable attribute preservation with `'preserveUnspecified' => true`. + +## Determining Whether a Table Exists + +Use `hasTable()` to check whether a table exists: + +```php +if ($this->hasTable('users')) { + // do something +} +``` + +## Dropping a Table + +Tables can be dropped using `drop()`: + +```php +$this->table('users')->drop()->save(); +``` + +## Renaming a Table + +To rename a table, call `rename()`: + +```php +$this->table('users') + ->rename('legacy_users') + ->update(); +``` + +## Changing the Primary Key + +To change the primary key on an existing table, use `changePrimaryKey()`: + +```php +$this->table('users') + ->changePrimaryKey(['new_id', 'username']) + ->save(); +``` + +## Creating Custom Primary Keys + +You can specify an `autoId` property in the migration class and set it to +`false`, which turns off automatic `id` column creation: + +```php +table('products') + ->addColumn('id', 'uuid') + ->addPrimaryKey('id') + ->addColumn('name', 'string') + ->addColumn('description', 'text') + ->create(); + } +} +``` + +> [!WARNING] +> Dealing with primary keys can only be done on table creation operations for +> some database servers. + +## Changing the Table Comment + +To change the comment on an existing table, use `changeComment()`: + +```php +$this->table('users') + ->changeComment('This is the table with users auth information') + ->save(); +``` + +## Next Steps + +- [Indexes and Constraints](indexes-and-constraints) +- [Schema Introspection and Platform Limitations](schema-introspection-and-platform-limitations) diff --git a/docs/en/guides/writing-migrations/index.md b/docs/en/guides/writing-migrations/index.md new file mode 100644 index 000000000..7d25425c4 --- /dev/null +++ b/docs/en/guides/writing-migrations/index.md @@ -0,0 +1,21 @@ +# Writing Migrations + +Migrations are a declarative API that helps you transform your database. Each +migration is represented by a PHP class in a unique file. It is preferred that +you write your migrations using the Migrations API, but raw SQL is also +supported. + +For generating migration files with `bake`, naming patterns, anonymous +migration classes, and command-line column syntax, see +[Creating Migrations](../../getting-started/creating-migrations). + +## Guide Map + +This section has been split into focused reference pages: + +- [Migration Methods](migration-methods) +- [Columns and Table Operations](columns-and-table-operations) +- [Indexes and Constraints](indexes-and-constraints) +- [Schema Introspection and Platform Limitations](schema-introspection-and-platform-limitations) + +Use these pages as the API reference for authoring migrations. diff --git a/docs/en/guides/writing-migrations/indexes-and-constraints.md b/docs/en/guides/writing-migrations/indexes-and-constraints.md new file mode 100644 index 000000000..5c7a680db --- /dev/null +++ b/docs/en/guides/writing-migrations/indexes-and-constraints.md @@ -0,0 +1,148 @@ +# Indexes and Constraints + +## Working With Indexes + +To add an index to a table, call `addIndex()`: + +```php +table('users') + ->addColumn('city', 'string') + ->addIndex(['city']) + ->save(); + } +} +``` + +You can also specify unique indexes, explicit names, sort order, index length, +and advanced adapter-specific options. + +The fluent builder is also available: + +```php +$this->table('users') + ->addIndex( + $this->index(['email', 'username']) + ->setType('unique') + ->setName('idx_users_email') + ->setOrder(['email' => 'DESC', 'username' => 'ASC']) + ) + ->save(); +``` + +Adapter-specific capabilities include: + +- MySQL `fulltext` indexes +- MySQL index-length options +- SQL Server and PostgreSQL `include` columns +- PostgreSQL, SQL Server, and SQLite partial indexes +- PostgreSQL concurrent index creation +- PostgreSQL `gin` indexes + +To remove indexes, use `removeIndex()` or `removeIndexByName()`. + +## Working With Foreign Keys + +Migrations supports foreign key constraints on database tables: + +```php +table('tag_relationships') + ->addColumn('tag_id', 'integer', ['null' => true]) + ->addForeignKey( + 'tag_id', + 'tags', + 'id', + ['delete' => 'SET_NULL', 'update' => 'NO_ACTION'] + ) + ->save(); + } +} +``` + +The `delete` and `update` options control `ON DELETE` and `ON UPDATE` +behavior. Valid values are `SET_NULL`, `NO_ACTION`, `CASCADE`, and +`RESTRICT`. + +Foreign keys can also be defined with arrays of columns for composite keys. + +The `foreignKey()` fluent builder is available for more complex cases: + +```php +$this->table('articles') + ->addForeignKey( + $this->foreignKey() + ->setColumns('user_id') + ->setReferencedTable('users') + ->setReferencedColumns('user_id') + ->setName('article_user_fk') + ) + ->save(); +``` + +Use `hasForeignKey()` to check whether a foreign key exists, and +`dropForeignKey()` to remove one. + +## Working With Check Constraints + +Check constraints allow you to enforce data validation rules at the database +level. + +> [!NOTE] +> Check constraints are supported by MySQL 8.0.16+, PostgreSQL, and SQLite. + +### Adding a Check Constraint + +```php +$this->table('products') + ->addColumn('price', 'decimal', ['precision' => 10, 'scale' => 2]) + ->addCheckConstraint('price_positive', 'price > 0') + ->save(); +``` + +### Using the Fluent Builder + +```php +$this->table('users') + ->addCheckConstraint( + $this->checkConstraint() + ->setName('age_valid') + ->setExpression('age >= 18 AND age <= 120') + ) + ->save(); +``` + +If you do not specify a name, one will be auto-generated. + +Check constraints can reference multiple columns and use more complex SQL +expressions. + +Use `hasCheckConstraint()` to verify existence and `dropCheckConstraint()` to +remove a constraint. + +### Database-Specific Behavior + +- MySQL stores check constraint metadata in + `INFORMATION_SCHEMA.CHECK_CONSTRAINTS` +- PostgreSQL stores constraints in `pg_constraint` +- SQLite recreates the table when altering check constraints +- SQL Server support is planned for a future release + +## Next Steps + +- [Columns and Table Operations](columns-and-table-operations) +- [Schema Introspection and Platform Limitations](schema-introspection-and-platform-limitations) diff --git a/docs/en/guides/writing-migrations/migration-methods.md b/docs/en/guides/writing-migrations/migration-methods.md new file mode 100644 index 000000000..2ac07c995 --- /dev/null +++ b/docs/en/guides/writing-migrations/migration-methods.md @@ -0,0 +1,135 @@ +# Migration Methods + +## The Change Method + +Migrations supports 'reversible migrations'. In many scenarios, you only need +to define the `up` logic, and Migrations can figure out how to generate the +rollback operations for you. For example: + +```php +table('user_logins'); + $table->addColumn('user_id', 'integer') + ->addColumn('created', 'datetime') + ->create(); + } +} +``` + +When executing this migration, Migrations will create the `user_logins` table +on the way up and automatically figure out how to drop the table on the way +down. Please be aware that when a `change` method exists, Migrations will +ignore the `up` and `down` methods. If you need to use these methods it is +recommended to create a separate migration file. + +> [!NOTE] +> When creating or updating tables inside a `change()` method you must use the +> Table `create()` and `update()` methods. Migrations cannot automatically +> determine whether a `save()` call is creating a new table or modifying an +> existing one. + +The following actions are reversible when done through the Table API in +Migrations, and will be automatically reversed: + +- Creating a table +- Renaming a table +- Adding a column +- Renaming a column +- Adding an index +- Adding a foreign key +- Adding a check constraint + +If a command cannot be reversed then Migrations will throw an +`IrreversibleMigrationException` when it's migrating down. If you wish to use a +command that cannot be reversed in the change function, you can use an `if` +statement with `$this->isMigratingUp()` to only run things in the up or down +direction. For example: + +```php +table('user_logins'); + $table->addColumn('user_id', 'integer') + ->addColumn('created', 'datetime') + ->create(); + if ($this->isMigratingUp()) { + $table->insert([['user_id' => 1, 'created' => '2020-01-19 03:14:07']]) + ->save(); + } + } +} +``` + +## The Up Method + +The `up()` method is automatically run by Migrations when you are migrating up +and it detects the given migration hasn't been executed previously. You should +use the `up()` method to transform the database with your intended changes. + +## The Down Method + +The `down()` method is automatically run by Migrations when you are migrating +down and it detects the given migration has been executed in the past. You +should use the `down()` method to reverse the transformations described in the +`up()` method. + +## The Init Method + +The `init()` method is run by Migrations before the migration methods if it +exists. This can be used for setting common class properties that are then used +within the migration methods. + +## The Should Execute Method + +The `shouldExecute()` method is run by Migrations before executing the +migration. This can be used to prevent the migration from being executed at +this time. It always returns `true` by default. You can override it in your +custom `BaseMigration` implementation. + +## Working With Tables + +The Table object enables you to manipulate database tables using PHP code. You +can retrieve an instance of the Table object by calling the `table()` method +from within your database migration: + +```php +table('tableName'); + } + + public function down(): void + { + } +} +``` + +You can then manipulate this table using the methods provided by the Table +object. + +## Next Steps + +- [Columns and Table Operations](columns-and-table-operations) +- [Indexes and Constraints](indexes-and-constraints) +- [Schema Introspection and Platform Limitations](schema-introspection-and-platform-limitations) diff --git a/docs/en/guides/writing-migrations/schema-introspection-and-platform-limitations.md b/docs/en/guides/writing-migrations/schema-introspection-and-platform-limitations.md new file mode 100644 index 000000000..d733a9c9d --- /dev/null +++ b/docs/en/guides/writing-migrations/schema-introspection-and-platform-limitations.md @@ -0,0 +1,105 @@ +# Schema Introspection and Platform Limitations + +## Checking Columns + +`BaseMigration` also provides methods for introspecting the current schema, +allowing you to conditionally make changes to schema, or read data. Schema is +inspected when the migration is run. + +### Get a Column List + +To retrieve all table columns, create a table object and call `getColumns()`: + +```php +table('users')->getColumns(); + } +} +``` + +### Get a Column by Name + +To retrieve one table column, call `getColumn()`: + +```php +table('users')->getColumn('email'); + } +} +``` + +### Check Whether a Column Exists + +Use `hasColumn()` to determine whether a table already has a given column: + +```php +table('user'); + if ($table->hasColumn('username')) { + // do something + } + } +} +``` + +## Changing Templates + +See [Custom Seed Migration Templates](../seeding#custom-seed-migration-templates) +for how to customize the templates used to generate migrations. + +## Database-Specific Limitations + +While Migrations aims to provide a database-agnostic API, some features have +database-specific limitations or are not available on all platforms. + +### SQL Server + +The following features are not supported on SQL Server: + +- Check constraints are not currently implemented +- Table comments are not supported +- `insertOrSkip()` is not supported; use `insertOrUpdate()` instead + +### SQLite + +SQLite limitations include: + +- named foreign keys are not supported +- table comments are stored as metadata, not in the database itself +- altering check constraints requires recreating the table +- table partitioning is not supported + +### PostgreSQL + +PostgreSQL does not support MySQL's `KEY` partitioning type. Use `HASH` +partitioning instead for similar distribution behavior. + +### MySQL/MariaDB + +For MySQL, the `$conflictColumns` parameter in `insertOrUpdate()` is ignored +because MySQL's `ON DUPLICATE KEY UPDATE` automatically applies to all unique +constraints. PostgreSQL and SQLite require this parameter to be specified. + +Some geometry column features may not work correctly on MariaDB due to +differences in GIS implementation compared to MySQL. diff --git a/docs/en/index.md b/docs/en/index.md index 2126603b6..7c5c13c0d 100644 --- a/docs/en/index.md +++ b/docs/en/index.md @@ -14,20 +14,22 @@ Migrations 5.x includes several new features: - **Seed tracking** - Seeds are now tracked in a `cake_seeds` table, preventing accidental re-runs -- **Check constraints** - Support for database check constraints via `addCheckConstraint()` +- **Check constraints** - Support for database check constraints via + `addCheckConstraint()` - **Default values in bake** - Specify default values when baking migrations (e.g., `active:boolean:default[true]`) -- **MySQL ALTER options** - Control `ALGORITHM` and `LOCK` for ALTER TABLE operations +- **MySQL ALTER options** - Control `ALGORITHM` and `LOCK` for ALTER TABLE + operations - **insertOrSkip()** - New method for idempotent seed data insertion -See the [Upgrading from 4.x to 5.x](upgrading) guide for breaking changes and migration steps from 4.x. +See the [Upgrading from 4.x to 5.x](upgrades/upgrading-from-4-x) guide for breaking changes and +migration steps from 4.x. ## Installation -By default Migrations is installed with the application skeleton. If -you've removed it and want to re-install it, you can do so by running the -following from your application's ROOT directory (where **composer.json** file is -located): +By default Migrations is installed with the application skeleton. If you've +removed it and want to re-install it, run the following from your application's +root directory: ```bash php composer.phar require cakephp/migrations "@stable" @@ -36,34 +38,26 @@ php composer.phar require cakephp/migrations "@stable" composer require cakephp/migrations "@stable" ``` -To use the plugin you'll need to load it in your application's -**config/bootstrap.php** file. You can use [CakePHP's Plugin shell](https://book.cakephp.org/5/en/console-and-shells/plugin-shell.html) to -load and unload plugins from your **config/bootstrap.php**: +To use the plugin, load it in your application's `config/bootstrap.php` file: ```bash bin/cake plugin load Migrations ``` -Or you can load the plugin by editing your **src/Application.php** file and -adding the following statement: +Or load the plugin in `src/Application.php`: ```php $this->addPlugin('Migrations'); ``` -Additionally, you will need to configure the default database configuration for -your application in your **config/app.php** file as explained in the [Database -Configuration section](https://book.cakephp.org/5/en/orm/database-basics.html#database-configuration). - -## Upgrading from 4.x - -If you are upgrading from Migrations 4.x, please see the [Upgrading from 4.x to 5.x](upgrading) guide -for breaking changes and migration steps. +Additionally, configure the default database connection in `config/app.php` as +explained in the [Database Configuration section](https://book.cakephp.org/5/en/orm/database-basics.html#database-configuration). ## Overview A migration is a PHP file that describes the changes to apply to your database. -A migration file can add, change or remove tables, columns, indexes and foreign keys. +A migration file can add, change, or remove tables, columns, indexes, and +foreign keys. If we wanted to create a table, we could use a migration similar to this: @@ -109,833 +103,114 @@ When applied, this migration will add a table to your database named - `modified` column of type `datetime` > [!NOTE] -> Migrations are not automatically applied, you can apply and rollback -> migrations with CLI commands. - -Once the file has been created in the **config/Migrations** folder, you can -apply it: +> Migrations are not automatically applied. Use the CLI commands to apply and +> roll back migrations. ```bash bin/cake migrations migrate ``` -## Creating Migrations - -Migration files are stored in the **config/Migrations** directory of your -application. The name of the migration files are prefixed with the date in -which they were created, in the format **YYYYMMDDHHMMSS_MigrationName.php**. -Here are examples of migration filenames: - -- **20160121163850_CreateProducts.php** -- **20160210133047_AddRatingToProducts.php** - -The easiest way to create a migrations file is by using `bin/cake bake migration` CLI command: - -```bash -bin/cake bake migration CreateProducts -``` - -This will create an empty migration that you can edit to add any columns, -indexes and foreign keys you need. See the [Creating A Table](writing-migrations#creating-a-table) section to -learn more about using migrations to define tables. - -> [!NOTE] -> Migrations need to be applied using `bin/cake migrations migrate` after -> they have been created. - -### Migration file names - -When generating a migration, you can follow one of the following patterns -to have additional skeleton code generated: - -- `/^(Create)(.*)/` Creates the specified table. -- `/^(Drop)(.*)/` Drops the specified table. - Ignores specified field arguments -- `/^(Add).*(?:To)(.*)/` Adds fields to the specified - table -- `/^(Remove).*(?:From)(.*)/` Removes fields from the - specified table -- `/^(Alter)(.*)/` Alters the specified table. An alias - for CreateTable and AddField. -- `/^(Alter).*(?:On)(.*)/` Alters fields from the specified table. - -You can also use the `underscore_form` as the name for your migrations i.e. -`create_products`. - -> [!WARNING] -> Migration names are used as class names, and thus may collide with -> other migrations if the class names are not unique. In this case, it may be -> necessary to manually override the name at a later date, or simply change -> the name you are specifying. - -### Creating a table - -You can use `bake migration` to create a table: - -```bash -bin/cake bake migration CreateProducts name:string description:text created modified -``` - -The command line above will generate a migration file that resembles: - -```php -table('products'); - $table->addColumn('name', 'string', [ - 'default' => null, - 'limit' => 255, - 'null' => false, - ]); - $table->addColumn('description', 'text', [ - 'default' => null, - 'null' => false, - ]); - $table->addColumn('created', 'datetime', [ - 'default' => null, - 'null' => false, - ]); - $table->addColumn('modified', 'datetime', [ - 'default' => null, - 'null' => false, - ]); - $table->create(); - } -} -``` - -### Column syntax - -The `bake migration` command provides a compact syntax to define columns when -generating a migration: - -```bash -bin/cake bake migration CreateProducts name:string description:text created modified -``` - -You can use the column syntax when creating tables and adding columns. You can -also edit the migration after generation to add or customize the columns - -Columns on the command line follow the following pattern: - - fieldName:fieldType?[length]:default[value]:indexType:indexName - -For instance, the following are all valid ways of specifying an email field: - -- `email:string?` -- `email:string:unique` -- `email:string?[50]` -- `email:string:unique:EMAIL_INDEX` -- `email:string[120]:unique:EMAIL_INDEX` - -While defining decimal columns, the `length` can be defined to have precision -and scale, separated by a comma. - -- `amount:decimal[5,2]` -- `amount:decimal?[5,2]` - -Columns with a question mark after the fieldType will make the column nullable. - -The `length` part is optional and should always be written between bracket. - -The `default[value]` part is optional and sets the default value for the column. -Supported value types include: - -- Booleans: `true` or `false` - e.g., `active:boolean:default[true]` -- Integers: `0`, `123`, `-456` - e.g., `count:integer:default[0]` -- Floats: `1.5`, `-2.75` - e.g., `rate:decimal:default[1.5]` -- Strings: `'hello'` or `"world"` (quoted) - e.g., `status:string:default['pending']` -- Null: `null` or `NULL` - e.g., `description:text?:default[null]` -- SQL expressions: `CURRENT_TIMESTAMP` - e.g., `created_at:datetime:default[CURRENT_TIMESTAMP]` - -Fields named `created` and `modified`, as well as any field with a `_at` -suffix, will automatically be set to the type `datetime`. - -There are some heuristics to choosing fieldtypes when left unspecified or set to -an invalid value. Default field type is `string`: - -- id: integer -- created, modified, updated: datetime -- latitude, longitude (or short forms lat, lng): decimal - -Additionally you can create an empty migrations file if you want full control -over what needs to be executed, by omitting to specify a columns definition: - -```bash -bin/cake migrations create MyCustomMigration -``` - -See [Writing Migrations](writing-migrations) for more information on how to use `Table` -objects to interact with tables and define schema changes. - -### Adding columns to an existing table - -If the migration name in the command line is of the form "AddXXXToYYY" and is -followed by a list of column names and types then a migration file containing -the code for creating the columns will be generated: - -```bash -bin/cake bake migration AddPriceToProducts price:decimal[5,2] -``` - -Executing the command line above will generate: - -```php -table('products'); - $table->addColumn('price', 'decimal', [ - 'default' => null, - 'null' => false, - 'precision' => 5, - 'scale' => 2, - ]); - $table->update(); - } -} -``` - -### Adding a column with an index - -It is also possible to add indexes to columns: - -```bash -bin/cake bake migration AddNameIndexToProducts name:string:index -``` - -will generate: - -```php -table('products'); - $table->addColumn('name', 'string') - ->addColumn('email', 'string') - ->addIndex(['name']) - // add a unique index: - ->addIndex('email', ['unique' => true]) - ->update(); - } -} -``` - -### Adding a column with a default value +## Guide Map -You can specify default values for columns using the `default[value]` syntax: +Use the focused guides below instead of a single long reference page: -```bash -bin/cake bake migration AddActiveToUsers active:boolean:default[true] -``` - -will generate: +- [Installation and Overview](getting-started/installation-and-overview) +- [Creating Migrations](getting-started/creating-migrations) +- [Snapshots and Diffs](getting-started/snapshots-and-diffs) +- [Running and Managing Migrations](getting-started/running-and-managing-migrations) +- [Integration and Deployment](advanced/integration-and-deployment) +- [Writing Migrations](guides/writing-migrations) +- [Using the Query Builder](guides/using-the-query-builder) +- [Executing Queries](guides/executing-queries) +- [Database Seeding](guides/seeding) +- [Upgrading to the builtin backend](upgrades/upgrading-to-builtin-backend) -```php -table('users'); - $table->addColumn('active', 'boolean', [ - 'default' => true, - 'null' => false, - ]); - $table->update(); - } -} -``` - -You can combine default values with other options like nullable and indexes: - -```bash -bin/cake bake migration AddStatusToOrders status:string:default['pending']:unique -``` - -### Altering a column - -In the same way, you can generate a migration to alter a column by using the -command line, if the migration name is of the form "AlterXXXOnYYY": - -```bash -bin/cake bake migration AlterPriceOnProducts name:float -``` - -will generate: - -```php -table('products'); - $table->changeColumn('name', 'float'); - $table->update(); - } -} -``` - -> [!WARNING] -> Changing the type of a column can result in data loss if the -> current and target column type are not compatible. For example converting -> a varchar to float. - -### Removing a column - -In the same way, you can generate a migration to remove a column by using the -command line, if the migration name is of the form "RemoveXXXFromYYY": - -```bash -bin/cake bake migration RemovePriceFromProducts price -``` - -creates the file: +## Upgrading from 4.x -```php -table('products'); - $table->removeColumn('price') - ->save(); - } -} -``` +## Creating Migrations -> [!NOTE] -> The removeColumn command is not reversible, so must be called in the -> up method. A corresponding addColumn call should be added to the -> down method. +Migration naming conventions, bake patterns, column syntax, and generated +examples are covered in [Creating Migrations](getting-started/creating-migrations). ## Generating migration snapshots from an existing database -If you have a pre-existing database and want to start using -migrations, or to version control the initial schema of your application's -database, you can run the `bake migration_snapshot` command: - -```bash -bin/cake bake migration_snapshot Initial -``` - -It will generate a migration file called **YYYYMMDDHHMMSS_Initial.php** -containing all the create statements for all tables in your database. - -By default, the snapshot will be created by connecting to the database defined -in the `default` connection configuration. If you need to bake a snapshot from -a different datasource, you can use the `--connection` option: - -```bash -bin/cake bake migration_snapshot Initial --connection my_other_connection -``` - -You can also make sure the snapshot includes only the tables for which you have -defined the corresponding model classes by using the `--require-table` flag: - -```bash -bin/cake bake migration_snapshot Initial --require-table -``` - -When using the `--require-table` flag, the shell will look through your -application `Table` classes and will only add the model tables in the snapshot. - -If you want to generate a snapshot without marking it as migrated (for example, -for use in unit tests), you can use the `--generate-only` flag: - -```bash -bin/cake bake migration_snapshot Initial --generate-only -``` - -This will create the migration file but will not add an entry to the migrations -tracking table, allowing you to move the file to a different location without causing -"MISSING" status issues. - -The same logic will be applied implicitly if you wish to bake a snapshot for a -plugin. To do so, you need to use the `--plugin` option: - -```bash -bin/cake bake migration_snapshot Initial --plugin MyPlugin -``` - -Only the tables which have a `Table` object model class defined will be added -to the snapshot of your plugin. - -> [!NOTE] -> When baking a snapshot for a plugin, the migration files will be created -> in your plugin's **config/Migrations** directory. - -Be aware that when you bake a snapshot, it is automatically added to the -migrations log table as migrated. +For snapshot generation, diff workflows, and dump files, see +[Snapshots and Diffs](getting-started/snapshots-and-diffs). ## Generating a diff -As migrations are applied and rolled back, the migrations plugin will generate -a 'dump' file of your schema. If you make manual changes to your database schema -outside of migrations, you can use `bake migration_diff` to generate -a migration file that captures the difference between the current schema dump -file and database schema. To do so, you can use the following command: - -```bash -bin/cake bake migration_diff NameOfTheMigrations -``` - -By default, the diff will be created by connecting to the database defined -in the `default` connection configuration. -If you need to bake a diff from a different datasource, you can use the -`--connection` option: - -```bash -bin/cake bake migration_diff NameOfTheMigrations --connection my_other_connection -``` - -If you want to use the diff feature on an application that already has a -migrations history, you need to manually create the dump file that will be used -as comparison: - -```bash -bin/cake migrations dump -``` - -The database state must be the same as it would be if you just migrated all -your migrations before you create a dump file. -Once the dump file is generated, you can start doing changes in your database -and use the `bake migration_diff` command whenever you see fit. - -> [!NOTE] -> Migration diff generation can not detect column renamings. +The `bake migration_diff` workflow is also covered in +[Snapshots and Diffs](getting-started/snapshots-and-diffs). ## Applying Migrations -Once you have generated or written your migration file, you need to execute the -following command to apply the changes to your database: - -```bash -# Run all the migrations -bin/cake migrations migrate - -# Migrate to a specific version using the ``--target`` option -# or ``-t`` for short. -# The value is the timestamp that is prefixed to the migrations file name:: -bin/cake migrations migrate -t 20150103081132 - -# By default, migration files are looked for in the **config/Migrations** -# directory. You can specify the directory using the ``--source`` option -# or ``-s`` for short. -# The following example will run migrations in the **config/Alternate** -# directory -bin/cake migrations migrate -s Alternate - -# You can run migrations to a different connection than the ``default`` one -# using the ``--connection`` option or ``-c`` for short -bin/cake migrations migrate -c my_custom_connection - -# Migrations can also be run for plugins. Simply use the ``--plugin`` option -# or ``-p`` for short -bin/cake migrations migrate -p MyAwesomePlugin -``` +For `migrate`, `rollback`, `status`, `mark_migrated`, and related command +options, see +[Running and Managing Migrations](getting-started/running-and-managing-migrations). ## Reverting Migrations -The rollback command is used to undo previous migrations executed by this -plugin. It is the reverse action of the `migrate` command: - -```bash -# You can rollback to the previous migration by using the -# ``rollback`` command:: -bin/cake migrations rollback - -# You can also pass a migration version number to rollback -# to a specific version:: -bin/cake migrations rollback -t 20150103081132 -``` - -You can also use the `--source`, `--connection` and `--plugin` options -just like for the `migrate` command. +Rollback usage is documented in +[Running and Managing Migrations](getting-started/running-and-managing-migrations). ## View Migrations Status -The Status command prints a list of all migrations, along with their current -status. You can use this command to determine which migrations have been run: - -```bash -bin/cake migrations status -``` - -You can also output the results as a JSON formatted string using the -`--format` option (or `-f` for short): - -```bash -bin/cake migrations status --format json -``` - -You can also use the `--source`, `--connection` and `--plugin` options -just like for the `migrate` command. - -### Cleaning up missing migrations - -Sometimes migration files may be deleted from the filesystem but still exist -in the migrations tracking table. These migrations will be marked as **MISSING** in the -status output. You can remove these entries from the tracking table using the -`--cleanup` option: - -```bash -bin/cake migrations status --cleanup -``` - -This will remove all migration entries from the tracking table that no longer -have corresponding migration files in the filesystem. +Status commands, cleanup, and JSON output are documented in +[Running and Managing Migrations](getting-started/running-and-managing-migrations). ## Marking a migration as migrated -It can sometimes be useful to mark a set of migrations as migrated without -actually running them. In order to do this, you can use the `mark_migrated` -command. The command works seamlessly as the other commands. - -You can mark all migrations as migrated using this command: - -```bash -bin/cake migrations mark_migrated -``` - -You can also mark all migrations up to a specific version as migrated using -the `--target` option: - -```bash -bin/cake migrations mark_migrated --target=20151016204000 -``` - -If you do not want the targeted migration to be marked as migrated during the -process, you can use the `--exclude` flag with it: - -```bash -bin/cake migrations mark_migrated --target=20151016204000 --exclude -``` - -Finally, if you wish to mark only the targeted migration as migrated, you can -use the `--only` flag: - -```bash -bin/cake migrations mark_migrated --target=20151016204000 --only -``` - -You can also use the `--source`, `--connection` and `--plugin` options -just like for the `migrate` command. - -> [!NOTE] -> When you bake a snapshot with the `cake bake migration_snapshot` -> command, the created migration will automatically be marked as migrated. -> To prevent this behavior (e.g., for unit test migrations), use the -> `--generate-only` flag. - -This command expects the migration version number as argument: - -```bash -bin/cake migrations mark_migrated 20150420082532 -``` - -If you wish to mark all migrations as migrated, you can use the `all` special -value. If you use it, it will mark all found migrations as migrated: - -```bash -bin/cake migrations mark_migrated all -``` +See [Running and Managing Migrations](getting-started/running-and-managing-migrations) +for `mark_migrated` examples and caveats. ## Seeding your database -Seed classes are a good way to populate your database with default or starter -data. They are also a great way to generate data for development environments. - -By default, seeds will be looked for in the `config/Seeds/` directory of -your application. See the [Database Seeding](seeding) for how to build and use seed classes. +Seed classes are documented in [Database Seeding](guides/seeding). ## Generating a dump file -The dump command creates a file to be used with the `bake migration_diff` -command: - -```bash -bin/cake migrations dump -``` - -Each generated dump file is specific to the Connection it is generated from (and -is suffixed as such). This allows the `bake migration_diff` command to -properly compute diff in case your application is dealing with multiple database -possibly from different database vendors. - -Dump files are created in the same directory as your migrations files. - -You can also use the `--source`, `--connection` and `--plugin` options -just like for the `migrate` command. +Dump generation is documented in +[Snapshots and Diffs](getting-started/snapshots-and-diffs). ## Using Migrations for Tests -If you are using migrations for your application schema you can also use those -same migrations to build schema in your tests. In your application's -`tests/bootstrap.php` file you can use the `Migrator` class to build schema -when tests are run. The `Migrator` will use existing schema if it is current, -and if the migration history that is in the database differs from what is in the -filesystem, all tables will be dropped and migrations will be rerun from the -beginning: - -```php -// in tests/bootstrap.php -use Migrations\TestSuite\Migrator; - -$migrator = new Migrator(); - -// Simple setup for with no plugins -$migrator->run(); - -// Run a non 'test' database -$migrator->run(['connection' => 'test_other']); - -// Run migrations for plugins -$migrator->run(['plugin' => 'Contacts']); - -// Run the Documents migrations on the test_docs connection. -$migrator->run(['plugin' => 'Documents', 'connection' => 'test_docs']); -``` - -If you need to run multiple sets of migrations, those can be run as follows: - -```php -// Run migrations for plugin Contacts on the ``test`` connection, and Documents on the ``test_docs`` connection -$migrator->runMany([ - ['plugin' => 'Contacts'], - ['plugin' => 'Documents', 'connection' => 'test_docs'] -]); -``` - -If your database also contains tables that are not managed by your application -like those created by PostGIS, then you can exclude those tables from the drop -& truncate behavior using the `skip` option: - -```php -$migrator->run(['connection' => 'test', 'skip' => ['postgis*']]); -``` - -The `skip` option accepts a `fnmatch()` compatible pattern to exclude tables -from drop & truncate operations. - -If you need to see additional debugging output from migrations are being run, -you can enable a `debug` level logger. +Test bootstrapping with `Migrations\TestSuite\Migrator` is covered in +[Integration and Deployment](advanced/integration-and-deployment). ## Using Migrations In Plugins -Plugins can also provide migration files. This makes plugins that are intended -to be distributed much more portable and easy to install. All commands in the -Migrations plugin support the `--plugin` or `-p` option that will scope the -execution to the migrations relative to that plugin: - -```bash -bin/cake migrations status -p PluginName - -bin/cake migrations migrate -p PluginName -``` +Plugin-scoped migration workflows are covered in +[Integration and Deployment](advanced/integration-and-deployment). ## Running Migrations in a non-shell environment -While typical usage of migrations is from the command line, you can also run -migrations from a non-shell environment, by using -`Migrations\Migrations` class. This can be handy in case you are developing a plugin -installer for a CMS for instance. The `Migrations` class allows you to run the -following commands from the migrations shell: - -- migrate -- rollback -- markMigrated -- status -- seed - -Each of these commands has a method defined in the `Migrations` class. - -Here is how to use it: - -```php -use Migrations\Migrations; - -$migrations = new Migrations(); - -// Will return an array of all migrations and their status -$status = $migrations->status(); - -// Will return true if success. If an error occurred, an exception will be thrown -$migrate = $migrations->migrate(); - -// Will return true if success. If an error occurred, an exception will be thrown -$rollback = $migrations->rollback(); - -// Will return true if success. If an error occurred, an exception will be thrown -$markMigrated = $migrations->markMigrated(20150804222900); - -// Will return true if success. If an error occurred, an exception will be thrown -$seeded = $migrations->seed(); -``` - -The methods can accept an array of parameters that should match options from -the commands: - -```php -use Migrations\Migrations; - -$migrations = new Migrations(); - -// Will return an array of all migrations and their status -$status = $migrations->status(['connection' => 'custom', 'source' => 'MyMigrationsFolder']); -``` - -You can pass any options the shell commands would take. -The only exception is the `markMigrated` command which is expecting the -version number of the migrations to mark as migrated as first argument. Pass -the array of parameters as the second argument for this method. - -Optionally, you can pass these parameters in the constructor of the class. -They will be used as default and this will prevent you from having to pass -them on each method call: - -```php -use Migrations\Migrations; - -$migrations = new Migrations(['connection' => 'custom', 'source' => 'MyMigrationsFolder']); - -// All the following calls will be done with the parameters passed to the Migrations class constructor -$status = $migrations->status(); -$migrate = $migrations->migrate(); -``` - -If you need to override one or more default parameters for one call, you can -pass them to the method: - -```php -use Migrations\Migrations; - -$migrations = new Migrations(['connection' => 'custom', 'source' => 'MyMigrationsFolder']); - -// This call will be made with the "custom" connection -$status = $migrations->status(); -// This one with the "default" connection -$migrate = $migrations->migrate(['connection' => 'default']); -``` - - +Programmatic execution through `Migrations\Migrations` is covered in +[Integration and Deployment](advanced/integration-and-deployment). ## Feature Flags -Migrations offers a few feature flags for compatibility. These features are disabled by default but can be enabled if required: - -- `unsigned_primary_keys`: Should Migrations create primary keys as unsigned integers? (default: `false`) -- `unsigned_ints`: Should Migrations create all integer columns as unsigned? (default: `false`) -- `column_null_default`: Should Migrations create columns as null by default? (default: `false`) -- `add_timestamps_use_datetime`: Should Migrations use `DATETIME` type - columns for the columns added by `addTimestamps()`. - -Set them via Configure to enable (e.g. in `config/app.php`): - -```text -'Migrations' => [ - 'unsigned_primary_keys' => true, - 'unsigned_ints' => true, - 'column_null_default' => true, -], -``` - -> [!NOTE] -> The `unsigned_primary_keys` and `unsigned_ints` options only affect MySQL databases. -> When generating migrations with `bake migration_snapshot` or `bake migration_diff`, -> the `signed` attribute will only be included in the output for unsigned columns -> (as `'signed' => false`). Signed is the default for integer columns in MySQL, so -> `'signed' => true` is never output. +Compatibility feature flags are covered in +[Integration and Deployment](advanced/integration-and-deployment). ## Skipping the `schema.lock` file generation -In order for the diff feature to work, a **.lock** file is generated everytime -you migrate, rollback or bake a snapshot, to keep track of the state of your -database schema at any given point in time. You can skip this file generation, -for instance when deploying on your production environment, by using the -`--no-lock` option for the aforementioned command: - -```bash -bin/cake migrations migrate --no-lock - -bin/cake migrations rollback --no-lock - -bin/cake bake migration_snapshot MyMigration --no-lock -``` +The `--no-lock` workflow is covered in +[Integration and Deployment](advanced/integration-and-deployment). ## Deployment -You should update your deployment scripts to run migrations when new code is -deployed. Ideally you want to run migrations after the code is on your servers, -but before the application code becomes active. - -After running migrations remember to clear the ORM cache so it renews the column -metadata of your tables. Otherwise, you might end up having errors about -columns not existing when performing operations on those new columns. The -CakePHP Core includes a [Schema Cache Shell](https://book.cakephp.org/5/en/console-and-shells/schema-cache.html) that you -can use to perform this operation: - -```bash -bin/cake migration migrate -bin/cake schema_cache clear -``` +Deployment guidance and schema cache refresh steps are covered in +[Integration and Deployment](advanced/integration-and-deployment). ## Alert of missing migrations -You can use the `Migrations.PendingMigrations` middleware in local development -to alert developers about new migrations that have not been applied: - -```php -use Migrations\Middleware\PendingMigrationsMiddleware; - -$config = [ - 'plugins' => [ - ... // Optionally include a list of plugins with migrations to check. - ], -]; - -$middlewareQueue - ... // ErrorHandler middleware - ->add(new PendingMigrationsMiddleware($config)) - ... // rest -``` - -You can add `'app'` config key set to `false` if you are only interested in -checking plugin migrations. - -You can temporarily disable the migration check by adding -`skip-migration-check=1` to the URL query string +Local development alerts for pending migrations are covered in +[Integration and Deployment](advanced/integration-and-deployment). ## IDE autocomplete support -The [IdeHelper plugin](https://github.com/dereuromark/cakephp-ide-helper) can help -you to get more IDE support for the tables, their column names and possible column types. -Specifically PHPStorm understands the meta information and can help you autocomplete those. +IDE-related tooling notes are covered in +[Integration and Deployment](advanced/integration-and-deployment). diff --git a/docs/en/index.rst b/docs/en/index.rst deleted file mode 100644 index f180413e9..000000000 --- a/docs/en/index.rst +++ /dev/null @@ -1,955 +0,0 @@ -Migrations 5.x -############## - -Migrations is a plugin that lets you track changes to your database schema over -time as PHP code that accompanies your application. This lets you ensure each -environment your application runs in has the appropriate schema by applying -migrations. - -Instead of writing schema modifications in SQL, this plugin allows you to -define schema changes with a high-level database portable API. - -What's New in 5.x -================= - -Migrations 5.x includes several new features: - -- **Seed tracking** - Seeds are now tracked in a ``cake_seeds`` table, preventing - accidental re-runs -- **Check constraints** - Support for database check constraints via ``addCheckConstraint()`` -- **Default values in bake** - Specify default values when baking migrations - (e.g., ``active:boolean:default[true]``) -- **MySQL ALTER options** - Control ``ALGORITHM`` and ``LOCK`` for ALTER TABLE operations -- **insertOrSkip()** - New method for idempotent seed data insertion - -See the :doc:`upgrading` guide for breaking changes and migration steps from 4.x. - -Installation -============ - -By default Migrations is installed with the application skeleton. If -you've removed it and want to re-install it, you can do so by running the -following from your application's ROOT directory (where **composer.json** file is -located): - -.. code-block:: bash - - php composer.phar require cakephp/migrations "@stable" - - # Or if composer is installed globally - composer require cakephp/migrations "@stable" - -To use the plugin you'll need to load it in your application's -**config/bootstrap.php** file. You can use `CakePHP's Plugin shell -`__ to -load and unload plugins from your **config/bootstrap.php**: - -.. code-block:: bash - - bin/cake plugin load Migrations - -Or you can load the plugin by editing your **src/Application.php** file and -adding the following statement:: - - $this->addPlugin('Migrations'); - -Additionally, you will need to configure the default database configuration for -your application in your **config/app.php** file as explained in the `Database -Configuration section -`__. - -Upgrading from 4.x -================== - -If you are upgrading from Migrations 4.x, please see the :doc:`upgrading` guide -for breaking changes and migration steps. - -Overview -======== - -A migration is a PHP file that describes the changes to apply to your database. -A migration file can add, change or remove tables, columns, indexes and foreign keys. - -If we wanted to create a table, we could use a migration similar to this:: - - table('products'); - $table->addColumn('name', 'string', [ - 'default' => null, - 'limit' => 255, - 'null' => false, - ]); - $table->addColumn('description', 'text', [ - 'default' => null, - 'null' => false, - ]); - $table->addColumn('created', 'datetime', [ - 'default' => null, - 'null' => false, - ]); - $table->addColumn('modified', 'datetime', [ - 'default' => null, - 'null' => false, - ]); - $table->create(); - } - } - -When applied, this migration will add a table to your database named -``products`` with the following column definitions: - -- ``id`` column of type ``integer`` as primary key. This column is added - implicitly, but you can customize the name and type if necessary. -- ``name`` column of type ``string`` -- ``description`` column of type ``text`` -- ``created`` column of type ``datetime`` -- ``modified`` column of type ``datetime`` - -.. note:: - - Migrations are not automatically applied, you can apply and rollback - migrations with CLI commands. - -Once the file has been created in the **config/Migrations** folder, you can -apply it: - -.. code-block:: bash - - bin/cake migrations migrate - -Creating Migrations -=================== - -Migration files are stored in the **config/Migrations** directory of your -application. The name of the migration files are prefixed with the date in -which they were created, in the format **YYYYMMDDHHMMSS_MigrationName.php**. -Here are examples of migration filenames: - -* **20160121163850_CreateProducts.php** -* **20160210133047_AddRatingToProducts.php** - -The easiest way to create a migrations file is by using ``bin/cake bake -migration`` CLI command: - -.. code-block:: bash - - bin/cake bake migration CreateProducts - -This will create an empty migration that you can edit to add any columns, -indexes and foreign keys you need. See the :ref:`creating-a-table` section to -learn more about using migrations to define tables. - -.. note:: - - Migrations need to be applied using ``bin/cake migrations migrate`` after - they have been created. - -Migration file names --------------------- - -When generating a migration, you can follow one of the following patterns -to have additional skeleton code generated: - -* ``/^(Create)(.*)/`` Creates the specified table. -* ``/^(Drop)(.*)/`` Drops the specified table. - Ignores specified field arguments -* ``/^(Add).*(?:To)(.*)/`` Adds fields to the specified - table -* ``/^(Remove).*(?:From)(.*)/`` Removes fields from the - specified table -* ``/^(Alter)(.*)/`` Alters the specified table. An alias - for CreateTable and AddField. -* ``/^(Alter).*(?:On)(.*)/`` Alters fields from the specified table. - -You can also use the ``underscore_form`` as the name for your migrations i.e. -``create_products``. - -.. warning:: - - Migration names are used as class names, and thus may collide with - other migrations if the class names are not unique. In this case, it may be - necessary to manually override the name at a later date, or simply change - the name you are specifying. - -Creating a table ----------------- - -You can use ``bake migration`` to create a table: - -.. code-block:: bash - - bin/cake bake migration CreateProducts name:string description:text created modified - -The command line above will generate a migration file that resembles:: - - table('products'); - $table->addColumn('name', 'string', [ - 'default' => null, - 'limit' => 255, - 'null' => false, - ]); - $table->addColumn('description', 'text', [ - 'default' => null, - 'null' => false, - ]); - $table->addColumn('created', 'datetime', [ - 'default' => null, - 'null' => false, - ]); - $table->addColumn('modified', 'datetime', [ - 'default' => null, - 'null' => false, - ]); - $table->create(); - } - } - -Column syntax -------------- - -The ``bake migration`` command provides a compact syntax to define columns when -generating a migration: - -.. code-block:: bash - - bin/cake bake migration CreateProducts name:string description:text created modified - -You can use the column syntax when creating tables and adding columns. You can -also edit the migration after generation to add or customize the columns - -Columns on the command line follow the following pattern:: - - fieldName:fieldType?[length]:default[value]:indexType:indexName - -For instance, the following are all valid ways of specifying an email field: - -* ``email:string?`` -* ``email:string:unique`` -* ``email:string?[50]`` -* ``email:string:unique:EMAIL_INDEX`` -* ``email:string[120]:unique:EMAIL_INDEX`` - -While defining decimal columns, the ``length`` can be defined to have precision -and scale, separated by a comma. - -* ``amount:decimal[5,2]`` -* ``amount:decimal?[5,2]`` - -Columns with a question mark after the fieldType will make the column nullable. - -The ``length`` part is optional and should always be written between bracket. - -The ``default[value]`` part is optional and sets the default value for the column. -Supported value types include: - -* Booleans: ``true`` or ``false`` - e.g., ``active:boolean:default[true]`` -* Integers: ``0``, ``123``, ``-456`` - e.g., ``count:integer:default[0]`` -* Floats: ``1.5``, ``-2.75`` - e.g., ``rate:decimal:default[1.5]`` -* Strings: ``'hello'`` or ``"world"`` (quoted) - e.g., ``status:string:default['pending']`` -* Null: ``null`` or ``NULL`` - e.g., ``description:text?:default[null]`` -* SQL expressions: ``CURRENT_TIMESTAMP`` - e.g., ``created_at:datetime:default[CURRENT_TIMESTAMP]`` - -Fields named ``created`` and ``modified``, as well as any field with a ``_at`` -suffix, will automatically be set to the type ``datetime``. - -There are some heuristics to choosing fieldtypes when left unspecified or set to -an invalid value. Default field type is ``string``: - -* id: integer -* created, modified, updated: datetime -* latitude, longitude (or short forms lat, lng): decimal - -Additionally you can create an empty migrations file if you want full control -over what needs to be executed, by omitting to specify a columns definition: - -.. code-block:: bash - - bin/cake migrations create MyCustomMigration - - -See :doc:`writing-migrations` for more information on how to use ``Table`` -objects to interact with tables and define schema changes. - -Adding columns to an existing table ------------------------------------ - -If the migration name in the command line is of the form "AddXXXToYYY" and is -followed by a list of column names and types then a migration file containing -the code for creating the columns will be generated: - -.. code-block:: bash - - bin/cake bake migration AddPriceToProducts price:decimal[5,2] - -Executing the command line above will generate:: - - table('products'); - $table->addColumn('price', 'decimal', [ - 'default' => null, - 'null' => false, - 'precision' => 5, - 'scale' => 2, - ]); - $table->update(); - } - } - -Adding a column with an index ------------------------------ - -It is also possible to add indexes to columns: - -.. code-block:: bash - - bin/cake bake migration AddNameIndexToProducts name:string:index - -will generate:: - - table('products'); - $table->addColumn('name', 'string') - ->addColumn('email', 'string') - ->addIndex(['name']) - // add a unique index: - ->addIndex('email', ['unique' => true]) - ->update(); - } - } - -Adding a column with a default value -------------------------------------- - -You can specify default values for columns using the ``default[value]`` syntax: - -.. code-block:: bash - - bin/cake bake migration AddActiveToUsers active:boolean:default[true] - -will generate:: - - table('users'); - $table->addColumn('active', 'boolean', [ - 'default' => true, - 'null' => false, - ]); - $table->update(); - } - } - -You can combine default values with other options like nullable and indexes: - -.. code-block:: bash - - bin/cake bake migration AddStatusToOrders status:string:default['pending']:unique - -Altering a column ------------------ - -In the same way, you can generate a migration to alter a column by using the -command line, if the migration name is of the form "AlterXXXOnYYY": - -.. code-block:: bash - - bin/cake bake migration AlterPriceOnProducts name:float - -will generate:: - - table('products'); - $table->changeColumn('name', 'float'); - $table->update(); - } - } - -.. warning:: - - Changing the type of a column can result in data loss if the - current and target column type are not compatible. For example converting - a varchar to float. - -Removing a column ------------------ - -In the same way, you can generate a migration to remove a column by using the -command line, if the migration name is of the form "RemoveXXXFromYYY": - -.. code-block:: bash - - bin/cake bake migration RemovePriceFromProducts price - -creates the file:: - - table('products'); - $table->removeColumn('price') - ->save(); - } - } - -.. note:: - - The `removeColumn` command is not reversible, so must be called in the - `up` method. A corresponding `addColumn` call should be added to the - `down` method. - -Generating migration snapshots from an existing database -======================================================== - -If you have a pre-existing database and want to start using -migrations, or to version control the initial schema of your application's -database, you can run the ``bake migration_snapshot`` command: - -.. code-block:: bash - - bin/cake bake migration_snapshot Initial - -It will generate a migration file called **YYYYMMDDHHMMSS_Initial.php** -containing all the create statements for all tables in your database. - -By default, the snapshot will be created by connecting to the database defined -in the ``default`` connection configuration. If you need to bake a snapshot from -a different datasource, you can use the ``--connection`` option: - -.. code-block:: bash - - bin/cake bake migration_snapshot Initial --connection my_other_connection - -You can also make sure the snapshot includes only the tables for which you have -defined the corresponding model classes by using the ``--require-table`` flag: - -.. code-block:: bash - - bin/cake bake migration_snapshot Initial --require-table - -When using the ``--require-table`` flag, the shell will look through your -application ``Table`` classes and will only add the model tables in the snapshot. - -If you want to generate a snapshot without marking it as migrated (for example, -for use in unit tests), you can use the ``--generate-only`` flag: - -.. code-block:: bash - - bin/cake bake migration_snapshot Initial --generate-only - -This will create the migration file but will not add an entry to the migrations -tracking table, allowing you to move the file to a different location without causing -"MISSING" status issues. - -The same logic will be applied implicitly if you wish to bake a snapshot for a -plugin. To do so, you need to use the ``--plugin`` option: - -.. code-block:: bash - - bin/cake bake migration_snapshot Initial --plugin MyPlugin - -Only the tables which have a ``Table`` object model class defined will be added -to the snapshot of your plugin. - -.. note:: - - When baking a snapshot for a plugin, the migration files will be created - in your plugin's **config/Migrations** directory. - -Be aware that when you bake a snapshot, it is automatically added to the -migrations log table as migrated. - -Generating a diff -================= - -As migrations are applied and rolled back, the migrations plugin will generate -a 'dump' file of your schema. If you make manual changes to your database schema -outside of migrations, you can use ``bake migration_diff`` to generate -a migration file that captures the difference between the current schema dump -file and database schema. To do so, you can use the following command: - -.. code-block:: bash - - bin/cake bake migration_diff NameOfTheMigrations - -By default, the diff will be created by connecting to the database defined -in the ``default`` connection configuration. -If you need to bake a diff from a different datasource, you can use the -``--connection`` option: - -.. code-block:: bash - - bin/cake bake migration_diff NameOfTheMigrations --connection my_other_connection - -If you want to use the diff feature on an application that already has a -migrations history, you need to manually create the dump file that will be used -as comparison: - -.. code-block:: bash - - bin/cake migrations dump - -The database state must be the same as it would be if you just migrated all -your migrations before you create a dump file. -Once the dump file is generated, you can start doing changes in your database -and use the ``bake migration_diff`` command whenever you see fit. - -.. note:: - - Migration diff generation can not detect column renamings. - -Applying Migrations -=================== - -Once you have generated or written your migration file, you need to execute the -following command to apply the changes to your database: - -.. code-block:: bash - - # Run all the migrations - bin/cake migrations migrate - - # Migrate to a specific version using the ``--target`` option - # or ``-t`` for short. - # The value is the timestamp that is prefixed to the migrations file name:: - bin/cake migrations migrate -t 20150103081132 - - # By default, migration files are looked for in the **config/Migrations** - # directory. You can specify the directory using the ``--source`` option - # or ``-s`` for short. - # The following example will run migrations in the **config/Alternate** - # directory - bin/cake migrations migrate -s Alternate - - # You can run migrations to a different connection than the ``default`` one - # using the ``--connection`` option or ``-c`` for short - bin/cake migrations migrate -c my_custom_connection - - # Migrations can also be run for plugins. Simply use the ``--plugin`` option - # or ``-p`` for short - bin/cake migrations migrate -p MyAwesomePlugin - -Reverting Migrations -==================== - -The rollback command is used to undo previous migrations executed by this -plugin. It is the reverse action of the ``migrate`` command: - -.. code-block:: bash - - # You can rollback to the previous migration by using the - # ``rollback`` command:: - bin/cake migrations rollback - - # You can also pass a migration version number to rollback - # to a specific version:: - bin/cake migrations rollback -t 20150103081132 - -You can also use the ``--source``, ``--connection`` and ``--plugin`` options -just like for the ``migrate`` command. - -View Migrations Status -====================== - -The Status command prints a list of all migrations, along with their current -status. You can use this command to determine which migrations have been run: - -.. code-block:: bash - - bin/cake migrations status - -You can also output the results as a JSON formatted string using the -``--format`` option (or ``-f`` for short): - -.. code-block:: bash - - bin/cake migrations status --format json - -You can also use the ``--source``, ``--connection`` and ``--plugin`` options -just like for the ``migrate`` command. - -Cleaning up missing migrations -------------------------------- - -Sometimes migration files may be deleted from the filesystem but still exist -in the migrations tracking table. These migrations will be marked as **MISSING** in the -status output. You can remove these entries from the tracking table using the -``--cleanup`` option: - -.. code-block:: bash - - bin/cake migrations status --cleanup - -This will remove all migration entries from the tracking table that no longer -have corresponding migration files in the filesystem. - -Marking a migration as migrated -=============================== - -It can sometimes be useful to mark a set of migrations as migrated without -actually running them. In order to do this, you can use the ``mark_migrated`` -command. The command works seamlessly as the other commands. - -You can mark all migrations as migrated using this command: - -.. code-block:: bash - - bin/cake migrations mark_migrated - -You can also mark all migrations up to a specific version as migrated using -the ``--target`` option: - -.. code-block:: bash - - bin/cake migrations mark_migrated --target=20151016204000 - -If you do not want the targeted migration to be marked as migrated during the -process, you can use the ``--exclude`` flag with it: - -.. code-block:: bash - - bin/cake migrations mark_migrated --target=20151016204000 --exclude - -Finally, if you wish to mark only the targeted migration as migrated, you can -use the ``--only`` flag: - -.. code-block:: bash - - bin/cake migrations mark_migrated --target=20151016204000 --only - -You can also use the ``--source``, ``--connection`` and ``--plugin`` options -just like for the ``migrate`` command. - -.. note:: - - When you bake a snapshot with the ``cake bake migration_snapshot`` - command, the created migration will automatically be marked as migrated. - To prevent this behavior (e.g., for unit test migrations), use the - ``--generate-only`` flag. - -This command expects the migration version number as argument: - -.. code-block:: bash - - bin/cake migrations mark_migrated 20150420082532 - -If you wish to mark all migrations as migrated, you can use the ``all`` special -value. If you use it, it will mark all found migrations as migrated: - -.. code-block:: bash - - bin/cake migrations mark_migrated all - -Seeding your database -===================== - -Seed classes are a good way to populate your database with default or starter -data. They are also a great way to generate data for development environments. - -By default, seeds will be looked for in the ``config/Seeds/`` directory of -your application. See the :doc:`seeding` for how to build and use seed classes. - -Generating a dump file -====================== - -The dump command creates a file to be used with the ``bake migration_diff`` -command: - -.. code-block:: bash - - bin/cake migrations dump - -Each generated dump file is specific to the Connection it is generated from (and -is suffixed as such). This allows the ``bake migration_diff`` command to -properly compute diff in case your application is dealing with multiple database -possibly from different database vendors. - -Dump files are created in the same directory as your migrations files. - -You can also use the ``--source``, ``--connection`` and ``--plugin`` options -just like for the ``migrate`` command. - - -Using Migrations for Tests -========================== - -If you are using migrations for your application schema you can also use those -same migrations to build schema in your tests. In your application's -``tests/bootstrap.php`` file you can use the ``Migrator`` class to build schema -when tests are run. The ``Migrator`` will use existing schema if it is current, -and if the migration history that is in the database differs from what is in the -filesystem, all tables will be dropped and migrations will be rerun from the -beginning:: - - // in tests/bootstrap.php - use Migrations\TestSuite\Migrator; - - $migrator = new Migrator(); - - // Simple setup for with no plugins - $migrator->run(); - - // Run a non 'test' database - $migrator->run(['connection' => 'test_other']); - - // Run migrations for plugins - $migrator->run(['plugin' => 'Contacts']); - - // Run the Documents migrations on the test_docs connection. - $migrator->run(['plugin' => 'Documents', 'connection' => 'test_docs']); - - -If you need to run multiple sets of migrations, those can be run as follows:: - - // Run migrations for plugin Contacts on the ``test`` connection, and Documents on the ``test_docs`` connection - $migrator->runMany([ - ['plugin' => 'Contacts'], - ['plugin' => 'Documents', 'connection' => 'test_docs'] - ]); - -If your database also contains tables that are not managed by your application -like those created by PostGIS, then you can exclude those tables from the drop -& truncate behavior using the ``skip`` option:: - - $migrator->run(['connection' => 'test', 'skip' => ['postgis*']]); - -The ``skip`` option accepts a ``fnmatch()`` compatible pattern to exclude tables -from drop & truncate operations. - -If you need to see additional debugging output from migrations are being run, -you can enable a ``debug`` level logger. - -Using Migrations In Plugins -=========================== - -Plugins can also provide migration files. This makes plugins that are intended -to be distributed much more portable and easy to install. All commands in the -Migrations plugin support the ``--plugin`` or ``-p`` option that will scope the -execution to the migrations relative to that plugin: - -.. code-block:: bash - - bin/cake migrations status -p PluginName - - bin/cake migrations migrate -p PluginName - -Running Migrations in a non-shell environment -============================================= - -While typical usage of migrations is from the command line, you can also run -migrations from a non-shell environment, by using -``Migrations\Migrations`` class. This can be handy in case you are developing a plugin -installer for a CMS for instance. The ``Migrations`` class allows you to run the -following commands from the migrations shell: - -* migrate -* rollback -* markMigrated -* status -* seed - -Each of these commands has a method defined in the ``Migrations`` class. - -Here is how to use it:: - - use Migrations\Migrations; - - $migrations = new Migrations(); - - // Will return an array of all migrations and their status - $status = $migrations->status(); - - // Will return true if success. If an error occurred, an exception will be thrown - $migrate = $migrations->migrate(); - - // Will return true if success. If an error occurred, an exception will be thrown - $rollback = $migrations->rollback(); - - // Will return true if success. If an error occurred, an exception will be thrown - $markMigrated = $migrations->markMigrated(20150804222900); - - // Will return true if success. If an error occurred, an exception will be thrown - $seeded = $migrations->seed(); - -The methods can accept an array of parameters that should match options from -the commands:: - - use Migrations\Migrations; - - $migrations = new Migrations(); - - // Will return an array of all migrations and their status - $status = $migrations->status(['connection' => 'custom', 'source' => 'MyMigrationsFolder']); - -You can pass any options the shell commands would take. -The only exception is the ``markMigrated`` command which is expecting the -version number of the migrations to mark as migrated as first argument. Pass -the array of parameters as the second argument for this method. - -Optionally, you can pass these parameters in the constructor of the class. -They will be used as default and this will prevent you from having to pass -them on each method call:: - - use Migrations\Migrations; - - $migrations = new Migrations(['connection' => 'custom', 'source' => 'MyMigrationsFolder']); - - // All the following calls will be done with the parameters passed to the Migrations class constructor - $status = $migrations->status(); - $migrate = $migrations->migrate(); - -If you need to override one or more default parameters for one call, you can -pass them to the method:: - - use Migrations\Migrations; - - $migrations = new Migrations(['connection' => 'custom', 'source' => 'MyMigrationsFolder']); - - // This call will be made with the "custom" connection - $status = $migrations->status(); - // This one with the "default" connection - $migrate = $migrations->migrate(['connection' => 'default']); - -.. _feature-flags: - -Feature Flags -============= - -Migrations offers a few feature flags for compatibility. These features are disabled by default but can be enabled if required: - -* ``unsigned_primary_keys``: Should Migrations create primary keys as unsigned integers? (default: ``false``) -* ``unsigned_ints``: Should Migrations create all integer columns as unsigned? (default: ``false``) -* ``column_null_default``: Should Migrations create columns as null by default? (default: ``false``) -* ``add_timestamps_use_datetime``: Should Migrations use ``DATETIME`` type - columns for the columns added by ``addTimestamps()``. - -Set them via Configure to enable (e.g. in ``config/app.php``):: - - 'Migrations' => [ - 'unsigned_primary_keys' => true, - 'unsigned_ints' => true, - 'column_null_default' => true, - ], - -.. note:: - - The ``unsigned_primary_keys`` and ``unsigned_ints`` options only affect MySQL databases. - When generating migrations with ``bake migration_snapshot`` or ``bake migration_diff``, - the ``signed`` attribute will only be included in the output for unsigned columns - (as ``'signed' => false``). Signed is the default for integer columns in MySQL, so - ``'signed' => true`` is never output. - -Skipping the ``schema.lock`` file generation -============================================ - -In order for the diff feature to work, a **.lock** file is generated everytime -you migrate, rollback or bake a snapshot, to keep track of the state of your -database schema at any given point in time. You can skip this file generation, -for instance when deploying on your production environment, by using the -``--no-lock`` option for the aforementioned command: - -.. code-block:: bash - - bin/cake migrations migrate --no-lock - - bin/cake migrations rollback --no-lock - - bin/cake bake migration_snapshot MyMigration --no-lock - -Deployment -========== - -You should update your deployment scripts to run migrations when new code is -deployed. Ideally you want to run migrations after the code is on your servers, -but before the application code becomes active. - -After running migrations remember to clear the ORM cache so it renews the column -metadata of your tables. Otherwise, you might end up having errors about -columns not existing when performing operations on those new columns. The -CakePHP Core includes a `Schema Cache Shell -`__ that you -can use to perform this operation: - -.. code-block:: bash - - bin/cake migration migrate - bin/cake schema_cache clear - -Alert of missing migrations -=========================== - -You can use the ``Migrations.PendingMigrations`` middleware in local development -to alert developers about new migrations that have not been applied:: - - use Migrations\Middleware\PendingMigrationsMiddleware; - - $config = [ - 'plugins' => [ - ... // Optionally include a list of plugins with migrations to check. - ], - ]; - - $middlewareQueue - ... // ErrorHandler middleware - ->add(new PendingMigrationsMiddleware($config)) - ... // rest - -You can add ``'app'`` config key set to ``false`` if you are only interested in -checking plugin migrations. - -You can temporarily disable the migration check by adding -``skip-migration-check=1`` to the URL query string - -IDE autocomplete support -======================== - -The `IdeHelper plugin -`__ can help -you to get more IDE support for the tables, their column names and possible column types. -Specifically PHPStorm understands the meta information and can help you autocomplete those. diff --git a/docs/en/seeding.rst b/docs/en/seeding.rst deleted file mode 100644 index d2aedf34b..000000000 --- a/docs/en/seeding.rst +++ /dev/null @@ -1,616 +0,0 @@ -Database Seeding -################ - -Seed classes are a great way to easily fill your database with data after -it's created. By default, they are stored in the ``config/Seeds`` directory. - -.. note:: - - Database seeding is entirely optional, and Migrations does not create a Seeds - directory by default. - -Creating a New Seed Class -========================= - -Migrations includes a command to easily generate a new seed class: - -.. code-block:: bash - - $ bin/cake bake seed MyNewSeed - -By default, it generates a traditional seed class with a named class: - -.. code-block:: php - - call('Articles');`` or ``$this->call('ArticlesSeed');`` - -Using the short name is recommended for cleaner, more concise code. - -Anonymous Seed Classes ----------------------- - -Migrations also supports generating anonymous seed classes, which use PHP's -anonymous class feature instead of named classes. This style is useful for: - -- Avoiding namespace declarations -- Better PHPCS compatibility (no class name to filename matching required) -- Simpler file structure without named class constraints - -To generate an anonymous seed class, use the ``--style anonymous`` option: - -.. code-block:: bash - - $ bin/cake bake seed MyNewSeed --style anonymous - -This generates a seed file using an anonymous class: - -.. code-block:: php - - [ - 'style' => 'anonymous', // or 'traditional' - ], - -Seed Options ------------- - -.. code-block:: bash - # You specify the name of the table the seed files will alter by using the ``--table`` option - bin/cake bake seed Articles --table my_articles_table - - # You can specify a plugin to bake into - bin/cake bake seed Articles --plugin PluginName - - # You can specify an alternative connection when generating a seed. - bin/cake bake seed Articles --connection connection - - # Include data from the Articles table in your seed. - bin/cake bake seed --data Articles - -By default, it will export all the rows found in your table. You can limit the -number of rows exported by using the ``--limit`` option: - -.. code-block:: bash - - # Will only export the first 10 rows found - bin/cake bake seed --data --limit 10 Articles - -If you only want to include a selection of fields from the table in your seed -file, you can use the ``--fields`` option. It takes the list of fields to -include as a comma separated value string: - -.. code-block:: bash - - # Will only export the fields `id`, `title` and `excerpt` - bin/cake bake seed --data --fields id,title,excerpt Articles - -.. tip:: - - Of course you can use both the ``--limit`` and ``--fields`` options in the - same command call. - -.. _custom-seed-migration-templates: - -Customizing Seed and Migration templates ----------------------------------------- - -Because migrations uses `bake `__ under the hood -you can customize the templates that migrations uses for creating seeds and -migrations by creating templates in your application. Custom templates for -migrations should be on one of the following paths: - -- ``ROOT/templates/plugin/Migrations/bake/`` -- ``ROOT/templates/bake/`` - -For example, the seed templates are: - -- Traditional: ``Seed/seed.twig`` at **ROOT/templates/plugin/Migrations/bake/Seed/seed.twig** -- Anonymous: ``Seed/seed-anonymous.twig`` at **ROOT/templates/plugin/Migrations/bake/Seed/seed-anonymous.twig** - -The BaseSeed Class -================== - -All Migrations seeds extend from the ``BaseSeed`` class. -It provides the necessary support to create your seed classes. Seed -classes are primarily used to insert test data. - -The Run Method -============== - -The run method is automatically invoked by Migrations when you execute the -``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:: - - 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: - -.. code-block:: php - - 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 run **every time** you execute ``seeds run`` -- The last execution time is still tracked in the ``cake_seeds`` table -- The ``seeds status`` command will show the seed as ``(idempotent)`` -- 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 -=============== - -The ``init()`` method is run by Migrations before the run method if it exists. This -can be used to initialize properties of the Seed class before using run. - -The Should Execute Method -========================= - -The ``shouldExecute()`` method is run by Migrations before executing the seed. -This can be used to prevent the seed from being executed at this time. It always -returns true by default. You can override it in your custom ``BaseSeed`` -implementation. - -Foreign Key Dependencies -======================== - -Often you'll find that seeds need to run in a particular order, so they don't -violate foreign key constraints. To define this order, you can implement the -``getDependencies()`` method that returns an array of seeds to run before the -current seed: - -.. code-block:: php - - call('Another'); // Short name without 'Seed' suffix - $this->call('YetAnother'); // Short name without 'Seed' suffix - - // You can use the plugin dot syntax to call seeds from a plugin - $this->call('PluginName.FromPlugin'); - } - } - -You can also use the full seed name including the ``Seed`` suffix: - -.. code-block:: php - - $this->call('AnotherSeed'); - $this->call('YetAnotherSeed'); - $this->call('PluginName.FromPluginSeed'); - -Both forms are supported and work identically. - -Inserting Data -============== - -Seed classes can also use the familiar ``Table`` object to insert data. You can -retrieve an instance of the Table object by calling the ``table()`` method from -within your seed class and then use the ``insert()`` method to insert data: - -.. code-block:: php - - 'foo', - 'created' => date('Y-m-d H:i:s'), - ],[ - 'body' => 'bar', - 'created' => date('Y-m-d H:i:s'), - ] - ]; - - $posts = $this->table('posts'); - $posts->insert($data) - ->saveData(); - } - } - -.. note:: - - You must call the ``saveData()`` method to commit your data to the table. - Migrations will buffer data until you do so. - -Insert Modes -============ - -In addition to the standard ``insert()`` method, Migrations provides specialized -insert methods for handling conflicts with existing data. - -Insert or Skip --------------- - -The ``insertOrSkip()`` method inserts rows but silently skips any that would -violate a unique constraint: - -.. code-block:: php - - 'USD', 'name' => 'US Dollar'], - ['code' => 'EUR', 'name' => 'Euro'], - ]; - - $this->table('currencies') - ->insertOrSkip($data) - ->saveData(); - } - } - -Insert or Update (Upsert) -------------------------- - -The ``insertOrUpdate()`` method performs an "upsert" operation - inserting new -rows and updating existing rows that conflict on unique columns: - -.. code-block:: php - - 'USD', 'rate' => 1.0000], - ['code' => 'EUR', 'rate' => 0.9234], - ]; - - $this->table('exchange_rates') - ->insertOrUpdate($data, ['rate'], ['code']) - ->saveData(); - } - } - -The method takes three arguments: - -- ``$data``: The rows to insert (same format as ``insert()``) -- ``$updateColumns``: Which columns to update when a conflict occurs -- ``$conflictColumns``: Which columns define uniqueness (must have a unique index) - -.. warning:: - - Database-specific behavior differences: - - **MySQL**: Uses ``ON DUPLICATE KEY UPDATE``. The ``$conflictColumns`` parameter - is ignored because MySQL automatically applies the update to *all* unique - constraint violations on the table. Passing ``$conflictColumns`` will trigger - a warning. If your table has multiple unique constraints, be aware that a - conflict on *any* of them will trigger the update. - - **PostgreSQL/SQLite**: Uses ``ON CONFLICT (...) DO UPDATE SET``. The - ``$conflictColumns`` parameter is required and specifies exactly which unique - constraint should trigger the update. A ``RuntimeException`` will be thrown - if this parameter is empty. - - **SQL Server**: Not currently supported. Use separate insert/update logic. - -Truncating Tables -================= - -In addition to inserting data Migrations makes it trivial to empty your tables using the -SQL `TRUNCATE` command: - -.. code-block:: php - - 'foo', - 'created' => date('Y-m-d H:i:s'), - ], - [ - 'body' => 'bar', - 'created' => date('Y-m-d H:i:s'), - ] - ]; - - $posts = $this->table('posts'); - $posts->insert($data) - ->saveData(); - - // empty the table - $posts->truncate(); - } - } - -.. note:: - - SQLite doesn't natively support the ``TRUNCATE`` command so behind the scenes - ``DELETE FROM`` is used. It is recommended to call the ``VACUUM`` command - after truncating a table. Migrations does not do this automatically. - -Executing Seed Classes -====================== - -This is the easy part. To seed your database, simply use the ``seeds run`` command: - -.. code-block:: bash - - $ bin/cake seeds run - -By default, Migrations will execute all available seed classes. If you would like to -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 seeds run User - # or - $ bin/cake seeds run UserSeed - -Both commands work identically. - -You can also run multiple seeds by separating them with commas: - -.. code-block:: bash - - $ bin/cake seeds run User,Permission,Log - # or with full names - $ bin/cake seeds run UserSeed,PermissionSeed,LogSeed - -You can also use the `-v` parameter for more output verbosity: - -.. code-block:: bash - - $ 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 -sample data or getting state for demos. diff --git a/docs/en/upgrading.md b/docs/en/upgrades/upgrading-from-4-x.md similarity index 97% rename from docs/en/upgrading.md rename to docs/en/upgrades/upgrading-from-4-x.md index c34d6c36a..0786baa8b 100644 --- a/docs/en/upgrading.md +++ b/docs/en/upgrades/upgrading-from-4-x.md @@ -103,12 +103,12 @@ $row = $stmt->fetch('assoc'); Seeds are now tracked in a `cake_seeds` table by default, preventing accidental re-runs. Use `--force` to run a seed again, or `bin/cake seeds reset` to clear tracking. -See [Database Seeding](seeding) for more details. +See [Database Seeding](../guides/seeding) for more details. ### Check Constraints Support for database check constraints via `addCheckConstraint()`. -See [Writing Migrations](writing-migrations) for usage details. +See [Writing Migrations](../guides/writing-migrations) for usage details. ### MySQL ALTER Options diff --git a/docs/en/upgrading-to-builtin-backend.md b/docs/en/upgrades/upgrading-to-builtin-backend.md similarity index 100% rename from docs/en/upgrading-to-builtin-backend.md rename to docs/en/upgrades/upgrading-to-builtin-backend.md diff --git a/docs/en/upgrading.rst b/docs/en/upgrading.rst deleted file mode 100644 index d413bff46..000000000 --- a/docs/en/upgrading.rst +++ /dev/null @@ -1,174 +0,0 @@ -Upgrading from 4.x to 5.x -######################### - -Migrations 5.x includes significant changes from 4.x. This guide outlines -the breaking changes and what you need to update when upgrading. - -Requirements -============ - -- **PHP 8.2+** is now required (was PHP 8.1+) -- **CakePHP 5.3+** is now required -- **Phinx has been removed** - The builtin backend is now the only supported backend - -If you were already using the builtin backend in 4.x (introduced in 4.3, default in 4.4), -the upgrade should be straightforward. See :doc:`upgrading-to-builtin-backend` for more -details on API differences between the phinx and builtin backends. - -Command Changes -=============== - -The phinx wrapper commands have been removed. The new command structure is: - -Migrations ----------- - -The migration commands remain unchanged: - -.. code-block:: bash - - bin/cake migrations migrate - bin/cake migrations rollback - bin/cake migrations status - bin/cake migrations mark_migrated - bin/cake migrations dump - -Seeds ------ - -Seed commands have changed: - -.. code-block:: bash - - # 4.x # 5.x - bin/cake migrations seed bin/cake seeds run - bin/cake migrations seed --seed X bin/cake seeds run X - -The new seed commands are: - -- ``bin/cake seeds run`` - Run seed classes -- ``bin/cake seeds run SeedName`` - Run a specific seed -- ``bin/cake seeds status`` - Show seed execution status -- ``bin/cake seeds reset`` - Reset seed execution tracking - -Maintaining Backward Compatibility ----------------------------------- - -If you need to maintain the old ``migrations seed`` command for existing scripts or -CI/CD pipelines, you can add command aliases in your ``src/Application.php``:: - - public function console(CommandCollection $commands): CommandCollection - { - $commands = $this->addConsoleCommands($commands); - - // Add backward compatibility alias - $commands->add('migrations seed', \Migrations\Command\SeedCommand::class); - - return $commands; - } - -Removed Classes and Namespaces -============================== - -The following have been removed in 5.x: - -- ``Migrations\Command\Phinx\*`` - All phinx wrapper commands -- ``Migrations\Command\MigrationsCommand`` - Use ``bin/cake migrations`` entry point -- ``Migrations\Command\MigrationsSeedCommand`` - Use ``bin/cake seeds run`` -- ``Migrations\Command\MigrationsCacheBuildCommand`` - Schema cache is managed differently -- ``Migrations\Command\MigrationsCacheClearCommand`` - Schema cache is managed differently -- ``Migrations\Command\MigrationsCreateCommand`` - Use ``bin/cake bake migration`` - -If you have code that directly references any of these classes, you will need to update it. - -API Changes -=========== - -Adapter Query Results ---------------------- - -If your migrations use ``AdapterInterface::query()`` to fetch rows, the return type has -changed from a phinx result to ``Cake\Database\StatementInterface``:: - - // 4.x (phinx) - $stmt = $this->getAdapter()->query('SELECT * FROM articles'); - $rows = $stmt->fetchAll(); - $row = $stmt->fetch(); - - // 5.x (builtin) - $stmt = $this->getAdapter()->query('SELECT * FROM articles'); - $rows = $stmt->fetchAll('assoc'); - $row = $stmt->fetch('assoc'); - -New Features in 5.x -=================== - -5.x includes several new features: - -Seed Tracking -------------- - -Seeds are now tracked in a ``cake_seeds`` table by default, preventing accidental re-runs. -Use ``--force`` to run a seed again, or ``bin/cake seeds reset`` to clear tracking. -See :doc:`seeding` for more details. - -Check Constraints ------------------ - -Support for database check constraints via ``addCheckConstraint()``. -See :doc:`writing-migrations` for usage details. - -MySQL ALTER Options -------------------- - -Support for ``ALGORITHM`` and ``LOCK`` options on MySQL ALTER TABLE operations, -allowing control over how MySQL performs schema changes. - -insertOrSkip() for Seeds ------------------------- - -New ``insertOrSkip()`` method for seeds to insert records only if they don't already exist, -making seeds more idempotent. - -Foreign Key Constraint Naming -============================= - -Starting in 5.x, when you use ``addForeignKey()`` without providing an explicit constraint -name, migrations will auto-generate a name using the pattern ``{table}_{columns}``. - -Previously, MySQL would auto-generate constraint names (like ``articles_ibfk_1``), while -PostgreSQL and SQL Server used migrations-generated names. Now all adapters use the same -consistent naming pattern. - -**Impact on existing migrations:** - -If you have existing migrations that use ``addForeignKey()`` without explicit names, and -later migrations that reference those constraints by name (e.g., in ``dropForeignKey()``), -the generated names may differ between old and new migrations. This could cause -``dropForeignKey()`` to fail if it's looking for a name that doesn't exist. - -**Recommendations:** - -1. For new migrations, you can rely on auto-generated names or provide explicit names -2. If you have rollback issues with existing migrations, you may need to update them - to use explicit constraint names -3. The auto-generated names include conflict resolution - if ``{table}_{columns}`` already - exists, a counter suffix is added (``_2``, ``_3``, etc.) - -**Name length limits:** - -Auto-generated names are truncated to respect database limits: - -- MySQL: 61 characters (64 - 3 for counter suffix) -- PostgreSQL: 60 characters (63 - 3) -- SQL Server: 125 characters (128 - 3) -- SQLite: No limit - -Migration File Compatibility -============================ - -Your existing migration files should work without changes in most cases. The builtin backend -provides the same API as phinx for common operations. - -If you encounter issues with existing migrations, please report them at -https://github.com/cakephp/migrations/issues diff --git a/docs/en/writing-migrations.md b/docs/en/writing-migrations.md deleted file mode 100644 index fd2fd88bb..000000000 --- a/docs/en/writing-migrations.md +++ /dev/null @@ -1,2527 +0,0 @@ -# Writing Migrations - -Migrations are a declarative API that helps you transform your database. Each migration -is represented by a PHP class in a unique file. It is preferred that you write -your migrations using the Migrations API, but raw SQL is also supported. - -## Creating a New Migration - -Let's start by creating a new migration with `bake`: - -```bash -bin/cake bake migration -``` - -This will create a new migration in the format -`YYYYMMDDHHMMSS_my_new_migration.php`, where the first 14 characters are -replaced with the current timestamp down to the second. - -If you have specified multiple migration paths, you will be asked to select -which path to create the new migration in. - -Bake will automatically creates a skeleton migration file with a single method: - -```php - [ - 'style' => 'anonymous', // or 'traditional' -], -``` - -This configuration also applies to seeds, allowing you to use consistent styling -across your entire project. - -## The Change Method - -Migrations supports 'reversible migrations'. In many scenarios, you -only need to define the `up` logic, and Migrations can figure out how to -generate the rollback operations for you. For example: - -```php -table('user_logins'); - $table->addColumn('user_id', 'integer') - ->addColumn('created', 'datetime') - ->create(); - } -} -``` - -When executing this migration, Migrations will create the `user_logins` table on -the way up and automatically figure out how to drop the table on the way down. -Please be aware that when a `change` method exists, Migrations will -ignore the `up` and `down` methods. If you need to use these methods it is -recommended to create a separate migration file. - -> [!NOTE] -> When creating or updating tables inside a `change()` method you must use -> the Table `create()` and `update()` methods. Migrations cannot automatically -> determine whether a `save()` call is creating a new table or modifying an -> existing one. - -The following actions are reversible when done through the Table API in -Migrations, and will be automatically reversed: - -- Creating a table -- Renaming a table -- Adding a column -- Renaming a column -- Adding an index -- Adding a foreign key -- Adding a check constraint - -If a command cannot be reversed then Migrations will throw an -`IrreversibleMigrationException` when it's migrating down. If you wish to -use a command that cannot be reversed in the change function, you can use an -if statement with `$this->isMigratingUp()` to only run things in the -up or down direction. For example: - -```php -table('user_logins'); - $table->addColumn('user_id', 'integer') - ->addColumn('created', 'datetime') - ->create(); - if ($this->isMigratingUp()) { - $table->insert([['user_id' => 1, 'created' => '2020-01-19 03:14:07']]) - ->save(); - } - } -} -``` - -## The Up Method - -The up method is automatically run by Migrations when you are migrating up and it -detects the given migration hasn't been executed previously. You should use the -up method to transform the database with your intended changes. - -## The Down Method - -The down method is automatically run by Migrations when you are migrating down and -it detects the given migration has been executed in the past. You should use -the down method to reverse/undo the transformations described in the up method. - -## The Init Method - -The `init()` method is run by Migrations before the migration methods if it exists. -This can be used for setting common class properties that are then used within -the migration methods. - -## The Should Execute Method - -The `shouldExecute()` method is run by Migrations before executing the migration. -This can be used to prevent the migration from being executed at this time. It always -returns true by default. You can override it in your custom `BaseMigration` -implementation. - -## Working With Tables - -The Table object enables you to easily manipulate database tables using PHP -code. You can retrieve an instance of the Table object by calling the -`table()` method from within your database migration: - -```php -table('tableName'); - } - - /** - * Migrate Down. - */ - public function down(): void - { - - } -} -``` - -You can then manipulate this table using the methods provided by the Table -object. - - - -## Adding Columns - -Column types are specified as strings and can be one of: - -- binary -- boolean -- char -- date -- datetime -- decimal -- float -- double -- smallinteger -- integer -- biginteger -- string -- text -- time -- timestamp -- uuid -- binaryuuid -- nativeuuid - -In addition, the MySQL adapter supports `enum`, `set`, `blob`, -`tinyblob`, `mediumblob`, `longblob`, `bit` and `json` column types -(`json` in MySQL 5.7 and above). When providing a limit value and using -`binary`, `varbinary` or `blob` and its subtypes, the retained column type -will be based on required length (see [Limit Option and MySQL](#limit-option-and-mysql) for details). - -With most adapters, the `uuid` and `nativeuuid` column types are aliases, -however with the MySQL adapter + MariaDB, the `nativeuuid` type maps to -a native uuid column instead of `CHAR(36)` like `uuid` does. - -In addition, the Postgres adapter supports `interval`, `json`, `jsonb`, -`uuid`, `cidr`, `inet` and `macaddr` column types (PostgreSQL 9.3 and -above). - -### Valid Column Options - -The following are valid column options: - -For any column type: - -| Option | Description | -|----|----| -| limit | set maximum length for strings, also hints column types in adapters (see note below) | -| length | alias for `limit` | -| default | set default value or action | -| null | allow `NULL` values, defaults to `true` (setting `identity` will override default to `false`) | -| after | specify the column that a new column should be placed after, or use `\Migrations\Db\Adapter\MysqlAdapter::FIRST` to place the column at the start of the table *(only applies to MySQL)* | -| comment | set a text comment on the column | - -For `decimal` and `float` columns: - -| Option | Description | -|----|----| -| precision | total number of digits (e.g., 10 in `DECIMAL(10,2)`) | -| scale | number of digits after the decimal point (e.g., 2 in `DECIMAL(10,2)`) | -| signed | enable or disable the `unsigned` option *(only applies to MySQL)* | - -> [!NOTE] -> **Precision and Scale Terminology** -> -> Migrations follows the SQL standard where `precision` represents the total number of digits, -> and `scale` represents digits after the decimal point. For example, to create `DECIMAL(10,2)` -> (10 total digits with 2 decimal places): -> -> ``` php -> $table->addColumn('price', 'decimal', [ -> 'precision' => 10, // Total digits -> 'scale' => 2, // Decimal places -> ]); -> ``` -> -> This differs from CakePHP's TableSchema which uses `length` for total digits and -> `precision` for decimal places. The migration adapter handles this conversion automatically. - -For `enum` and `set` columns: - -| Option | Description | -|--------|-----------------------------------------------------| -| values | Can be a comma separated list or an array of values | - -For `smallinteger`, `integer` and `biginteger` columns: - -| Option | Description | -|----|----| -| identity | enable or disable automatic incrementing (if enabled, will set `null: false` if `null` option is not set) | -| signed | enable or disable the `unsigned` option *(only applies to MySQL)* | - -For Postgres, when using `identity`, it will utilize the `serial` type -appropriate for the integer size, so that `smallinteger` will give you -`smallserial`, `integer` gives `serial`, and `biginteger` gives -`bigserial`. - -For `date` columns: - -| Option | Description | -|---------|---------------------------------------------| -| default | set default value (use with `CURRENT_DATE`) | - -For `time` columns: - -| Option | Description | -|----|----| -| default | set default value (use with `CURRENT_TIME`) | -| timezone | enable or disable the `with time zone` option *(only applies to Postgres)* | - -For `datetime` columns: - -| Option | Description | -|----|----| -| default | set default value (use with `CURRENT_TIMESTAMP`) | -| timezone | enable or disable the `with time zone` option *(only applies to Postgres)* | - -For `timestamp` columns: - -| Option | Description | -|----|----| -| default | set default value (use with `CURRENT_TIMESTAMP`) | -| update | set an action to be triggered when the row is updated (use with `CURRENT_TIMESTAMP`) *(only applies to MySQL)* | -| timezone | enable or disable the `with time zone` option for `time` and `timestamp` columns *(only applies to Postgres)* | - -You can add `created` and `updated` timestamps to a table using the -`addTimestamps()` method. This method accepts three arguments, where the first -two allow setting alternative names for the columns while the third argument -allows you to enable the `timezone` option for the columns. The defaults for -these arguments are `created`, `updated`, and `false` respectively. For -the first and second argument, if you provide `null`, then the default name -will be used, and if you provide `false`, then that column will not be -created. Please note that attempting to set both to `false` will throw -a `\RuntimeException`. Additionally, you can use the -`addTimestampsWithTimezone()` method, which is an alias to `addTimestamps()` -that will always set the third argument to `true` (see examples below). The -`created` column will have a default set to `CURRENT_TIMESTAMP`. For MySQL -only, `updated` column will have update set to -`CURRENT_TIMESTAMP`: - -```php -table('users')->addTimestamps()->create(); - // Use defaults (with timezones) - $table = $this->table('users')->addTimestampsWithTimezone()->create(); - - // Override the 'created' column name with 'recorded_at'. - $table = $this->table('books')->addTimestamps('recorded_at')->create(); - - // Override the 'updated' column name with 'amended_at', preserving timezones. - // The two lines below do the same, the second one is simply cleaner. - $table = $this->table('books')->addTimestamps(null, 'amended_at', true)->create(); - $table = $this->table('users')->addTimestampsWithTimezone(null, 'amended_at')->create(); - - // Only add the created column to the table - $table = $this->table('books')->addTimestamps(null, false); - // Only add the updated column to the table - $table = $this->table('users')->addTimestamps(false); - // Note, setting both false will throw a \RuntimeError - } -} -``` - -For `boolean` columns: - -| Option | Description | -|--------|-------------------------------------------------------------------| -| signed | enable or disable the `unsigned` option *(only applies to MySQL)* | - -For `string` and `text` columns: - -| Option | Description | -|----|----| -| collation | set collation that differs from table defaults *(only applies to MySQL)* | -| encoding | set character set that differs from table defaults *(only applies to MySQL)* | - -### Limit Option and MySQL - -When using the MySQL adapter, there are a couple things to consider when working with limits: - -- When using a `string` primary key or index on MySQL 5.7 or below, or the - MyISAM storage engine, and the default charset of `utf8mb4_unicode_ci`, you - must specify a limit less than or equal to 191, or use a different charset. -- Additional hinting of database column type can be made for `integer`, - `text`, `blob`, `tinyblob`, `mediumblob`, `longblob` columns. Using - `limit` with one the following options will modify the column type - accordingly: - -| Limit | Column Type | -|--------------|-------------| -| BLOB_TINY | TINYBLOB | -| BLOB_REGULAR | BLOB | -| BLOB_MEDIUM | MEDIUMBLOB | -| BLOB_LONG | LONGBLOB | -| TEXT_TINY | TINYTEXT | -| TEXT_REGULAR | TEXT | -| TEXT_MEDIUM | MEDIUMTEXT | -| TEXT_LONG | LONGTEXT | -| INT_TINY | TINYINT | -| INT_SMALL | SMALLINT | -| INT_MEDIUM | MEDIUMINT | -| INT_REGULAR | INT | -| INT_BIG | BIGINT | - -For `binary` or `varbinary` types, if limit is set greater than allowed 255 -bytes, the type will be changed to the best matching blob type given the -length: - -```php -table('cart_items'); -$table->addColumn('user_id', 'integer') - ->addColumn('product_id', 'integer', ['limit' => MysqlAdapter::INT_BIG]) - ->addColumn('subtype_id', 'integer', ['limit' => MysqlAdapter::INT_SMALL]) - ->addColumn('quantity', 'integer', ['limit' => MysqlAdapter::INT_TINY]) - ->create(); -``` - -### Default values with expressions - -If you need to set a default to an expression, you can use a `Literal` to have -the column's default value used without any quoting or escaping. This is helpful -when you want to use a function as a default value: - -```php -use Migrations\BaseMigration; -use Migrations\Db\Literal; - -class AddSomeColumns extends BaseMigration -{ - public function change(): void - { - $this->table('users') - ->addColumn('uniqid', 'uuid', [ - 'default' => Literal::from('uuid_generate_v4()') - ]) - ->create(); - } -} -``` - - - -### Creating a Table - -Creating a table is really easy using the Table object. Let's create a table to -store a collection of users: - -```php -table('users'); - $users->addColumn('username', 'string', ['limit' => 20]) - ->addColumn('password', 'string', ['limit' => 40]) - ->addColumn('password_salt', 'string', ['limit' => 40]) - ->addColumn('email', 'string', ['limit' => 100]) - ->addColumn('first_name', 'string', ['limit' => 30]) - ->addColumn('last_name', 'string', ['limit' => 30]) - ->addColumn('created', 'datetime') - ->addColumn('updated', 'datetime', ['null' => true]) - ->addIndex(['username', 'email'], ['unique' => true]) - ->create(); - } -} -``` - -Columns are added using the `addColumn()` method. We create a unique index -for both the username and email columns using the `addIndex()` method. -Finally calling `create()` commits the changes to the database. - -> [!NOTE] -> Migrations automatically creates an auto-incrementing primary key column called `id` for every -> table. - -The `id` option sets the name of the automatically created identity field, -while the `primary_key` option selects the field or fields used for primary -key. `id` will always override the `primary_key` option unless it's set to -false. If you don't need a primary key set `id` to false without specifying -a `primary_key`, and no primary key will be created. - -To specify an alternate primary key, you can specify the `primary_key` option -when accessing the Table object. Let's disable the automatic `id` column and -create a primary key using two columns instead: - -```php -table('followers', ['id' => false, 'primary_key' => ['user_id', 'follower_id']]); - $table->addColumn('user_id', 'integer') - ->addColumn('follower_id', 'integer') - ->addColumn('created', 'datetime') - ->create(); - } -} -``` - -Setting a single `primary_key` doesn't enable the `AUTO_INCREMENT` option. -To simply change the name of the primary key, we need to override the default `id` field name: - -```php -table('followers', ['id' => 'user_id']); - $table->addColumn('follower_id', 'integer') - ->addColumn('created', 'timestamp', ['default' => 'CURRENT_TIMESTAMP']) - ->create(); - } -} -``` - -In addition, the MySQL adapter supports following options: - -| Option | Platform | Description | -|----|----|----| -| comment | MySQL, Postgres | set a text comment on the table | -| collation | MySQL, SqlServer | set the table collation *(defaults to database collation)* | -| row_format | MySQL | set the table row format | -| engine | MySQL | define table engine *(defaults to \`\`InnoDB\`\`)* | -| signed | MySQL | whether the primary key is `signed` *(defaults to \`\`true\`\`)* | -| limit | MySQL | set the maximum length for the primary key | - -By default, the primary key is `signed`. -To set it to be unsigned, pass the `signed` option with a `false` -value, or enable the `Migrations.unsigned_primary_keys` and -`Migrations.unsigned_ints` feature flags (see [Feature Flags](index#feature-flags)). -Both flags should be used together so that foreign key columns match -the primary keys they reference: - -```php -table('followers', ['signed' => false]); - $table->addColumn('follower_id', 'integer') - ->addColumn('created', 'timestamp', ['default' => 'CURRENT_TIMESTAMP']) - ->create(); - } -} -``` - -If you need to create a table with a different collation than the database, -use: - -```php -table('categories', [ - 'collation' => 'latin1_german1_ci' - ]) - ->addColumn('title', 'string') - ->create(); - } -} -``` - -Note however this can only be done on table creation : there is currently no way -of adding a column to an existing table with a different collation than the -table or the database. Only `MySQL` and `SqlServer` supports this -configuration key for the time being. - -To view available column types and options, see [Adding Columns](#adding-columns) for details. - -### MySQL ALTER TABLE Options - -::: info Added in version 5.0.0 -`ALGORITHM` and `LOCK` options were added in 5.0.0. -::: - -When modifying tables in MySQL, you can control how the ALTER TABLE operation is -performed using the `algorithm` and `lock` options. This is useful for performing -zero-downtime schema changes on large tables in production environments. - -```php -table('large_table'); - $table->addIndex(['status'], [ - 'name' => 'idx_status', - ]); - $table->update([ - 'algorithm' => 'INPLACE', - 'lock' => 'NONE', - ]); - } -} -``` - -Available `algorithm` values: - -| Algorithm | Description | -|----|----| -| DEFAULT | Let MySQL choose the algorithm (default behavior) | -| INPLACE | Modify the table in place without copying data (when possible) | -| COPY | Create a copy of the table with the changes (legacy method) | -| INSTANT | Only modify metadata, no table rebuild (MySQL 8.0+, limited operations) | - -Available `lock` values: - -| Lock | Description | -|-----------|----------------------------------------------------------| -| DEFAULT | Use minimal locking for the algorithm (default behavior) | -| NONE | Allow concurrent reads and writes during the operation | -| SHARED | Allow concurrent reads but block writes | -| EXCLUSIVE | Block all reads and writes during the operation | - -> [!NOTE] -> Not all operations support all algorithm/lock combinations. MySQL will raise -> an error if the requested combination is not possible for the operation. -> The `INSTANT` algorithm is only available in MySQL 8.0+ and only for specific -> operations like adding columns at the end of a table. - -> [!WARNING] -> Using `ALGORITHM=INPLACE, LOCK=NONE` does not guarantee zero-downtime for -> all operations. Some operations may still require a table copy or exclusive lock. -> Always test schema changes on a staging environment first. - -### Table Partitioning - -Migrations supports table partitioning for MySQL and PostgreSQL. Partitioning helps -manage large tables by splitting them into smaller, more manageable pieces. - -> [!NOTE] -> Partition columns must be included in the primary key for MySQL. SQLite does -> not support partitioning. MySQL's `RANGE` and `LIST` types only work with -> integer columns - use `RANGE COLUMNS` and `LIST COLUMNS` for DATE/STRING columns. - -#### RANGE Partitioning - -RANGE partitioning is useful when you want to partition by numeric ranges. For MySQL, -use `TYPE_RANGE` with integer columns or expressions, and `TYPE_RANGE_COLUMNS` for -DATE/DATETIME/STRING columns: - -```php -table('orders', [ - 'id' => false, - 'primary_key' => ['id', 'order_date'], - ]); - $table->addColumn('id', 'integer', ['identity' => true]) - ->addColumn('order_date', 'date') - ->addColumn('amount', 'decimal', ['precision' => 10, 'scale' => 2]) - ->partitionBy(Partition::TYPE_RANGE_COLUMNS, 'order_date') - ->addPartition('p2022', '2023-01-01') - ->addPartition('p2023', '2024-01-01') - ->addPartition('p2024', '2025-01-01') - ->addPartition('pmax', 'MAXVALUE') - ->create(); - } -} -``` - -#### LIST Partitioning - -LIST partitioning is useful when you want to partition by discrete values. For MySQL, -use `TYPE_LIST` with integer columns and `TYPE_LIST_COLUMNS` for STRING columns: - -```php -table('customers', [ - 'id' => false, - 'primary_key' => ['id', 'region'], - ]); - $table->addColumn('id', 'integer', ['identity' => true]) - ->addColumn('region', 'string', ['limit' => 20]) - ->addColumn('name', 'string') - ->partitionBy(Partition::TYPE_LIST_COLUMNS, 'region') - ->addPartition('p_americas', ['US', 'CA', 'MX', 'BR']) - ->addPartition('p_europe', ['UK', 'DE', 'FR', 'IT']) - ->addPartition('p_asia', ['JP', 'CN', 'IN', 'KR']) - ->create(); - } -} -``` - -#### HASH Partitioning - -HASH partitioning distributes data evenly across a specified number of partitions: - -```php -table('sessions'); - $table->addColumn('user_id', 'integer') - ->addColumn('data', 'text') - ->partitionBy(Partition::TYPE_HASH, 'user_id', ['count' => 8]) - ->create(); - } -} -``` - -#### KEY Partitioning (MySQL only) - -KEY partitioning is similar to HASH but uses MySQL's internal hashing function: - -```php -table('cache', [ - 'id' => false, - 'primary_key' => ['cache_key'], - ]); - $table->addColumn('cache_key', 'string', ['limit' => 255]) - ->addColumn('value', 'binary') - ->partitionBy(Partition::TYPE_KEY, 'cache_key', ['count' => 16]) - ->create(); - } -} -``` - -#### Partitioning with Expressions - -You can partition by expressions using the `Literal` class: - -```php -table('events', [ - 'id' => false, - 'primary_key' => ['id', 'created_at'], - ]); - $table->addColumn('id', 'integer', ['identity' => true]) - ->addColumn('created_at', 'datetime') - ->partitionBy(Partition::TYPE_RANGE, Literal::from('YEAR(created_at)')) - ->addPartition('p2022', 2023) - ->addPartition('p2023', 2024) - ->addPartition('pmax', 'MAXVALUE') - ->create(); - } -} -``` - -#### Modifying Partitions on Existing Tables - -You can add or drop partitions on existing partitioned tables: - -```php -table('orders') - ->addPartitionToExisting('p2025', '2026-01-01') - ->update(); - } - - public function down(): void - { - // Drop the partition - $this->table('orders') - ->dropPartition('p2025') - ->update(); - } -} -``` - -### Saving Changes - -When working with the Table object, Migrations stores certain operations in a -pending changes cache. Once you have made the changes you want to the table, -you must save them. To perform this operation, Migrations provides three methods, -`create()`, `update()`, and `save()`. `create()` will first create -the table and then run the pending changes. `update()` will just run the -pending changes, and should be used when the table already exists. `save()` -is a helper function that checks first if the table exists and if it does not -will run `create()`, else it will run `update()`. - -As stated above, when using the `change()` migration method, you should always -use `create()` or `update()`, and never `save()` as otherwise migrating -and rolling back may result in different states, due to `save()` calling -`create()` when running migrate and then `update()` on rollback. When -using the `up()`/`down()` methods, it is safe to use either `save()` or -the more explicit methods. - -When in doubt with working with tables, it is always recommended to call -the appropriate function and commit any pending changes to the database. - -### Renaming a Column - -To rename a column, access an instance of the Table object then call the -`renameColumn()` method: - -```php -table('users'); - $table->renameColumn('bio', 'biography') - ->save(); - } - - /** - * Migrate Down. - */ - public function down(): void - { - $table = $this->table('users'); - $table->renameColumn('biography', 'bio') - ->save(); - } -} -``` - -### Adding a Column After Another Column - -When adding a column with the MySQL adapter, you can dictate its position using -the `after` option, where its value is the name of the column to position it -after: - -```php -table('users'); - $table->addColumn('city', 'string', ['after' => 'email']) - ->update(); - } -} -``` - -This would create the new column `city` and position it after the `email` -column. The `\Migrations\Db\Adapter\MysqlAdapter::FIRST` constant can be used -to specify that the new column should be created as the first column in that -table. - -### Dropping a Column - -To drop a column, use the `removeColumn()` method: - -```php -table('users'); - $table->removeColumn('short_name') - ->save(); - } -} -``` - -### Specifying a Column Limit - -You can limit the maximum length of a column by using the `limit` option: - -```php -table('tags'); - $table->addColumn('short_name', 'string', ['limit' => 30]) - ->update(); - } -} -``` - -### Changing Column Attributes - -There are two methods for modifying existing columns: - -#### Updating Columns (Recommended) - -To modify specific column attributes while preserving others, use the `updateColumn()` method. -This method automatically preserves unspecified attributes like defaults, nullability, limits, etc.: - -```php -table('users'); - // Make email nullable, preserving all other attributes - $users->updateColumn('email', null, ['null' => true]) - ->save(); - } - - /** - * Migrate Down. - */ - public function down(): void - { - $users = $this->table('users'); - $users->updateColumn('email', null, ['null' => false]) - ->save(); - } -} -``` - -You can pass `null` as the column type to preserve the existing type, or specify a new type: - -```php -// Preserve type and other attributes, only change nullability -$table->updateColumn('email', null, ['null' => true]); - -// Change type to biginteger, preserve default and other attributes -$table->updateColumn('user_id', 'biginteger'); - -// Change default value, preserve everything else -$table->updateColumn('status', null, ['default' => 'active']); -``` - -The following attributes are automatically preserved by `updateColumn()`: - -- Default values -- NULL/NOT NULL constraint -- Column limit/length -- Decimal scale/precision -- Comments -- Signed/unsigned (for numeric types) -- Collation and encoding -- Enum/set values - -#### Changing Columns (Traditional) - -To completely replace a column definition, use the `changeColumn()` method. -This method requires you to specify all desired column attributes. -See [Adding Columns](#adding-columns) and [Valid Column Options](#valid-column-options) for allowed values: - -```php -table('users'); - // Must specify all attributes - $users->changeColumn('email', 'string', [ - 'limit' => 255, - 'null' => true, - 'default' => null, - ]) - ->save(); - } - - /** - * Migrate Down. - */ - public function down(): void - { - - } -} -``` - -You can enable attribute preservation with `changeColumn()` by passing -`'preserveUnspecified' => true` in the options: - -```php -$table->changeColumn('email', 'string', [ - 'null' => true, - 'preserveUnspecified' => true, -]); -``` - -> [!NOTE] -> For most use cases, `updateColumn()` is recommended as it is safer and requires -> less code. Use `changeColumn()` when you need to completely redefine a column -> or when working with legacy code that expects the traditional behavior. - -### Working With Indexes - -To add an index to a table you can simply call the `addIndex()` method on the -table object: - -```php -table('users'); - $table->addColumn('city', 'string') - ->addIndex(['city']) - ->save(); - } - - /** - * Migrate Down. - */ - public function down(): void - { - - } -} -``` - -By default Migrations instructs the database adapter to create a simple index. We -can pass an additional parameter `unique` to the `addIndex()` method to -specify a unique index. We can also explicitly specify a name for the index -using the `name` parameter, the index columns sort order can also be specified using -the `order` parameter. The order parameter takes an array of column names and sort order key/value pairs: - -```php -table('users'); - $table->addColumn('email', 'string') - ->addColumn('username','string') - ->addIndex(['email', 'username'], [ - 'unique' => true, - 'name' => 'idx_users_email', - 'order' => ['email' => 'DESC', 'username' => 'ASC']] - ) - ->save(); - } -} -``` - -As of 4.6.0, you can use `BaseMigration::index()` to get a fluent builder to -define indexes: - -```php -table('users'); - $table->addColumn('email', 'string') - ->addColumn('username','string') - ->addIndex( - $this->index(['email', 'username']) - ->setType('unique') - ->setName('idx_users_email') - ->setOrder(['email' => 'DESC', 'username' => 'ASC']) - ) - ->save(); - } -} -``` - -The MySQL adapter also supports `fulltext` indexes. If you are using a version before 5.6 you must -ensure the table uses the `MyISAM` engine: - -```php -table('users', ['engine' => 'MyISAM']); - $table->addColumn('email', 'string') - ->addIndex('email', ['type' => 'fulltext']) - ->create(); - } -} -``` - -MySQL adapter supports setting the index length defined by limit option. -When you are using a multi-column index, you are able to define each column index length. -The single column index can define its index length with or without defining column name in limit option: - -```php -table('users'); - $table->addColumn('email', 'string') - ->addColumn('username','string') - ->addColumn('user_guid', 'string', ['limit' => 36]) - ->addIndex(['email','username'], ['limit' => ['email' => 5, 'username' => 2]]) - ->addIndex('user_guid', ['limit' => 6]) - ->create(); - } -} -``` - -The SQL Server and PostgreSQL adapters support `include` (non-key) columns on indexes: - -```php -table('users'); - $table->addColumn('email', 'string') - ->addColumn('firstname','string') - ->addColumn('lastname','string') - ->addIndex(['email'], ['include' => ['firstname', 'lastname']]) - ->create(); - } -} -``` - -PostgreSQL, SQLServer, and SQLite support partial indexes by defining where -clauses for the index: - -```php -table('users'); - $table->addColumn('email', 'string') - ->addColumn('is_verified','boolean') - ->addIndex( - $this->index('email') - ->setName('user_email_verified_idx') - ->setType('unique') - ->setWhere('is_verified = true') - ) - ->create(); - } -} -``` - -PostgreSQL can create indexes concurrently which avoids taking disruptive locks -during index creation: - -```php -table('users'); - $table->addColumn('email', 'string') - ->addIndex( - $this->index('email') - ->setName('user_email_unique_idx') - ->setType('unique') - ->setConcurrently(true) - ) - ->create(); - } -} -``` - -PostgreSQL adapters also supports Generalized Inverted Index `gin` indexes: - -```php -table('users'); - $table->addColumn('address', 'string') - ->addIndex('address', ['type' => 'gin']) - ->create(); - } -} -``` - -Removing indexes is as easy as calling the `removeIndex()` method. You must -call this method for each index: - -```php -table('users'); - $table->removeIndex(['email']) - ->save(); - - // alternatively, you can delete an index by its name, ie: - $table->removeIndexByName('idx_users_email') - ->save(); - } - - /** - * Migrate Down. - */ - public function down(): void - { - - } -} -``` - -::: info Added in version 4.6.0 -`Index::setWhere()`, and `Index::setConcurrently()` were added. -::: - -### Working With Foreign Keys - -Migrations has support for creating foreign key constraints on your database tables. -Let's add a foreign key to an example table: - -```php -table('tags'); - $table->addColumn('tag_name', 'string') - ->save(); - - $refTable = $this->table('tag_relationships'); - $refTable->addColumn('tag_id', 'integer', ['null' => true]) - ->addForeignKey( - 'tag_id', - 'tags', - 'id', - ['delete'=> 'SET_NULL', 'update'=> 'NO_ACTION'], - ) - ->save(); - - } - - /** - * Migrate Down. - */ - public function down(): void - { - - } -} -``` - -The 'delete' and 'update' options allow you to define the `ON UPDATE` and `ON DELETE` behavior. Possibles values are 'SET_NULL', 'NO_ACTION', 'CASCADE' and -'RESTRICT'. If 'SET_NULL' is used then the column must be created as nullable -with the option `['null' => true]`. - -Foreign keys can be defined with arrays of columns to build constraints between -tables with composite keys: - -```php -table('follower_events'); - $table->addColumn('user_id', 'integer') - ->addColumn('follower_id', 'integer') - ->addColumn('event_id', 'integer') - ->addForeignKey( - ['user_id', 'follower_id'], - 'followers', - ['user_id', 'follower_id'], - [ - 'delete'=> 'NO_ACTION', - 'update'=> 'NO_ACTION', - 'constraint' => 'user_follower_id', - ] - ) - ->save(); - } -} -``` - -The options parameter of `addForeignKey()` supports the following options: - -| Option | Description | -|------------|--------------------------------------------------------| -| update | set an action to be triggered when the row is updated | -| delete | set an action to be triggered when the row is deleted | -| constraint | set a name to be used by foreign key constraint | -| deferrable | define deferred constraint application (postgres only) | - -Using the `foreignKey()` method provides a fluent builder to define a foreign -key: - -```php -table('articles'); - $table->addForeignKey( - $this->foreignKey() - ->setColumns('user_id') - ->setReferencedTable('users') - ->setReferencedColumns('user_id') - ->setDelete(ForeignKey::CASCADE) - ->setName('article_user_fk') - ) - ->save(); - } -} -``` - -::: info Added in version 4.6.0 -The `foreignKey` method was added. -::: - -We can also easily check if a foreign key exists: - -```php -table('tag_relationships'); - $exists = $table->hasForeignKey('tag_id'); - if ($exists) { - // do something - } - } - - /** - * Migrate Down. - */ - public function down(): void - { - - } -} -``` - -Finally, to delete a foreign key, use the `dropForeignKey` method. - -Note that like other methods in the `Table` class, `dropForeignKey` also -needs `save()` to be called at the end in order to be executed. This allows -Migrations to intelligently plan migrations when more than one table is -involved: - -```php -table('tag_relationships'); - $table->dropForeignKey('tag_id')->save(); - } - - /** - * Migrate Down. - */ - public function down(): void - { - - } -} -``` - -### Working With Check Constraints - -::: info Added in version 5.0.0 -Check constraints were added in 5.0.0. -::: - -Check constraints allow you to enforce data validation rules at the database level. -They are particularly useful for ensuring data integrity across your application. - -> [!NOTE] -> Check constraints are supported by MySQL 8.0.16+, PostgreSQL, and SQLite. -> SQL Server support is planned for a future release. - -#### Adding a Check Constraint - -You can add a check constraint to a table using the `addCheckConstraint()` method: - -```php -table('products'); - $table->addColumn('price', 'decimal', ['precision' => 10, 'scale' => 2]) - ->addCheckConstraint('price_positive', 'price > 0') - ->save(); - } - - /** - * Migrate Down. - */ - public function down(): void - { - $table = $this->table('products'); - $table->dropCheckConstraint('price_positive') - ->save(); - } -} -``` - -The first argument is the constraint name, and the second is the SQL expression -that defines the constraint. The expression should evaluate to a boolean value. - -#### Using the CheckConstraint Fluent Builder - -For more complex scenarios, you can use the `checkConstraint()` method to get -a fluent builder: - -```php -table('users'); - $table->addColumn('age', 'integer') - ->addColumn('status', 'string', ['limit' => 20]) - ->addCheckConstraint( - $this->checkConstraint() - ->setName('age_valid') - ->setExpression('age >= 18 AND age <= 120') - ) - ->addCheckConstraint( - $this->checkConstraint() - ->setName('status_valid') - ->setExpression("status IN ('active', 'inactive', 'pending')") - ) - ->save(); - } -} -``` - -#### Auto-Generated Constraint Names - -If you don't specify a constraint name, one will be automatically generated based -on the table name and expression hash: - -```php -table('inventory'); - $table->addColumn('quantity', 'integer') - // Name will be auto-generated like 'inventory_chk_a1b2c3d4' - ->addCheckConstraint( - $this->checkConstraint() - ->setExpression('quantity >= 0') - ) - ->save(); - } -} -``` - -#### Complex Check Constraints - -Check constraints can reference multiple columns and use complex SQL expressions: - -```php -table('date_ranges'); - $table->addColumn('start_date', 'date') - ->addColumn('end_date', 'date') - ->addColumn('discount', 'decimal', ['precision' => 5, 'scale' => 2]) - ->addCheckConstraint('valid_date_range', 'end_date >= start_date') - ->addCheckConstraint('valid_discount', 'discount BETWEEN 0 AND 100') - ->save(); - } -} -``` - -#### Checking if a Check Constraint Exists - -You can verify if a check constraint exists using the `hasCheckConstraint()` method: - -```php -table('products'); - $exists = $table->hasCheckConstraint('price_positive'); - if ($exists) { - // do something - } else { - $table->addCheckConstraint('price_positive', 'price > 0') - ->save(); - } - } -} -``` - -#### Dropping a Check Constraint - -To remove a check constraint, use the `dropCheckConstraint()` method with the -constraint name: - -```php -table('products'); - $table->dropCheckConstraint('price_positive') - ->save(); - } - - /** - * Migrate Down. - */ - public function down(): void - { - $table = $this->table('products'); - $table->addCheckConstraint('price_positive', 'price > 0') - ->save(); - } -} -``` - -> [!NOTE] -> Like other table operations, `dropCheckConstraint()` requires `save()` -> to be called to execute the change. - -#### Database-Specific Behavior - -**MySQL (8.0.16+)** - -Check constraints are fully supported. MySQL stores constraint metadata in the -`INFORMATION_SCHEMA.CHECK_CONSTRAINTS` table. - -**PostgreSQL** - -Check constraints are fully supported and stored in the `pg_constraint` catalog. -PostgreSQL allows the most flexible expressions in check constraints. - -**SQLite** - -Check constraints are supported but with some limitations. SQLite does not support -`ALTER TABLE` operations for check constraints, so adding or dropping constraints -requires recreating the entire table. This is handled automatically by the adapter. - -**SQL Server** - -Check constraint support for SQL Server is planned for a future release. - -### Determining Whether a Table Exists - -You can determine whether or not a table exists by using the `hasTable()` -method: - -```php -hasTable('users'); - if ($exists) { - // do something - } - } - - /** - * Migrate Down. - */ - public function down(): void - { - - } -} -``` - -### Dropping a Table - -Tables can be dropped quite easily using the `drop()` method. It is a -good idea to recreate the table again in the `down()` method. - -Note that like other methods in the `Table` class, `drop` also needs `save()` -to be called at the end in order to be executed. This allows Migrations to intelligently -plan migrations when more than one table is involved: - -```php -table('users')->drop()->save(); - } - - /** - * Migrate Down. - */ - public function down(): void - { - $users = $this->table('users'); - $users->addColumn('username', 'string', ['limit' => 20]) - ->addColumn('password', 'string', ['limit' => 40]) - ->addColumn('password_salt', 'string', ['limit' => 40]) - ->addColumn('email', 'string', ['limit' => 100]) - ->addColumn('first_name', 'string', ['limit' => 30]) - ->addColumn('last_name', 'string', ['limit' => 30]) - ->addColumn('created', 'datetime') - ->addColumn('updated', 'datetime', ['null' => true]) - ->addIndex(['username', 'email'], ['unique' => true]) - ->save(); - } -} -``` - -### Renaming a Table - -To rename a table access an instance of the Table object then call the -`rename()` method: - -```php -table('users'); - $table - ->rename('legacy_users') - ->update(); - } - - /** - * Migrate Down. - */ - public function down(): void - { - $table = $this->table('legacy_users'); - $table - ->rename('users') - ->update(); - } -} -``` - -### Changing the Primary Key - -To change the primary key on an existing table, use the `changePrimaryKey()` -method. Pass in a column name or array of columns names to include in the -primary key, or `null` to drop the primary key. Note that the mentioned -columns must be added to the table, they will not be added implicitly: - -```php -table('users'); - $users - ->addColumn('username', 'string', ['limit' => 20, 'null' => false]) - ->addColumn('password', 'string', ['limit' => 40]) - ->save(); - - $users - ->addColumn('new_id', 'integer', ['null' => false]) - ->changePrimaryKey(['new_id', 'username']) - ->save(); - } - - /** - * Migrate Down. - */ - public function down(): void - { - - } -} -``` - -### Creating Custom Primary Keys - -You can specify a `autoId` property in the Migration class and set it to -`false`, which will turn off the automatic `id` column creation. You will -need to manually create the column that will be used as a primary key and add -it to the table declaration: - -```php -table('products'); - $table - ->addColumn('id', 'uuid') - ->addPrimaryKey('id') - ->addColumn('name', 'string') - ->addColumn('description', 'text') - ->create(); - } -} -``` - -The above will create a `CHAR(36)` `id` column that is also the primary key. - -When specifying a custom primary key on the command line, you must note -it as the primary key in the id field, otherwise you may get an error -regarding duplicate id fields, i.e.: - -```bash -bin/cake bake migration CreateProducts id:uuid:primary name:string description:text created modified -``` - -All baked migrations and snapshot will use this new way when necessary. - -> [!WARNING] -> Dealing with primary key can only be done on table creation operations. -> This is due to limitations for some database servers the plugin supports. - -### Changing the Table Comment - -To change the comment on an existing table, use the `changeComment()` method. -Pass in a string to set as the new table comment, or `null` to drop the existing comment: - -```php -table('users'); - $users - ->addColumn('username', 'string', ['limit' => 20]) - ->addColumn('password', 'string', ['limit' => 40]) - ->save(); - - $users - ->changeComment('This is the table with users auth information, password should be encrypted') - ->save(); - } - - /** - * Migrate Down. - */ - public function down(): void - { - - } -} -``` - -## Checking Columns - -`BaseMigration` also provides methods for introspecting the current schema, -allowing you to conditionally make changes to schema, or read data. -Schema is inspected **when the migration is run**. - -### Get a column list - -To retrieve all table columns, simply create a `table` object and call -`getColumns()` method. This method will return an array of Column classes with -basic info. Example below: - -```php -table('users')->getColumns(); - ... - } - - /** - * Migrate Down. - */ - public function down(): void - { - ... - } -} -``` - -### Get a column by name - -To retrieve one table column, simply create a `table` object and call the -`getColumn()` method. This method will return a Column class with basic info -or NULL when the column doesn't exist. Example below: - -```php -table('users')->getColumn('email'); - ... - } - - /** - * Migrate Down. - */ - public function down(): void - { - ... - } -} -``` - -### Checking whether a column exists - -You can check if a table already has a certain column by using the -`hasColumn()` method: - -```php -table('user'); - $column = $table->hasColumn('username'); - - if ($column) { - // do something - } - - } -} - - - /** - * Migrate Down. - */ - public function down(): void - { - - } -} -``` - -### Dropping a Table - -Tables can be dropped quite easily using the `drop()` method. It is a -good idea to recreate the table again in the `down()` method. - -Note that like other methods in the `Table` class, `drop` also needs `save()` -to be called at the end in order to be executed. This allows Migrations to intelligently -plan migrations when more than one table is involved: - -```php -table('users')->drop()->save(); - } - - /** - * Migrate Down. - */ - public function down(): void - { - $users = $this->table('users'); - $users->addColumn('username', 'string', ['limit' => 20]) - ->addColumn('password', 'string', ['limit' => 40]) - ->addColumn('password_salt', 'string', ['limit' => 40]) - ->addColumn('email', 'string', ['limit' => 100]) - ->addColumn('first_name', 'string', ['limit' => 30]) - ->addColumn('last_name', 'string', ['limit' => 30]) - ->addColumn('created', 'datetime') - ->addColumn('updated', 'datetime', ['null' => true]) - ->addIndex(['username', 'email'], ['unique' => true]) - ->save(); - } -} -``` - -### Renaming a Table - -To rename a table access an instance of the Table object then call the -`rename()` method: - -```php -table('users'); - $table - ->rename('legacy_users') - ->update(); - } - - /** - * Migrate Down. - */ - public function down(): void - { - $table = $this->table('legacy_users'); - $table - ->rename('users') - ->update(); - } -} -``` - -### Changing the Primary Key - -To change the primary key on an existing table, use the `changePrimaryKey()` -method. Pass in a column name or array of columns names to include in the -primary key, or `null` to drop the primary key. Note that the mentioned -columns must be added to the table, they will not be added implicitly: - -```php -table('users'); - $users - ->addColumn('username', 'string', ['limit' => 20, 'null' => false]) - ->addColumn('password', 'string', ['limit' => 40]) - ->save(); - - $users - ->addColumn('new_id', 'integer', ['null' => false]) - ->changePrimaryKey(['new_id', 'username']) - ->save(); - } - - /** - * Migrate Down. - */ - public function down(): void - { - - } -} -``` - -### Creating Custom Primary Keys - -You can specify a `autoId` property in the Migration class and set it to -`false`, which will turn off the automatic `id` column creation. You will -need to manually create the column that will be used as a primary key and add -it to the table declaration: - -```php -table('products'); - $table - ->addColumn('id', 'uuid') - ->addPrimaryKey('id') - ->addColumn('name', 'string') - ->addColumn('description', 'text') - ->create(); - } -} -``` - -The above will create a `CHAR(36)` `id` column that is also the primary key. - -When specifying a custom primary key on the command line, you must note -it as the primary key in the id field, otherwise you may get an error -regarding duplicate id fields, i.e.: - -```bash -bin/cake bake migration CreateProducts id:uuid:primary name:string description:text created modified -``` - -All baked migrations and snapshot will use this new way when necessary. - -> [!WARNING] -> Dealing with primary key can only be done on table creation operations. -> This is due to limitations for some database servers the plugin supports. - -### Changing the Table Comment - -To change the comment on an existing table, use the `changeComment()` method. -Pass in a string to set as the new table comment, or `null` to drop the existing comment: - -```php -table('users'); - $users - ->addColumn('username', 'string', ['limit' => 20]) - ->addColumn('password', 'string', ['limit' => 40]) - ->save(); - - $users - ->changeComment('This is the table with users auth information, password should be encrypted') - ->save(); - } - - /** - * Migrate Down. - */ - public function down(): void - { - - } -} -``` - -## Checking Columns - -`BaseMigration` also provides methods for introspecting the current schema, -allowing you to conditionally make changes to schema, or read data. -Schema is inspected **when the migration is run**. - -### Get a column list - -To retrieve all table columns, simply create a `table` object and call -`getColumns()` method. This method will return an array of Column classes with -basic info. Example below: - -```php -table('users')->getColumns(); - ... - } - - /** - * Migrate Down. - */ - public function down(): void - { - ... - } -} -``` - -### Get a column by name - -To retrieve one table column, simply create a `table` object and call the -`getColumn()` method. This method will return a Column class with basic info -or NULL when the column doesn't exist. Example below: - -```php -table('users')->getColumn('email'); - ... - } - - /** - * Migrate Down. - */ - public function down(): void - { - ... - } -} -``` - -### Checking whether a column exists - -You can check if a table already has a certain column by using the -`hasColumn()` method: - -```php -table('user'); - $column = $table->hasColumn('username'); - - if ($column) { - // do something - } - - } -} -``` - -### Changing templates - -See [Custom Seed Migration Templates](seeding#custom-seed-migration-templates) for how to customize the templates -used to generate migrations. - -## Database-Specific Limitations - -While Migrations aims to provide a database-agnostic API, some features have -database-specific limitations or are not available on all platforms. - -### SQL Server - -The following features are not supported on SQL Server: - -**Check Constraints** - -Check constraints are not currently implemented for SQL Server. Attempting to -use `addCheckConstraint()` or `dropCheckConstraint()` will throw a -`BadMethodCallException`. - -**Table Comments** - -SQL Server does not support table comments. Attempting to use `changeComment()` -will throw a `BadMethodCallException`. - -**INSERT IGNORE / insertOrSkip()** - -SQL Server does not support the `INSERT IGNORE` syntax used by `insertOrSkip()`. -This method will throw a `RuntimeException` on SQL Server. Use `insertOrUpdate()` -instead for upsert operations, which uses `MERGE` statements on SQL Server. - -### SQLite - -**Foreign Key Names** - -SQLite does not support named foreign keys. The foreign key constraint name option -is ignored when creating foreign keys on SQLite. - -**Table Comments** - -SQLite does not support table comments directly. Comments are stored as metadata -but not in the database itself. - -**Check Constraint Modifications** - -SQLite does not support `ALTER TABLE` operations for check constraints. Adding or -dropping check constraints requires recreating the entire table, which is handled -automatically by the adapter. - -**Table Partitioning** - -SQLite does not support table partitioning. - -### PostgreSQL - -**KEY Partitioning** - -PostgreSQL does not support MySQL's `KEY` partitioning type. Use `HASH` -partitioning instead for similar distribution behavior. - -### MySQL/MariaDB - -**insertOrUpdate() Conflict Columns** - -For MySQL, the `$conflictColumns` parameter in `insertOrUpdate()` is ignored -because MySQL's `ON DUPLICATE KEY UPDATE` automatically applies to all unique -constraints. PostgreSQL and SQLite require this parameter to be specified. - -**MariaDB GIS/Geometry** - -Some geometry column features may not work correctly on MariaDB due to differences -in GIS implementation compared to MySQL. diff --git a/docs/en/writing-migrations.rst b/docs/en/writing-migrations.rst deleted file mode 100644 index e69de29bb..000000000