Veritabanı Şemasını Değiştirme

1.7k görüntülenme Markdown

Tablolarınızı, platformun size verdiği bir anda kendi kodunuz kurar ve taşır; aynı kodun tekrar çalıştırılmaya da güvenli olması gerekir.

Genel Bakış

Bir migration dizini ya da artırılacak bir sürüm dosyası yoktur. Depolamaya ihtiyaç duyan bir modül onu kendi yaşam döngüsünde, etkinleştirildiğinde kurar ve eski bir kurulumu da aynı yerde ileri taşır. Yani tek bir metot hem kurma hem yükseltme yolunu tutar ve içindeki her ifadenin, ikinci çalıştırmayı etkisiz kılan kendi koşulu olmalıdır.

Çekirdeğin kendi tabloları sizin değiştireceğiniz tablolar değildir. Ürünün gönderdiği bir tabloya kolon eklemek, bir güncellemenin geri alacağı ve daha kötüsü çakışabileceği bir değişikliktir. İhtiyacınız olanı kendi tablonuzda saklayın ve join edin.

Ön Koşullar

  • Bir modül; çünkü tablo sahipliği burada yaşar. Depolamaya ihtiyaç duyan bir kanca dinleyicisi de aynı sebeple bir modüle aittir.
  • Ürününkiyle çakışamayacak bir tablo adı. Başına modülünüzün adını koyun.

Adım Adım

Modül Etkinleştirilince Kur

  1. Modülünüzün yaşam döngüsündeki etkinleştirme adımını tanımlayın ve şema kontrolünüzü oradan çağırın.
  2. Kontrolde, kurmadan önce tablonun var olup olmadığını sorun. Zaten kurulu bir modülü etkinleştirmek hata vermemelidir.
  3. Modülü panelden etkinleştirin ve tablonun göründüğünü doğrulayın.

Aynı Metotta Taşı

  1. Tablo zaten varsa, eski bir biçimi ileri taşımak için aynı metodu kullanın: eski kolon hâlâ orada mı diye sorun ve yalnızca oradaysa değiştirin.
  2. Her adımı saklanan bir sürüm numarasıyla değil kendi koşuluyla koruyun; böylece bir sürümü atlamış kurulum da aynı noktaya yakınsar.
  3. Kontrolü arka arkaya iki kez çalıştırın ve ikincisinin hiçbir şey yapmadığını doğrulayın.

Yalnızca Boş Tabloyu Tohumla

  1. Özelliğinizin başlangıç satırlarına ihtiyacı varsa, onları yalnızca tablo boşken ekleyin.
  2. Her etkinleştirmede tohumlama yapmayın; modülü kapatıp yeniden açan bir operatör ya kopya satırlar alır ya da kendi düzenlemelerini sessizce kaybeder.
  3. Kapatıp yeniden açın, sonra satır sayısının değişmediğini doğrulayın.

Referans

Platformun Çağırdıkları

change_addon_status() Çağıran taraf. Panelden 'enable' ya da 'disable' alır, metot çiftinizi çalıştırır ve yeni durumu ancak ondan sonra modülün yapılandırmasına yazar.
Modules::getInstance() Başka her şeyin modülünüze ulaşma yolu; şema adımını iki kez çalıştırmak isteyen bir script de dahil. Sınıfı asla doğrudan kurmayın.
tanımladığınız yaşam döngüsü metotları
// Modülünüz tanımlar, operatör anahtarı çevirdiğinde platform çağırır. "enable"da varsa
// önce activate(), sonra enable() çalışır; "disable"da deactivate()/disable() çifti aynısını
// yapar. Yeni durum YALNIZCA sonuncusu doğru bir değer döndürdüyse yazılır; yani yanlış bir
// dönüş ya da fırlatılan bir exception, modülü yarım kurulu bırakmak yerine tıklamayı reddeder.
public function enable(): bool;
public function disable(): bool;

// Kendi adımlarınız, enable() içinden çağrılır. Hem kurma hem yükseltme yolu burada yaşar;
// yani ikisi de her etkinleştirmede, her yeniden etkinleştirmede ve her güncellemeden sonra
// çalışır.
private function check_database(): void;   // tablolar
private function check_columns(): void;    // sonraki bir sürümün eklediği kolonlar
private function seed(): void;             // başlangıç satırları, bir kez

Sorgu Katmanının Şemaya Bakan Kısmı

bir şema adımının kullandığı WDB metotları
public static function hasTable($table = '');               // bool, SHOW TABLES LIKE ile - önek eklenmez
public static function exec($arg = '');                     // int etkilenen satır; başarısızlıkta 0, FIRLATMADAN
public static function query($statement, $isthis = false);  // PDOStatement ya da başarısızlıkta false - o da fırlatmadan
public static function getAssoc($statement = false);        // o statement'ın tek satırı; satır yoksa false
public static function getErrorMessage();                   // exec() ya da query() neden boş döndü
public static function getPrefix(): string;                 // kurulum bir önek tanımlamışsa şema öneki

// DDL exec()/query() üzerinden çalışır; çünkü zincirli kurucu yalnızca DML yazar. Bu ikisinden
// hiçbiri exception fırlatmaz: düşen bir CREATE ya da ALTER 0 / false döner ve hiçbir şey demez.

İfade Başına Bir Koşul

Eklediğiniz şey İkinci çalıştırmayı etkisiz kılan koşul İfade
bir tablo WDB::hasTable($t) false CREATE TABLE
bir kolon SHOW COLUMNS FROM $t LIKE 'col' satır döndürmüyor ALTER TABLE ... ADD COLUMN
adı değişen kolon eski ad hâlâ satır döndürüyor ALTER TABLE ... CHANGE
kaldırılan kolon ad hâlâ satır döndürüyor ALTER TABLE ... DROP COLUMN
başlangıç satırları tablonun satır sayısı 0 INSERT

