Migracje w PHP - Wersjonowanie Struktury Bazy Danych

28.06.2025

Migracje w PHP - Wersjonowanie Struktury Bazy Danych

Czym są migracje bazy danych?

Migracje to sposób na kontrolę wersji struktury bazy danych. Podobnie jak Git śledzi zmiany w kodzie, migracje śledzą zmiany w schemacie bazy - tworzenie tabel, dodawanie kolumn, modyfikację indeksów. Każda migracja to odwracalna operacja, którą możesz zastosować lub cofnąć.

Dlaczego warto używać migracji?

  • Kontrola wersji - każda zmiana schematu jest śledzona w kodzie
  • Przenośność - łatwe przenoszenie zmian między środowiskami (dev, staging, production)
  • Odwracalność - możliwość cofnięcia zmian za pomocą rollback
  • Współpraca - wielu developerów może synchronizować strukturę bazy
  • Atomowość - migracje wykonywane w transakcjach

Struktura pliku migracji

Każda migracja to klasa anonimowa z dwoma metodami: up() (zastosuj zmiany) i down() (cofnij zmiany):

<?php
use DJWeb\Framework\DBAL\Migrations\Migration;
use DJWeb\Framework\DBAL\Schema\Columns\*;

return new class extends Migration {
    public function up(): void
    {
        $this->schema->createTable('products', [
            new IntColumn('id', nullable: false, autoIncrement: true),
            new VarcharColumn('name', length: 255),
            new TextColumn('description'),
            new IntColumn('price', unsigned: true),
            new IntColumn('stock', unsigned: true, defaultValue: 0),
            new EnumColumn('status', ['draft', 'published', 'archived']),
            new DateTimeColumn('created_at', current: true),
            new PrimaryColumn('id'),
        ]);

        $this->schema->createIndex('products', 'idx_status', 'status');
    }

    public function down(): void
    {
        $this->schema->dropTable('products');
    }
};

Tworzenie migracji - konwencje nazewnicze

Migracje używają timestamp jako prefixu nazwy pliku, co gwarantuje kolejność wykonania:

<?php
// Komenda CLI tworząca nową migrację
php djweb make:migration create_users_table

// Wygenerowany plik:
// database/migrations/20250130_143022_create_users_table.php

Zaawansowane operacje migracyjne

Modyfikacja istniejącej tabeli

<?php
return new class extends Migration {
    public function up(): void
    {
        // Dodanie nowej kolumny
        $this->schema->addColumn(
            'users',
            new VarcharColumn('phone', length: 20, nullable: true)
        );

        // Dodanie indeksu
        $this->schema->createIndex('users', 'idx_phone', 'phone');
    }

    public function down(): void
    {
        $this->schema->dropIndex('users', 'idx_phone');
        $this->schema->dropColumn('users', 'phone');
    }
};

Migracja z modyfikacją danych

<?php
return new class extends Migration {
    public function up(): void
    {
        // Dodaj kolumnę
        $this->schema->addColumn(
            'users',
            new EnumColumn('role', ['user', 'admin'], defaultValue: 'user')
        );

        // Aktualizuj istniejące dane
        $this->connection->execute(
            "UPDATE users SET role = 'admin' WHERE email LIKE '%@admin.com'"
        );
    }

    public function down(): void
    {
        $this->schema->dropColumn('users', 'role');
    }
};

Komendy CLI do zarządzania migracjami

# Utworzenie nowej migracji
php djweb make:migration create_orders_table

# Wykonanie wszystkich pending migracji
php djweb migrate

# Cofnięcie ostatniej migracji
php djweb migrate:rollback

# Cofnięcie wszystkich migracji
php djweb migrate:reset

# Status migracji (które zostały wykonane)
php djweb migrate:status

DatabaseMigrationRepository - tracking wykonanych migracji

System śledzi wykonane migracje w specjalnej tabeli migrations:

<?php
class DatabaseMigrationRepository
{
    public function log(string $migration, int $batch): void
    {
        $this->connection->execute(
            'INSERT INTO migrations (migration, batch) VALUES (?, ?)',
            [$migration, $batch]
        );
    }

    public function getRan(): array
    {
        return $this->connection->query(
            'SELECT migration FROM migrations ORDER BY batch, migration'
        );
    }

    public function getLastBatch(): array
    {
        $lastBatch = $this->connection->query(
            'SELECT MAX(batch) as batch FROM migrations'
        )[0]['batch'] ?? 0;

        return $this->connection->query(
            'SELECT migration FROM migrations WHERE batch = ?',
            [$lastBatch]
        );
    }
}

MigrationResolver - ładowanie plików migracji

Resolver skanuje katalog database/migrations i ładuje klasy migracji:

<?php
class MigrationResolver
{
    public function resolve(string $path): array
    {
        $files = glob($path . '/*.php');
        $migrations = [];

        foreach ($files as $file) {
            $migration = require $file;
            $migrations[basename($file)] = $migration;
        }

        ksort($migrations); // sortowanie po timestamp
        return $migrations;
    }
}

Transakcje w migracjach

Każda migracja wykonywana jest w transakcji - jeśli wystąpi błąd, wszystkie zmiany są wycofywane:

<?php
try {
    $this->connection->beginTransaction();

    // Wykonanie migracji
    $migration->up();

    // Log do bazy
    $repository->log($migrationName, $batch);

    $this->connection->commit();
} catch (\Exception $e) {
    $this->connection->rollback();
    throw $e;
}

Praktyczne zastosowanie

W prawdziwym projekcie struktura migracji wygląda następująco:

database/
└── migrations/
    ├── 20250115_100000_create_users_table.php
    ├── 20250115_110000_create_posts_table.php
    ├── 20250120_143000_add_role_to_users.php
    ├── 20250125_092000_create_comments_table.php
    └── 20250130_154500_add_indexes_to_posts.php

🎓 Zbuduj system migracji od podstaw!

W kursie PHP 8.4 implementujesz kompletny system migracji - od Migration base class przez DatabaseMigrationRepository, MigrationResolver, aż po komendy CLI (migrate, rollback, reset). Pełna obsługa transakcji i rollback.

Kup pełny kurs PHP 8.4 🚀 Pobierz darmowy fragment 📥

Zobacz też inne artykuły o DBAL