Some checks failed
Deploy Application / deploy (push) Has been cancelled
369 lines
9.8 KiB
Markdown
369 lines
9.8 KiB
Markdown
# Migration Helper Examples
|
|
|
|
This document shows how to use the new `MigrationHelper` service to simplify migration code and explains the improved migration generation system.
|
|
|
|
## Overview
|
|
|
|
The `MigrationHelper` provides a simplified API for common migration operations without requiring abstract classes. It uses composition and follows framework principles.
|
|
|
|
The improved `MigrationGenerator` now:
|
|
- Automatically determines namespace from file path using `PathProvider`
|
|
- Uses `PhpNamespace` Value Object for type-safe namespace handling
|
|
- Automatically detects if migration should be `SafelyReversible` based on name patterns
|
|
- Generates cleaner code using `MigrationHelper` by default
|
|
|
|
## Basic Usage
|
|
|
|
### Before (Old Way)
|
|
|
|
```php
|
|
final readonly class CreateUsersTable implements Migration
|
|
{
|
|
public function up(ConnectionInterface $connection): void
|
|
{
|
|
$schema = new Schema($connection);
|
|
$schema->createIfNotExists('users', function (Blueprint $table) {
|
|
$table->ulid('ulid')->primary();
|
|
$table->string('name', 255);
|
|
$table->timestamps();
|
|
});
|
|
$schema->execute();
|
|
}
|
|
|
|
public function getVersion(): MigrationVersion
|
|
{
|
|
return MigrationVersion::fromTimestamp('2024_01_15_000001');
|
|
}
|
|
|
|
public function getDescription(): string
|
|
{
|
|
return 'Create Users Table';
|
|
}
|
|
}
|
|
```
|
|
|
|
### After (New Way with Helper)
|
|
|
|
```php
|
|
final readonly class CreateUsersTable implements Migration, SafelyReversible
|
|
{
|
|
public function up(ConnectionInterface $connection): void
|
|
{
|
|
$helper = new MigrationHelper($connection);
|
|
$helper->createTable('users', function (Blueprint $table) {
|
|
$table->ulid('ulid')->primary();
|
|
$table->string('name', 255);
|
|
$table->timestamps();
|
|
});
|
|
}
|
|
|
|
public function down(ConnectionInterface $connection): void
|
|
{
|
|
$helper = new MigrationHelper($connection);
|
|
$helper->dropTable('users');
|
|
}
|
|
|
|
public function getVersion(): MigrationVersion
|
|
{
|
|
return MigrationVersion::fromTimestamp('2024_01_15_000001');
|
|
}
|
|
|
|
public function getDescription(): string
|
|
{
|
|
return 'Create Users Table';
|
|
}
|
|
}
|
|
```
|
|
|
|
## Available Helper Methods
|
|
|
|
### Creating Tables
|
|
|
|
```php
|
|
$helper = new MigrationHelper($connection);
|
|
|
|
$helper->createTable('users', function (Blueprint $table) {
|
|
$table->ulid('ulid')->primary();
|
|
$table->string('name', 255);
|
|
$table->string('email', 255)->unique();
|
|
$table->timestamps();
|
|
});
|
|
```
|
|
|
|
### Dropping Tables
|
|
|
|
```php
|
|
$helper->dropTable('users');
|
|
```
|
|
|
|
### Adding Columns
|
|
|
|
```php
|
|
// Simple column
|
|
$helper->addColumn('users', 'phone', 'string', ['length' => 20]);
|
|
|
|
// Column with options
|
|
$helper->addColumn('users', 'age', 'integer', [
|
|
'nullable' => false,
|
|
'default' => 0
|
|
]);
|
|
|
|
// Decimal column
|
|
$helper->addColumn('orders', 'total', 'decimal', [
|
|
'precision' => 10,
|
|
'scale' => 2,
|
|
'nullable' => false
|
|
]);
|
|
```
|
|
|
|
### Adding Indexes
|
|
|
|
```php
|
|
// Simple index
|
|
$helper->addIndex('users', ['email']);
|
|
|
|
// Named index
|
|
$helper->addIndex('users', ['name', 'email'], 'idx_users_name_email');
|
|
|
|
// Unique index
|
|
$helper->addUniqueIndex('users', ['email'], 'uk_users_email');
|
|
```
|
|
|
|
### Dropping Columns
|
|
|
|
```php
|
|
$helper->dropColumn('users', 'phone');
|
|
```
|
|
|
|
### Renaming Tables
|
|
|
|
```php
|
|
$helper->renameTable('old_table_name', 'new_table_name');
|
|
```
|
|
|
|
## Advanced: Using Schema Directly
|
|
|
|
For complex operations, you can still use Schema directly:
|
|
|
|
```php
|
|
public function up(ConnectionInterface $connection): void
|
|
{
|
|
$helper = new MigrationHelper($connection);
|
|
$schema = $helper->schema();
|
|
|
|
// Complex operations
|
|
$schema->table('users', function (Blueprint $table) {
|
|
$table->dropColumn('old_column');
|
|
$table->renameColumn('old_name', 'new_name');
|
|
$table->addColumn('new_column', 'string', 255);
|
|
});
|
|
|
|
$schema->execute();
|
|
}
|
|
```
|
|
|
|
## Benefits
|
|
|
|
1. **Less Boilerplate**: No need to manually create Schema and call execute()
|
|
2. **Consistent API**: All helper methods follow the same pattern
|
|
3. **Type Safe**: Uses Value Objects and proper types
|
|
4. **Composable**: Can mix helper methods with direct Schema usage
|
|
5. **No Inheritance**: Uses composition, follows framework principles
|
|
|
|
## Migration Generator
|
|
|
|
The `MigrationGenerator` has been significantly improved with automatic namespace detection and smarter code generation.
|
|
|
|
### Automatic Namespace Detection
|
|
|
|
The generator uses `PathProvider` to automatically determine the correct namespace from the file path, making it work with any project structure:
|
|
|
|
```php
|
|
// When generating: php console.php make:migration CreateUsersTable User
|
|
// Path: src/Domain/User/Migrations/CreateUsersTable.php
|
|
// Namespace automatically determined: App\Domain\User\Migrations
|
|
```
|
|
|
|
**How it works:**
|
|
1. `PathProvider` reads `composer.json` to understand PSR-4 autoloading rules
|
|
2. The migration directory path is analyzed
|
|
3. `PhpNamespace` Value Object is created from the path
|
|
4. Fallback to default structure if path doesn't match PSR-4 rules
|
|
|
|
**Benefits:**
|
|
- Works with any namespace structure configured in `composer.json`
|
|
- No hardcoded namespace assumptions
|
|
- Type-safe namespace handling via `PhpNamespace` Value Object
|
|
- Automatically adapts to project structure changes
|
|
|
|
### Automatic SafelyReversible Detection
|
|
|
|
The generator analyzes migration names to determine if they should implement `SafelyReversible`:
|
|
|
|
**Safe patterns (automatically reversible):**
|
|
- `Create*` - Creating tables can be rolled back
|
|
- `Add*` - Adding columns/indexes can be removed
|
|
- `Index*` - Index operations are reversible
|
|
- `Constraint*` - Constraints can be dropped
|
|
- `Rename*` - Renaming can be reversed
|
|
|
|
**Unsafe patterns (forward-only):**
|
|
- `Drop*` - Dropping tables/columns loses data
|
|
- `Delete*` - Data deletion cannot be reversed
|
|
- `Remove*` - Removing columns loses data
|
|
- `Truncate*` - Truncating loses all data
|
|
- `Alter*Type` - Type changes may lose data
|
|
|
|
**Example:**
|
|
```bash
|
|
# Creates SafelyReversible migration
|
|
php console.php make:migration CreateUsersTable User
|
|
|
|
# Creates forward-only migration
|
|
php console.php make:migration DropOldColumns User
|
|
```
|
|
|
|
### Generated Code Examples
|
|
|
|
**Create Table Migration (SafelyReversible):**
|
|
```bash
|
|
php console.php make:migration CreateUsersTable User
|
|
```
|
|
|
|
Generates:
|
|
```php
|
|
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Domain\User\Migrations;
|
|
|
|
use App\Framework\Database\ConnectionInterface;
|
|
use App\Framework\Database\Migration\Migration;
|
|
use App\Framework\Database\Migration\MigrationVersion;
|
|
use App\Framework\Database\Migration\Services\MigrationHelper;
|
|
use App\Framework\Database\Migration\SafelyReversible;
|
|
|
|
final readonly class CreateUsersTable implements Migration, SafelyReversible
|
|
{
|
|
public function up(ConnectionInterface $connection): void
|
|
{
|
|
$helper = new MigrationHelper($connection);
|
|
|
|
// TODO: Implement your migration here
|
|
// Example:
|
|
// $helper->createTable('users', function($table) {
|
|
// $table->ulid('ulid')->primary();
|
|
// $table->string('name', 255);
|
|
// $table->timestamps();
|
|
// });
|
|
}
|
|
|
|
public function down(ConnectionInterface $connection): void
|
|
{
|
|
$helper = new MigrationHelper($connection);
|
|
$helper->dropTable('users'); // Auto-generated from name
|
|
}
|
|
|
|
public function getVersion(): MigrationVersion
|
|
{
|
|
return MigrationVersion::fromTimestamp('2024_01_15_120000');
|
|
}
|
|
|
|
public function getDescription(): string
|
|
{
|
|
return 'Create Users Table';
|
|
}
|
|
}
|
|
```
|
|
|
|
**Forward-Only Migration:**
|
|
```bash
|
|
php console.php make:migration DropOldColumns User
|
|
```
|
|
|
|
Generates:
|
|
```php
|
|
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Domain\User\Migrations;
|
|
|
|
use App\Framework\Database\ConnectionInterface;
|
|
use App\Framework\Database\Migration\Migration;
|
|
use App\Framework\Database\Migration\MigrationVersion;
|
|
use App\Framework\Database\Migration\Services\MigrationHelper;
|
|
|
|
final readonly class DropOldColumns implements Migration
|
|
{
|
|
public function up(ConnectionInterface $connection): void
|
|
{
|
|
$helper = new MigrationHelper($connection);
|
|
|
|
// TODO: Implement your migration here
|
|
// Example:
|
|
// $helper->dropColumn('users', 'old_column');
|
|
}
|
|
|
|
// No down() method - forward-only migration
|
|
|
|
public function getVersion(): MigrationVersion
|
|
{
|
|
return MigrationVersion::fromTimestamp('2024_01_15_120001');
|
|
}
|
|
|
|
public function getDescription(): string
|
|
{
|
|
return 'Drop Old Columns';
|
|
}
|
|
}
|
|
```
|
|
|
|
### Custom Namespace Structures
|
|
|
|
The generator works with any namespace structure defined in `composer.json`:
|
|
|
|
```json
|
|
{
|
|
"autoload": {
|
|
"psr-4": {
|
|
"App\\": "src/",
|
|
"Custom\\Namespace\\": "custom/path/"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
If you generate a migration in `custom/path/Migrations/`, the namespace will automatically be `Custom\Namespace\Migrations`.
|
|
|
|
### MigrationMetadata Value Object
|
|
|
|
For advanced use cases, you can use `MigrationMetadata` to work with migration metadata:
|
|
|
|
```php
|
|
use App\Framework\Database\Migration\ValueObjects\MigrationMetadata;
|
|
use App\Framework\Core\ValueObjects\PhpNamespace;
|
|
|
|
$namespace = PhpNamespace::fromString('App\Domain\User\Migrations');
|
|
$metadata = MigrationMetadata::create(
|
|
version: MigrationVersion::fromTimestamp('2024_01_15_120000'),
|
|
description: 'Create Users Table',
|
|
namespace: $namespace,
|
|
domain: 'User',
|
|
author: 'John Doe'
|
|
);
|
|
|
|
// Metadata automatically extracts domain from namespace
|
|
// $metadata->domain === 'User'
|
|
```
|
|
|
|
## Best Practices
|
|
|
|
1. **Use MigrationHelper for common operations** - Reduces boilerplate
|
|
2. **Let generator detect SafelyReversible** - Don't manually add it unless needed
|
|
3. **Trust PathProvider for namespaces** - It handles PSR-4 correctly
|
|
4. **Use PhpNamespace Value Objects** - Type-safe namespace operations
|
|
5. **Keep migrations simple** - Use helper methods, fall back to Schema for complex cases
|
|
|