cycle/migrations
Cycle Database Migrations provides structured, versioned database schema changes for Cycle ORM/DBAL. Configure a migrations directory and table, run pending migrations programmatically, and optionally include vendor migrations or generate migration files during schema compilation.
Installation
composer require cycle/migrations
Add the service provider to config/app.php:
'providers' => [
// ...
Cycle\Migrations\MigrationsServiceProvider::class,
],
Configuration Publish the config file:
php artisan vendor:publish --provider="Cycle\Migrations\MigrationsServiceProvider" --tag="config"
Update config/migrations.php with your database connection and table prefix.
First Use Case Generate a migration for a new table:
php artisan make:migration create_users_table --table=users
This creates a migration file in database/migrations/ with auto-generated columns (e.g., id, created_at, updated_at).
Schema-First Migrations
Define your schema in a model (e.g., User.php) using attributes:
#[Cycle\Migrations\Attributes\Column(type: 'integer', autoIncrement: true)]
public int $id;
#[Cycle\Migrations\Attributes\Column(type: 'timestamp', nullable: false)]
public DateTimeInterface $created_at;
Run:
php artisan migrations:generate
This generates a migration file based on the model’s attributes.
Incremental Migrations
Use --incremental to generate only new/changed columns:
php artisan migrations:generate --incremental
Foreign Key Handling Automatically detect and generate foreign keys via relationships:
#[Cycle\Migrations\Attributes\BelongsTo(model: User::class)]
public ?User $author;
The generated migration includes the foreign key column and constraint.
Column Positioning (Updated in v4.2.6)
Previously, after and first attributes with default values (e.g., after: 'id') could cause unintended behavior. Now, these are ignored by default unless explicitly set to non-default values (e.g., after: 'custom_column'). Example:
#[Cycle\Migrations\Attributes\Column(type: 'string', after: 'id')] // Explicitly set
public string $name;
#[Cycle\Migrations\Attributes\Column(type: 'string')] // 'after' ignored if not specified
public string $email;
Seeding Integration
Pair with cycle/seeder for auto-generated seeders:
php artisan make:seeder UsersTableSeeder --table=users
Cycle\Migrations\Column class to support custom types (e.g., JSON, enum).MigrationsEvent::CREATING to modify migrations before generation:
event(new MigrationsEvent('creating', $table, $columns));
Cycle\Migrations\Generators\MySqlGenerator for platform-specific tweaks (e.g., PostgreSQL UUIDs).Schema Drift
ALTER TABLE) may conflict with auto-generated migrations.php artisan migrations:reset and regenerate migrations.Attribute Parsing
#[Cycle\Migrations\Attributes\Index]) may not be detected if the model lacks proper namespace imports.use statements at the top of the model.Foreign Key Ambiguity
User ↔ Post) may generate redundant foreign keys.#[Cycle\Migrations\Attributes\ForeignKey(ignore: true)] on one side.Timestamps Handling
created_at/updated_at may conflict with existing columns.nullable: true or use #[Cycle\Migrations\Attributes\Ignore].Column Positioning (v4.2.6)
after or first attributes (e.g., after: 'id') are now ignored unless explicitly set to non-default values. This prevents unintended column ordering.after/first behavior, update your models to explicitly define column positions:
#[Cycle\Migrations\Attributes\Column(type: 'string', after: 'id')] // Required for ordering
public string $name;
--dry-run to preview migrations without executing:
php artisan migrations:generate --dry-run
config/migrations.php:
'debug' => true,
Logs appear in storage/logs/migration-debug.log.Custom Generators
Override the default generator by binding a new class to the Cycle\Migrations\Generator interface in the service provider.
Migration Events Listen for events to modify migrations dynamically:
MigrationsEvent::listen('creating', function ($event) {
$event->columns[] = new Column('custom_field', 'string');
});
Table Naming
Customize table names via the #[Cycle\Migrations\Attributes\Table(name: 'custom_name')] attribute or by overriding the getTable() method in the model.
Batch Processing
For large schemas, use --batch=50 to split migrations into chunks:
php artisan migrations:generate --batch=50
How can I help you explore Laravel packages today?