Skip to main content

Upgrade/downgrade via web de APP

Per saber quines migracions tenim pendents (les que estan al sistema de fitxers però no a la base de dades), hem de comparar manualment la llista de totes les migracions disponibles amb l'historial de les ja executades.

MigrateController.php

Utilitzarem findMigrations() per llegir els fitxers del disc i getHistory() per llegir la taula de la BD, i després les compararem.

PHP
namespace App\Controllers;

use Config\Services;
use Throwable;

class MigrateController extends BaseController
{
    protected $migrations;

    public function __construct()
    {
        $this->migrations = Services::migrations();
    }

    public function index()
    {
        // 1. Obtenim l'historial de les ja executades (ordenades per ID descendent)
        $history = $this->migrations->getHistory();
        
        // 2. Obtenim TOTES les migracions que existeixen al directori /Database/Migrations
        $allAvailable = $this->migrations->findMigrations();
        
        // 3. Calculem les pendents comparant els noms (procediment manual en CI4)
        $executedNames = array_column($history, 'name');
        $pending = [];

        foreach ($allAvailable as $migration) {
            // Si el nom de la migració no està a l'historial, està pendent
            if (!in_array($migration['name'], $executedNames)) {
                $pending[] = $migration;
            }
        }

        return view('admin/migrations', [
            'history' => $history,
            'pending' => $pending,
        ]);
    }

    public function upgrade()
    {
        try {
            // latest() retorna el batch (int) si s'executen, o false si no n'hi ha
            if ($this->migrations->latest()) {
                return redirect()->back()->with('message', 'Base de dades actualitzada al darrer batch.');
            }
            return redirect()->back()->with('message', 'No hi havia migracions noves per aplicar.');
        } catch (Throwable $e) {
            return redirect()->back()->with('error', 'Error en Upgrade: ' . $e->getMessage());
        }
    }

    public function downgrade()
    {
        try {
            // regressive(0) torna enrera l'últim grup (batch) sencer
            // Retorna true si ha tingut èxit
            $this->migrations->regressive(0);
            return redirect()->back()->with('message', 'S’ha desfet l’últim grup de migracions (Rollback).');
        } catch (Throwable $e) {
            return redirect()->back()->with('error', 'Error en Downgrade: ' . $e->getMessage());
        }
    }
}

Vista views/admin/migrations.php

HTML
<!DOCTYPE html>
<html lang="ca">
<head>
    <meta charset="UTF-8">
    <title>Gestor de Migracions</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
</head>
<body class="container mt-5">

    <div class="d-flex justify-content-between align-items-center mb-4">
        <h2>🛠️ Gestió de Migracions Web</h2>
        <div>
            <a href="<?= base_url('migrate/upgrade') ?>" class="btn btn-success">Executar Pendents (Upgrade)</a>
            <a href="<?= base_url('migrate/downgrade') ?>" class="btn btn-danger" onclick="return confirm('ATENCIÓ: Això eliminarà taules o columnes segons el mètode down(). Continuar?')">Desfer últim Batch (Rollback)</a>
        </div>
    </div>

    <?php if (session()->getFlashdata('message')): ?>
        <div class="alert alert-info"><?= session()->getFlashdata('message') ?></div>
    <?php endif; ?>
    
    <?php if (session()->getFlashdata('error')): ?>
        <div class="alert alert-danger"><?= session()->getFlashdata('error') ?></div>
    <?php endif; ?>

    <div class="row">
        <div class="col-md-8">
            <h4>📜 Historial (Taula `migrations`)</h4>
            <table class="table table-sm table-bordered">
                <thead class="table-dark">
                    <tr>
                        <th>Batch</th>
                        <th>Nom de la Migració</th>
                        <th>Data d'execució</th>
                    </tr>
                </thead>
                <tbody>
                    <?php if (empty($history)): ?>
                        <tr><td colspan="3" class="text-center">No hi ha migracions executades.</td></tr>
                    <?php else: ?>
                        <?php foreach ($history as $m): ?>
                        <tr>
                            <td><span class="badge bg-primary"><?= $m->batch ?></span></td>
                            <td><code><?= $m->name ?></code></td>
                            <td><?= date('Y-m-d H:i:s', $m->time) ?></td>
                        </tr>
                        <?php endforeach; ?>
                    <?php endif; ?>
                </tbody>
            </table>
        </div>

        <div class="col-md-4">
            <h4>⏳ Fitxers Pendents</h4>
            <div class="list-group">
                <?php if (empty($pending)): ?>
                    <div class="list-group-item list-group-item-success">Sincronitzat: No hi ha fitxers pendents.</div>
                <?php else: ?>
                    <?php foreach ($pending as $p): ?>
                        <div class="list-group-item">
                            <small class="text-muted"><?= $p['version'] ?></small><br>
                            <strong><?= $p['name'] ?></strong>
                        </div>
                    <?php endforeach; ?>
                <?php endif; ?>
            </div>
        </div>
    </div>

</body>
</html>
  • findMigrations(): Retorna un array amb tots els fitxers trobats a app/Database/Migrations. Cada element conté name, version, path, etc.

  • getHistory(): Retorna un array d'objectes directament de la taula de la base de dades.

  • Lògica de comparació: Com que CI4 no té un mètode "màgic" per donar-te la resta, fem un array_column dels noms ja guardats i filtrem els fitxers del disc que no hi són.

Aquest mètode és molt més robust i "real", ja que utilitza exactament el que el framework posa a la nostra disposició.