Örnek

bir modülün şema adımı
public function enable(): bool
{
    $this->check_database();
    $this->check_columns();

    return true;
}

private function check_database(): void
{
    if (\WDB::hasTable(self::TABLE)) {
        // Zaten kurulu: burası kurma değil yükseltme yolu. Her adımın kendi koşulu var;
        // böylece bir sürümü atlamış kurulum da aynı noktaya yakınsar.
        $old = \WDB::query("SHOW COLUMNS FROM `" . self::TABLE . "` LIKE 'ticket_id'");
        if ($old && \WDB::getAssoc($old))
            \WDB::exec("ALTER TABLE `" . self::TABLE . "` CHANGE `ticket_id` `owner_id` INT UNSIGNED NOT NULL DEFAULT 0");

        return;
    }

    $created = \WDB::exec('CREATE TABLE `' . self::TABLE . '` (
        `id`        INT UNSIGNED NOT NULL AUTO_INCREMENT,
        `owner_id`  INT UNSIGNED NOT NULL DEFAULT 0,
        `status`    VARCHAR(32)  NOT NULL DEFAULT "",
        `ctime`     DATETIME     NOT NULL,
        PRIMARY KEY (`id`),
        KEY `owner_id` (`owner_id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4');

    // exec() hatayı yutar; başarıyı varsaymak yerine sorun.
    if (!\WDB::hasTable(self::TABLE))
        throw new \Exception('Tablo kurulamadı: ' . \WDB::getErrorMessage());

    $this->seed();
}

private function check_columns(): void
{
    // Sonraki bir sürümün getirdiği kolonlar; tablo => kolon => tanım.
    $columns = [
        self::TABLE => ['provider' => 'VARCHAR(30) NOT NULL DEFAULT ""'],
    ];

    foreach ($columns as $table => $definitions) {
        if (!\WDB::hasTable($table)) continue;

        foreach ($definitions as $column => $definition) {
            $stmt = \WDB::query("SHOW COLUMNS FROM `` LIKE ''");
            if ($stmt && \WDB::getAssoc($stmt)) continue;

            \WDB::exec("ALTER TABLE `` ADD COLUMN `` ");
        }
    }
}

private function seed(): void
{
    // Başlangıç satırları BİR KEZ girer. Modülü kapatıp yeniden açan bir operatör ne kopya
    // satır almalı ne de kendi düzenlemelerini kaybetmeli.
    $stmt  = \WDB::select('COUNT(id) AS total')->from(self::TABLE);
    $total = $stmt->build() ? (int) (($stmt->getAssoc() ?: [])['total'] ?? 0) : 0;
    if ($total > 0) return;

    \WDB::insert(self::TABLE, ['owner_id' => 0, 'status' => 'ready', 'ctime' => \DateManager::Now()]);
}
ikinci çalıştırmanın hiçbir şey yapmadığını kanıtlama
// Tek kullanımlık bir script: biçimi oku, adımı iki kez çalıştır, tekrar oku. "Tekrar
// çalıştırmaya güvenlidir" iddiası ancak iki okuma birbirini tuttuğunda bir şey ifade eder.
$module = Modules::getInstance('Addons', 'AcmeScanner');

$shape = static function (): array {
    $rows = WDB::query('SHOW COLUMNS FROM `Acme_scans`');
    $cols = $rows ? WDB::fetch_assoc($rows) : [];

    $count = WDB::select('COUNT(id) AS total')->from('Acme_scans');

    return [
        'columns' => array_column($cols, 'Field'),
        'rows'    => $count->build() ? (int) (($count->getAssoc() ?: [])['total'] ?? 0) : 0,
    ];
};

$module->enable();
$first = $shape();

$module->enable();
$second = $shape();

echo $first === $second ? "idempotent\n" : "SAPMA VAR\n";

Tuzaklar

Ürünün tablolarına kolon eklemeyin

O tabloların sahibi bir güncellemedir. Kolonunuz hayatta kalabilir, düşürülebilir ya da ürünün aynı adla eklediği bir kolonla çakışabilir. Verinizi kendi tablonuzda tutun ve onlarınkine kimlik üzerinden join edin.

Düşen bir ifade hiçbir şey söylemez

exec() ve query() bir SQL hatasında exception fırlatmaz: 0 ve false döner, sebebi de biri sorana kadar getErrorMessage() içinde bekler. Yalnız bu ikisini çağıran bir şema adımı, hiçbir şeyin kurulmadığı bir kurulumda da başarı bildirir. Biçimi geri okuyun ya da kontrol onu bulamadığında exception fırlatın.

Adım birden çok kez çalışır

Etkinleştirme, yeniden etkinleştirme ve güncelleme hepsi buraya uğrar. Her ifadenin ikinci çalıştırmayı etkisiz kılan bir koşulu olmalı; emin olmanın yolu da üzerine düşünmek değil, iki kez çalıştırıp biçimi karşılaştırmaktır.

Modülü kaldırmak operatörün verisini kaldırmamalı

Kapatıldığında tablonuzu düşürmek, yanlışlıkla yapılmış bir tıklamayı veri kaybına çevirir. Satırları bırakın; yeniden etkinleştirme verisini bıraktığı yerde bulur, gerçekten silinmesini isteyen operatör de bunu söyleyebilir.

Faydalı oldu mu?

Geri bildiriminiz için teşekkürler!

Hâlâ Yardıma mı İhtiyacınız Var?

Yukarıda bulamadığınız her şey için destek ekibimiz her zaman yanınızda.