An elderly wizard with a long white beard sits at a desk in a dimly lit library, writing in an ancient tome by candlelight. The room is adorned with ornate, gilded frames and shelves filled with books.

Migrationen sind ein Tool in CakePHP zum Verwalten von Datenbankschemaänderungen. Sie bieten eine versionskontrollierte Möglichkeit, Ihre Datenbankstruktur zu ändern, wodurch es einfacher wird, Änderungen in verschiedenen Umgebungen oder Installationen Ihrer Anwendung zu verfolgen, freizugeben und bereitzustellen. CakePHP verfügt über Tools zum Generieren von Snapshots Ihrer Datenbankstruktur und Migrationen zwischen ihnen. Da Migrationen auch Abfragesprachenanweisungen für Ihre Datenbank ausführen können, können sie auch zum Ändern von Daten verwendet werden. Willow CMS hat seit der Veröffentlichung für diese Site mehrere Versionen durchlaufen, wobei einige dieser Versionen Änderungen am Datenbankschema und an den Daten erforderten. In diesem Beitrag erkläre ich Ihnen anhand einiger praktischer Beispiele, wie ich damit umgegangen bin.

Erstellen von Migrationen – ein einfaches Beispiel

In CakePHP werden Migrationen mit dembake migration Befehl. Dieser Befehl generiert eine PHP-Klasse, die die SQL-Anweisungen für die Migration definiert. Um beispielsweise eine Migration zum Hinzufügen eines neuenemail Spalte zurusers Tabelle können Sie Folgendes ausführen:

bin/cake bake migration AddEmailToUsers email:string

Dieser Befehl erstellt eine Migrationsdatei mit dem NamenAddEmailToUsers in Ihremconfig/Migrations Verzeichnis mit einer Methode namenschange() das die notwendigen Änderungen definiert. Sie können die Methoden in CakePHPsTable Klasse, um die Schemaänderungen zu definieren. Hier ist ein Beispiel für eine change()-Methode zum Hinzufügen deremail Spalte:

use Migrations\AbstractMigration;

class AddEmailToUsers extends AbstractMigration
{
    /**
     * Change Method.
     *
     * More information on this method is available here:
     * https://book.cakephp.org/phinx/0/en/migrations.html#the-change-method
     * @return void
     */
    public function change(): void
    {
        $table = $this->table('users');
        $table->addColumn('email', 'string', [
            'default' => null,
            'limit' => 255,
            'null' => false,
        ]);
        $table->update();
    }
}

Migrationen können auch rückgängig gemacht werden mit dembin/cake migrations rollback Befehl. Wenn Sie diesen Befehl ausführen, wird die letzte als ausgeführt aufgezeichnete Migrationsdatei zurückgesetzt. Ihr Code zum Ausführen der Rollback-Aktionen sollte der Migrationsdatei über den Befehlpublic function down(): void {} Verfahren.

Ausführen von Migrationen

Nachdem Sie die Schemaänderungen definiert haben, wenden Sie diese mit demmigrate Befehl:

bin/cake migrations migrate

Dieser Befehl führt alle ausstehenden Migrationen aus und aktualisiert Ihr Datenbankschema. Das Schöne daran ist, dass Migrationen als abgeschlossen markiert werden, sobald sie über einen Datensatz imphinxlog Tabelle in der Datenbank, was bedeutet, dass Migrationen sicher auf automatisierte Weise verwendet werden können, beispielsweise im Skript setup_dev_env.sh der Willow CMS-Entwicklungsumgebung oder im Startskript für eine Produktionsinstallation .

Hier sehen Sie diephinxlog Tabelle in meiner Entwicklungsumgebung nach einem Lauf des Setup-Skripts und einem Lauf der 4 Migrationen seit V1.

Eine Tabelle mit Informationen zu verschiedenen Datenmigrationen, einschließlich Version, Migrationsname, Startzeit, Endzeit und Haltepunkt.

Lassen Sie uns auf jede dieser Migrationen näher eingehen …

