Ders 11 / 21
Göçler ve Tohumlama
Şemayı sürümleyen göçler ve başlangıç verisini yazan tohumlayıcı kurulur; kayıtlı göçün ikinci çalışmada atlandığı, yeni göçün koştuğu ve çalışmış bir göç dosyasının düzenlenmesinin bağlanmadığı ölçülür.
Yayın Güncelleme
Bu derste
- Yukarı ve aşağı yönlü göçler ile bir göç kaydı tutan çalıştırıcı kurmak
- Kayıtta bulunan göçün ikinci çalıştırmada atlandığını ölçmek
- Yeni eklenen göçün koştuğunu ve geri alınmış göçün yeniden koşabildiğini göstermek
- Çalışmış bir göç dosyasının düzenlenmesinin şemaya ulaşmadığını gerçek çıktıyla okumak
İçindekiler
İlişkiler tablolara dayanır ve tabloların bir yerden gelmesi gerekir. Şema elle kurulursa her ortamda farklı çıkar; göçler (migration) şemayı adım adım ve tekrarlanabilir biçimde kurmak için vardır. Peki aynı göç iki kez çalıştırılırsa tablo iki kez mi kurulur?
Bu dersin ölçü ekseni, göç dosyasının bu çalıştırmada koştu mu sorusudur. Karar sırası sinifla() fonksiyonunun kendisidir: koştu, ertelendi, atlandı, bağlanmadı. Ağırlıklı sınıf atlandı’dır: göç dosyası bulunmuş, yola konmuş, ama kayıtta olduğu için çalıştırılmamıştır.
GV16. Şema, çalışma dizinindeki sema.json dosyasında tablo adlarından kolon listelerine uzanan bir dizi olarak tutulur; gerçek bir veri tabanı yoktur. GV17. Göç kaydı goc_kaydi.json dosyasında çalışmış göçlerin adlarını sırayla tutar. GV18. Göç dosyaları goclar/ altında adlarına göre sıralanarak çalışır ve her biri yukari ile asagi fonksiyonlarını döndürür. GV19. Tohumlayıcı yalnızca tablo boşken veri yazar; başlangıç verisi veri.json dosyasındadır.
Ölçüm düzeneği değişmeden gelir.
<?php // kostu.php — beklenen bir parça bu istekte koştu mu? declare(strict_types=1); final class Gunluk { public static array $yol = []; // isteğin yoluna konan parçalar public static array $kostu = []; // istek içinde çağrılanlar public static array $ertelenen = []; // isteğin dışına bırakılanlar public static function yeniIstek(): void { self::$yol = self::$kostu = self::$ertelenen = []; } public static function yola(string $parca): void { self::$yol[] = $parca; } public static function kostu(string $parca): void { self::$kostu[] = $parca; } public static function ertele(string $parca): void { self::$ertelenen[] = $parca; } } function sinifla(string $parca): string { return match (true) { in_array($parca, Gunluk::$kostu, true) => 'koştu', in_array($parca, Gunluk::$ertelenen, true) => 'ertelendi', in_array($parca, Gunluk::$yol, true) => 'atlandı', default => 'bağlanmadı', }; }
Göç Çalıştırıcısı
Göç, şemayı bir sürümden ötekine taşıyan dosyadır. yukari fonksiyonu değişikliği uygular, asagi fonksiyonu geri alır. Çalıştırıcı (migrator) göç dosyalarını sırayla gezer, kayıtta olanları çalıştırmaz, olmayanları çalıştırıp kayda ekler. Geri alma (rollback) kayda eklenen son göçün asagi fonksiyonunu çağırır ve kaydı siler. Tohumlama (seeding) şemanın üstüne başlangıç verisini yazar. Göç dosyasındaki kolon ekleme kolonEkle() üzerinden geçer ve her ekleme kendini kolon:<tablo>.<kolon> adıyla kaydeder.
<?php // goc.php — göç çalıştırıcı, göç kaydı ve tohumlayıcı declare(strict_types=1); require_once 'kostu.php'; function jsonOku(string $dosya): array { return is_file($dosya) ? json_decode(file_get_contents($dosya), true) : []; } function jsonYaz(string $dosya, array $veri): void { file_put_contents($dosya, json_encode($veri)); } function kolonEkle(array $sema, string $tablo, string $kolon): array { Gunluk::kostu("kolon:$tablo.$kolon"); $sema[$tablo][] = $kolon; return $sema; } function kolonSil(array $sema, string $tablo, string $kolon): array { $sema[$tablo] = array_values(array_diff($sema[$tablo], [$kolon])); return $sema; } final class Gocucu { public function yukari(): void { $kayit = jsonOku('goc_kaydi.json'); $sema = jsonOku('sema.json'); $dosyalar = glob('goclar/*.php'); sort($dosyalar); foreach ($dosyalar as $dosya) { $ad = basename($dosya, '.php'); Gunluk::yola("goc:$ad"); if (in_array($ad, $kayit, true)) continue; Gunluk::kostu("goc:$ad"); $goc = require $dosya; $sema = $goc['yukari']($sema); $kayit[] = $ad; } jsonYaz('sema.json', $sema); jsonYaz('goc_kaydi.json', $kayit); } public function geriAl(): void { $kayit = jsonOku('goc_kaydi.json'); $sema = jsonOku('sema.json'); $ad = array_pop($kayit); $goc = require "goclar/$ad.php"; Gunluk::kostu("asagi:$ad"); jsonYaz('sema.json', $goc['asagi']($sema)); jsonYaz('goc_kaydi.json', $kayit); } } final class Tohumlayici { public function calistir(): void { Gunluk::yola('tohum:yazilar'); $veri = jsonOku('veri.json'); if (!empty($veri['yazilar'])) return; Gunluk::kostu('tohum:yazilar'); $veri['yazilar'] = [['id' => 1, 'baslik' => 'Ilk Yazi'], ['id' => 2, 'baslik' => 'Ikinci Yazi']]; jsonYaz('veri.json', $veri); } } function sifirla(): void { foreach (['sema.json', 'goc_kaydi.json', 'veri.json'] as $dosya) if (is_file($dosya)) unlink($dosya); if (!is_dir('goclar')) mkdir('goclar'); foreach (glob('goclar/*.php') as $dosya) unlink($dosya); } function raporla(array $parcalar): void { foreach ($parcalar as $parca) printf(" %-24s %s\n", $parca, sinifla($parca)); }
Üç göç dosyası, bir yazar betiğiyle yazılır. İlki yazilar tablosunu kurar, ikincisi slug kolonunu ekler, üçüncüsü etiketler tablosunu kurar; üçüncüsü karşıt ölçümde sonradan eklenir.
<?php // gocler.php — örnek göç dosyalarını yazar function gocYaz(string $ad): void { $kaynak = match ($ad) { '001_yazilar' => <<<'GOC' <?php return [ 'yukari' => function (array $sema): array { $sema['yazilar'] = []; return kolonEkle(kolonEkle($sema, 'yazilar', 'id'), 'yazilar', 'baslik'); }, 'asagi' => fn(array $sema): array => array_diff_key($sema, ['yazilar' => 1]), ]; GOC, '002_slug' => <<<'GOC' <?php return [ 'yukari' => fn(array $sema): array => kolonEkle($sema, 'yazilar', 'slug'), 'asagi' => fn(array $sema): array => kolonSil($sema, 'yazilar', 'slug'), ]; GOC, '003_etiketler' => <<<'GOC' <?php return [ 'yukari' => function (array $sema): array { $sema['etiketler'] = []; return kolonEkle(kolonEkle($sema, 'etiketler', 'id'), 'etiketler', 'ad'); }, 'asagi' => fn(array $sema): array => array_diff_key($sema, ['etiketler' => 1]), ]; GOC, }; file_put_contents("goclar/$ad.php", $kaynak); }
Kayıtlı Göçün Atlanması
Ölçüm iki göçü iki kez çalıştırır ve her çalıştırmadan sonra tohumlayıcıyı da çağırır. Beklenen: ilk çalıştırmada iki göç ve tohumlama koştu, ikincisinde üçü de atlandı; şema ikinci çalıştırmada değişmez.
<?php // gozlem.php — ikinci çalıştırmada kayıtlı göç atlanır require_once 'goc.php'; require_once 'gocler.php'; sifirla(); gocYaz('001_yazilar'); gocYaz('002_slug'); foreach ([1, 2] as $n) { Gunluk::yeniIstek(); (new Gocucu())->yukari(); (new Tohumlayici())->calistir(); echo "calistirma $n\n"; raporla(['goc:001_yazilar', 'goc:002_slug', 'tohum:yazilar']); } echo "sema: " . json_encode(jsonOku('sema.json')) . "\n";
calistirma 1
goc:001_yazilar koştu
goc:002_slug koştu
tohum:yazilar koştu
calistirma 2
goc:001_yazilar atlandı
goc:002_slug atlandı
tohum:yazilar atlandı
sema: {"yazilar":["id","baslik","slug"]}
İkinci çalıştırmada göç dosyaları bulundu, yola konuldu, kayıtta oldukları için çağrılmadı: atlandı. Bu hata değil, göçün yinelemeye dayanıklı olmasını sağlayan mekanizmadır. Tohumlayıcı da aynı biçimde tablo dolu olduğu için atlandı; aksi hâlde iki yazı dört yazıya çıkardı.
Karşıt Ölçüm: Yeni Göç ve Geri Alma
Üç göç çalışmış bir şemaya dördüncü göç eklenir. Beklenen: yeni göç koştu, öncekiler atlandı. Sonra son göç geri alınır ve tekrar çalıştırılır; geri alınan göç kayıttan çıktığı için yeniden koştu olmalıdır.
<?php // yenigoc.php — yeni göç koşar, geri alınan göç yeniden koşar require_once 'goc.php'; require_once 'gocler.php'; sifirla(); gocYaz('001_yazilar'); gocYaz('002_slug'); (new Gocucu())->yukari(); gocYaz('003_etiketler'); Gunluk::yeniIstek(); (new Gocucu())->yukari(); echo "yeni goc eklendi\n"; raporla(['goc:001_yazilar', 'goc:002_slug', 'goc:003_etiketler']); Gunluk::yeniIstek(); (new Gocucu())->geriAl(); echo "geri alindi: " . json_encode(array_keys(jsonOku('sema.json'))) . "\n"; raporla(['asagi:003_etiketler']); Gunluk::yeniIstek(); (new Gocucu())->yukari(); echo "yeniden calisti: " . json_encode(array_keys(jsonOku('sema.json'))) . "\n"; raporla(['goc:002_slug', 'goc:003_etiketler']);
yeni goc eklendi goc:001_yazilar atlandı goc:002_slug atlandı goc:003_etiketler koştu geri alindi: ["yazilar"] asagi:003_etiketler koştu yeniden calisti: ["yazilar","etiketler"] goc:002_slug atlandı goc:003_etiketler koştu
Göç çalıştırıcısı yeni dosyayı kendisi buldu; kimse onu adıyla çağırmadı. Geri alma kayıt ve şemayı birlikte geri çevirdi, bu yüzden göç yeniden koştu. Atlanan ile koşan arasındaki ayrım dosyada değil kayıttadır.
Sınırlayıcı Ölçüm
Bir göç çalıştıktan sonra dosyası düzenlenirse çalıştırıcı bunu fark eder mi? Ölçüm 002_slug dosyasını yukarıdaki göç çalışmış hâlde iken değiştirir ve ikinci bir kolon, ozet, ekler. Çalıştırıcı yeniden çalıştırılır. Beklenen: göç atlandı, çünkü kayıtta; düzenleme ise hiçbir koşu kaydı bırakmadığı için bağlanmadı. Şemada ozet olmaz. Düzeltme yolu, değişikliği yeni bir göç dosyasıyla yapmaktır; ölçümün sonunda bu denenir.
<?php // duzenleme.php — çalışmış göç dosyasının düzenlenmesi bağlanmaz require_once 'goc.php'; require_once 'gocler.php'; sifirla(); gocYaz('001_yazilar'); gocYaz('002_slug'); (new Gocucu())->yukari(); file_put_contents('goclar/002_slug.php', <<<'GOC' <?php return [ 'yukari' => fn(array $sema): array => kolonEkle(kolonEkle($sema, 'yazilar', 'slug'), 'yazilar', 'ozet'), 'asagi' => fn(array $sema): array => kolonSil(kolonSil($sema, 'yazilar', 'slug'), 'yazilar', 'ozet'), ]; GOC); Gunluk::yeniIstek(); (new Gocucu())->yukari(); echo "002 duzenlendi\n"; raporla(['goc:002_slug', 'kolon:yazilar.ozet']); echo "sema: " . json_encode(jsonOku('sema.json')) . "\n"; file_put_contents('goclar/004_ozet.php', <<<'GOC' <?php return [ 'yukari' => fn(array $sema): array => kolonEkle($sema, 'yazilar', 'ozet'), 'asagi' => fn(array $sema): array => kolonSil($sema, 'yazilar', 'ozet'), ]; GOC); Gunluk::yeniIstek(); (new Gocucu())->yukari(); echo "004 eklendi\n"; raporla(['goc:002_slug', 'goc:004_ozet', 'kolon:yazilar.ozet']); echo "sema: " . json_encode(jsonOku('sema.json')) . "\n";
002 duzenlendi
goc:002_slug atlandı
kolon:yazilar.ozet bağlanmadı
sema: {"yazilar":["id","baslik","slug"]}
004 eklendi
goc:002_slug atlandı
goc:004_ozet koştu
kolon:yazilar.ozet koştu
sema: {"yazilar":["id","baslik","slug","ozet"]}
Düzenlenmiş dosya atlandı: bulundu, ama kayıtta olduğu için çağrılmadı. Dosyaya yazılan ozet kolonu ise bağlanmadı; ne yola konuldu ne çağrıldı ve şemada yok. Hata verilmemesi bu sınıfın tehlikesidir: geliştirici kolonu eklediğini sanır, ortam farklı bir şemada kalır. Yeni bir göç dosyası aynı kolonu koştu yaptı ve şemaya ulaştı. Bu yüzden çalışmış bir göç değiştirilmez; şema değişikliği her zaman yeni göçle yapılır.
Özet
- Göç, şemayı sürümler; çalıştırıcı kayıtta olmayan göçleri çalıştırır ve kaydı günceller.
- Bu dersin ölçü sınıfı
atlandı’dır: kayıtlı göç ikinci çalıştırmada bulundu ve yola konuldu, ama çağrılmadı; şema değişmedi. - Yeni eklenen göç
koştu; geri alınan göç kayıttan çıktığı için yenidenkoştu. - Çalışmış göç dosyasındaki düzenleme
bağlanmadı: dosya atlandı, eklenen kolon şemaya ulaşmadı ve hata çıkmadı. - Tohumlayıcı da tablo doluyken
atlandı; şema değişikliği her zaman yeni göçle yapılır.
Sonraki Adım
Şema ve veri hazır. Modele yeni bir kolon eklendiğinde bu kolon API yanıtına kendiliğinden ulaşır mı? Bir sonraki ders sayfalamayı ve kaynak dönüşümünü ölçer; modelde olan ama yanıt tanımında olmayan bir kolonun bağlanmadı olduğunu gösterir.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.