Migrationen - einige Beispiele aus der Praxis

V1 - Basistabellen erstellen oder löschen

Schauen Sie sich den Quellcode für v1 an, die erste Migration für Willow , die zwar lang ist, aber eigentlich super einfach ist.up Methode verwendet dietable MethodenaddColumn Undcreate um jede Tabelle in der Datenbank zu erstellen. Der einzige Unterschied zwischen dieser Datei und dem ersten Beispiel besteht darin, dass die Methoden verkettet sind.

    public function up(): void
    {
        $this->table('aiprompts', ['id' => false, 'primary_key' => ['id']])
            ->addColumn('id', 'uuid', [
                'default' => null,
                'limit' => null,
                'null' => false,
            ])
            ->addColumn('task_type', 'string', [
                'default' => null,
                'limit' => 50,
                'null' => false,
            ])
            // more addColumns......
            ->create();
        
        // more calls to $this->table......

Die down-Methode macht diese Anweisungen rückgängig mit demdrop Undsave Tabellenmethoden.

    public function down(): void
    {
        $this->table('aiprompts')->drop()->save();
        // more calls to $this->table......

Diese Migration enthält keine Daten, sie erstellt/löscht nur die von Willow CMS benötigten Tabellen. Selbst für die einfachste CakePHP-App benötigen Sie eine solche Migration, wenn Sie PHPUnit-Tests ausführen möchten. CakePHP führt Ihre Migrationen jedes Mal aus, wenn Sie die Tests ausführen, damit Ihre Testdaten (Fixtures) geladen werden können.

ChangeExpiresAtToDatetime - Einen Spaltendatentyp ändern

Schauen Sie sich den Quelltext für ChangeExpiresAtToDatetime an. Auch dieser ist einfach, dieses Mal wird der Datentyp für eine Spalte imblocked_ips Tisch.

Die Up-Methode verwendet diechangeColumn -Methode, um die Änderung anzugebendatetime für dieexpires_at Spalte.

    public function up(): void
    {

        $this->table('blocked_ips')
            ->changeColumn('expires_at', 'datetime', [
                'default' => null,
                'limit' => null,
                'null' => true,
            ])
            ->update();
    }

Die Down-Methode ändert es wieder zurück zudate .

    public function down(): void
    {

        $this->table('blocked_ips')
            ->changeColumn('expires_at', 'date', [
                'default' => null,
                'length' => null,
                'null' => true,
            ])
            ->update();
    }

InsertSettings - Standarddaten in die Einstellungstabelle laden

Schauen Sie sich den Quelltext für InsertSettings an, der nur dieinsert Undsave Tabellenmethoden zum Laden der Standardeinstellungen für Willow CMS in diesettings Tisch.

    public function change(): void
    {
        $this->table('settings')
            ->insert([
                'id' => Text::uuid(),
                'ordering' => 7,
                'category' => 'AI',
                'key_name' => 'articleSummaries',
                'value' => '0',
                'value_type' => 'bool',
                'value_obscure' => false,
                'description' => 'Automatically generate concise and compelling summaries for your articles and pages. When enabled, the system will analyze the content and create a brief synopsis that captures the key points. These summaries will appear on the article index page and other areas where a short overview is preferable to displaying the full text.',
                'data' => null,
                'column_width' => 2,
            ])
            // More insert statements below......

Ich habe mich eigentlich nicht darum gekümmert,down Methode, um Rollbacks bei dieser Migration zu berücksichtigen, aber vielleicht sollte ich eine hinzufügen. Ich werde ein Ticket auf dem Willow CMS-Ticket-Board erstellen.

AddRobotsTemplate - Weitere Standarddaten laden

Sehen Sie sich die Quelle für AddRobotsTemplate.php an, die eine weitere Einstellung hinzufügt. Sie fragen sich vielleicht, warum ich das nicht einfach in die vorherige InsertSettings-Migration einfüge? Nun, erstens brauchte ich diese Einstellung nicht und zweitens hatte ich die neueste Version von Willow CMS bereits in der Produktion bereitgestellt – was bedeutet, dass ich die vorherige Migration bereits auf der Produktionsdatenbank als Teil der automatisierten Bereitstellung ausgeführt hatte, die ein neues Docker-Image erstellt und wiederum die CakePHP-Migrationsbefehle ausführt, um die Datenbank zu aktualisieren .

Dies ist ein schönes Beispiel dafür, wie CakePHP die vorherige Migration als ausgeführt markiert hat inphinxlog Tabelle, damit sie nie wieder ausgeführt wird (es sei denn, es wird ein Rollback durchgeführt), und daher ist diese neue Migration erforderlich. In dieser Datei gibt es einige Ungereimtheiten, wenn es um die Standarddaten für die Einstellung geht, bei denen es sich um einen Textblock handelt, der am einfachsten mit EOT-Markern zum Speichern in einem$robotsTemplate Variable, auf die dann in den verketteten Aufrufen verwiesen wird, um die Einstellung zu laden. Migrationen sind nur PHP-Code, Sie können also jede Art von zusätzlichem Code und Logik rund um die Aufrufe der Tabellenmethoden erstellen.

    public function change(): void
    {
        $robotsTemplate = <<table('settings')
            ->insert([
                'id' => Text::uuid(),
                'ordering' => 4,
                'category' => 'SEO',
                'key_name' => 'robots',
                'value' => $robotsTemplate,
                'value_type' => 'textarea',
                'value_obscure' => false,
                'description' => 'The template for robots.txt file. Use {LANG} as a placeholder for the language code. This template will be used to generate the robots.txt file content.',
                'data' => null,
                'column_width' => 4,
            ])
            ->save();
    }
}

Newslugstable - Ändern eines Tabellenschemas und Migrieren von Daten

Schauen Sie sich die Quelle für Newslugstable an, wo ich die$this->execute Methode, um im Rahmen der Migration SQL-Anweisungen auf der Datenbank auszuführen. Dieslugs Tabelle in der Produktionsdatenbank für diese Site enthält bereits Daten, die in die neue Tabellenstruktur migriert werden müssen. Dazu führt die Up-Methode die folgenden Schritte aus:

  1. VerwendenaddColumn Tabellenmethode zum Erstellen neuer Tabellenspalten mit lockeren Standardwerten (Nullwerte zulassen)
  2. Verwendenexecute Methode zum Migrieren der Daten aus den alten Spalten in die neuen Spalten mithilfe einer SQL-Anweisung
  3. VerwendenchangeColumn Tabellenmethode zum Ändern der neuen Spalten, sodass keine Nullwerte mehr zulässig sind
  4. Verwenden Sie dieaddIndex Tabellenmethode zum Erstellen einiger Indizes für die neue Spaltenstruktur
  5. Verwenden Sie dieremoveColumn Tabellenmethode zum Entfernen der alten Spalten, die nicht mehr benötigt werden
  6. verwenden Sie dieexecute Methode und etwas Code zum Erstellen neuer Daten in der Slug-Tabelle (da Tags auch ein neues SlugBehavior verwenden und Datensätze in dieser Tabelle haben sollten)
    public function up(): void
    {
        // First, add the new 'model' column
        $this->table('slugs')
            ->addColumn('model', 'string', [
                'limit' => 20,
                'null' => true, // Temporarily allow null
                'after' => 'id',
            ])
            ->update();

        // Add the new foreign_key column
        $this->table('slugs')
            ->addColumn('foreign_key', 'uuid', [
                'null' => true, // Temporarily allow null
                'after' => 'model',
            ])
            ->update();

        // Update existing records to set model and foreign_key
        $this->execute("UPDATE slugs SET model = 'Articles', foreign_key = article_id");

        // Now make the new columns required
        $this->table('slugs')
            ->changeColumn('model', 'string', [
                'limit' => 20,
                'null' => false,
            ])
            ->changeColumn('foreign_key', 'uuid', [
                'null' => false,
            ])
            ->update();

        // Add indexes for the new structure
        $this->table('slugs')
            ->addIndex(['model', 'slug'], [
                'name' => 'idx_slugs_lookup',
            ])
            ->addIndex(['model', 'foreign_key'], [
                'name' => 'idx_slugs_foreign',
            ])
            ->update();

        // Remove the modified column as it's no longer needed
        $this->table('slugs')
            ->removeColumn('modified')
            ->update();

        // Finally, remove the old article_id column
        $this->table('slugs')
            ->removeColumn('article_id')
            ->update();

        // Migrate existing tag slugs to the slugs table
        $connection = $this->getAdapter()->getConnection();
    
        $tags = $connection->execute("
            SELECT id, slug, DATE_FORMAT(created, '%Y-%m-%d %H:%i:%s') as created 
            FROM tags 
            WHERE slug IS NOT NULL 
            AND slug != ''
        ")->fetchAll('assoc');

        foreach ($tags as $tag) {
            $this->table('slugs')
                ->insert([
                    'id' => Text::uuid(),
                    'foreign_key' => $tag['id'],
                    'model' => 'Tags',
                    'slug' => $tag['slug'],
                    'created' => $tag['created'],
                ])
                ->save();
        }
    }

Die Down-Methode macht diese Änderungen rückgängig. Der Vollständigkeit halber sollte sie wahrscheinlich die Indizes für die Spalten erstellen, die wir neu erstellen, aber da ich nicht beabsichtige, diese Migration jemals dauerhaft rückgängig zu machen, habe ich mir die Mühe nicht gemacht.

    public function down(): void
    {
        // Add back the article_id column
        $this->table('slugs')
            ->addColumn('article_id', 'uuid', [
                'null' => true,
                'after' => 'id',
            ])
            ->update();

        // Restore data from foreign_key to article_id where model is 'Articles'
        $this->execute("UPDATE slugs SET article_id = foreign_key WHERE model = 'Articles'");

        // Make article_id required again
        $this->table('slugs')
            ->changeColumn('article_id', 'uuid', [
                'null' => false,
            ])
            ->update();

        // Add back the modified column
        $this->table('slugs')
            ->addColumn('modified', 'datetime', [
                'null' => true,
            ])
            ->update();

        // Remove the new polymorphic columns and indexes
        $this->table('slugs')
            ->removeIndex(['model', 'slug'])
            ->removeIndex(['model', 'foreign_key'])
            ->removeColumn('model')
            ->removeColumn('foreign_key')
            ->update();
    }

Hier sehen Sie die Struktur der Slug-Tabelle vor der Migration …

Ein Bild, das die Struktur einer Datenbanktabelle mit Spalten für Name, Typ, Sortierung, Attribute, Null und Standard zeigt.

… und danach…

Ein Bild, das die Struktur einer Datenbanktabelle mit Spalten für Name, Typ, Sortierung, Attribute, Null und Standard zeigt.

Einpacken

Wir haben die Grundlagen der Migration in CakePHP behandelt, vom Erstellen und Ausführen einfacher Migrationen bis hin zu komplexeren Szenarien wie dem Ändern von Spaltentypen und dem Migrieren vorhandener Daten. Die wichtigsten Erkenntnisse sind:

  • Struktur und Versionskontrolle: Migrationen bringen Ordnung in Ihre Datenbankänderungen und machen sie nachvollziehbar und umkehrbar.
  • Vereinfachte Bereitstellung: Verwenden Sie Migrationen, um Schemaaktualisierungen sicher in verschiedenen Umgebungen bereitzustellen, von der Entwicklung bis zur Produktion.
  • Automatisierung: Integrieren Sie Migrationen in Ihre Bereitstellungsskripte für nahtlose, automatisierte Datenbankaktualisierungen.

Bis zum nächsten Mal und viel Spaß beim Backen!

Schlagwörter

CMS Code Inhaltsbearbeitung Entwicklung KuchenPHP Infrastruktur DevOps