Ders 11 / 12
Bağımlılık Yöneticisi
Bildirim dosyası, sürüm kısıtı ve kilit dosyası; kilitsiz bir bağımlılığın alt sürümünün çıktıyı sessizce değiştirmesi; kilit dosyasının sürümü sabitlemesi; ana sürüm sınırının kısıt tarafından korunması ve anlamlı sürümlemenin bir söz olması.
Yayın Güncelleme
Bu derste
- Kilit dosyası olmayan bir bağımlılığın alt sürümünün çıktıyı değiştirdiğini ölçer
- Kilit dosyasının sürümü sabitleyip değişikliği durdurduğunu gösterir
- Sürüm kısıtının ana sürüm değişikliğine karşı korurken alt sürüm davranışına karşı korumadığını ayırt eder
İçindekiler
Ortak Arayüz Standartları dersinde gerçekleştirim, kodu bizim yazdığımız bir sınıftı. Bu derste gerçekleştirim başka birinin yayımladığı bir pakettir ve değişiklik sizin deponuzda değil, paketin yayım sürümünde olur. Soru aynı: bu değişiklik nereye ulaşıyor?
Bir paket yöneticisi üç şeyi birlikte tutar: hangi paketlere bağlı olduğunuzu söyleyen bildirim dosyası, hangi sürüm aralığını kabul ettiğinizi söyleyen sürüm kısıtı ve çözülen kesin sürümleri kaydeden kilit dosyası. PHP’nin yaygın paket yöneticisi de bu üç parçayla çalışır. Bu derste yöneticinin kendisi koşturulmuyor; ağa çıkılmaz. Ölçüm, aynı üç parçayı yerel bir dizinde taklit eden küçük bir çözücüyle yapılır.
ST4. Ölçümdeki çözücü ^1.4 biçimli tek kısıtı anlar: 1.4’ten büyük ya da eşit, 2.0.0’dan küçük. ^ ile ~ karşılaştırması ayrı bir fonksiyonla modellenir. Gerçek bir yöneticinin sıfırlı ana sürümler (0.x) için daha dar çalışan kuralı, birden çok pakete bağlı çözümleme ve bağımlılığın bağımlılıkları modellenmez.
Ölçüm Düzeneği
yayilim.php önceki derslerle aynıdır: aynı programı değişiklikten önce ve sonra kurup koşturur. Sınıfı sırayla sorar: çıkış 0 değilse kıran, çıktı farklıysa örtük, düzenlenen dosya bağlantı listesindeyse bağlantıda, hiçbiri değilse yerinde. Önceki sürüm 0 ile çıkmalıdır ve kullanan main.php düzenlenmez.
<?php // yayilim.php — bir degisikligin nereye ulastigini olcen yardimcilar function yaz(string $dizin, array $dosyalar): void { foreach ($dosyalar as $ad => $icerik) { if ($icerik === null) { continue; // null: dosya silindi } $yol = "$dizin/$ad"; if (!is_dir(dirname($yol))) { mkdir(dirname($yol), 0777, true); } file_put_contents($yol, $icerik); } } function oku(string $dizin): array { $dosyalar = []; $gezgin = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($dizin, FilesystemIterator::SKIP_DOTS) ); foreach ($gezgin as $dosya) { $ad = substr($dosya->getPathname(), strlen($dizin) + 1); $dosyalar[$ad] = file_get_contents($dosya->getPathname()); } return $dosyalar; } function kostur(string $dizin): array { $komut = 'cd ' . escapeshellarg($dizin) . ' && php -d display_errors=0 main.php 2>/dev/null'; exec($komut, $satirlar, $kod); return [$kod, implode("\n", $satirlar)]; } function olc(string $ad, array $taban, array $degisiklik, array $baglanti = []): string { if (array_key_exists('main.php', $degisiklik)) { throw new LogicException("$ad: kullanan duzenlenmez"); } yaz("olcum/$ad/once", $taban); yaz("olcum/$ad/sonra", array_merge($taban, $degisiklik)); [$kodOnce, $ciktiOnce] = kostur("olcum/$ad/once"); [$kodSonra, $ciktiSonra] = kostur("olcum/$ad/sonra"); if ($kodOnce !== 0) { throw new LogicException("$ad: taban calismadi"); } $sinif = match (true) { $kodSonra !== 0 => 'kiran', $ciktiOnce !== $ciktiSonra => 'ortuk', array_intersect(array_keys($degisiklik), $baglanti) !== [] => 'baglantida', default => 'yerinde', }; printf( "%-18s %d dosya cikis=%-3d cikti %-6s -> %s\n", $ad, count($degisiklik), $kodSonra, $ciktiOnce === $ciktiSonra ? 'ayni' : 'farkli', $sinif ); return $sinif; }
Kilitsiz Bağımlılık: Örtük
Gözlem kilitsiz-surum: bagimliliklar.json {"tarih": "^1.4"} diyor, yani Tarih paketinin 1.4 ve üstünü, 2.0.0’ın altında kabul ediyor. cozucu.php paketler dizinindeki sürümlerden kısıta uyan en yükseğini seçip kaynak.php dosyasını yükler. main.php tarihi Tarih::bicimle(2026, 10, 15) ile biçimlendirir. Tabanda yalnız paketler/tarih-1.4.0 var ve çözücü onu seçiyor. Değişiklik: paketin 1.5.0 sürümü yayımlanıyor. Sizin deponuzda hiçbir dosya düzenlenmiyor; yalnız paketler/tarih-1.5.0/kaynak.php ekleniyor. Kilit dosyası yok.
<?php // kilitsiz.php — kilit dosyasi olmayan bagimlilik yeni alt surumu alir require_once 'yayilim.php'; // 11. Bagimliligin yeni bir alt surumu yayimlanir; kilit dosyasi yok $siniflar[] = olc('kilitsiz-surum', [ 'bagimliliklar.json' => '{"tarih": "^1.4"}', 'paketler/tarih-1.4.0/kaynak.php' => <<<'PHP' <?php class Tarih { public static function bicimle(int $yil, int $ay, int $gun): string { return sprintf('%04d-%02d-%02d', $yil, $ay, $gun); } } PHP, 'cozucu.php' => <<<'PHP' <?php function surumSec(string $paket, string $kisit): string { $alt = ltrim($kisit, '^'); $ust = ((int) explode('.', $alt)[0] + 1) . '.0.0'; $adaylar = []; foreach (glob("paketler/$paket-*") as $dizin) { $surum = substr($dizin, strlen("paketler/$paket-")); if (version_compare($surum, $alt, '>=') && version_compare($surum, $ust, '<')) { $adaylar[] = $surum; } } usort($adaylar, 'version_compare'); return end($adaylar); } $istek = json_decode(file_get_contents('bagimliliklar.json'), true); $kilit = is_file('kilit.json') ? json_decode(file_get_contents('kilit.json'), true) : []; foreach ($istek as $paket => $kisit) { require "paketler/$paket-" . ($kilit[$paket] ?? surumSec($paket, $kisit)) . '/kaynak.php'; } PHP, 'main.php' => <<<'PHP' <?php require 'cozucu.php'; echo Tarih::bicimle(2026, 10, 15), "\n"; PHP, ], [ 'paketler/tarih-1.5.0/kaynak.php' => <<<'PHP' <?php class Tarih { public static function bicimle(int $yil, int $ay, int $gun): string { return sprintf('%02d.%02d.%04d', $gun, $ay, $yil); } } PHP, ]); echo kostur('olcum/kilitsiz-surum/once')[1], ' -> ', kostur('olcum/kilitsiz-surum/sonra')[1], "\n"; $adlar = array_keys(oku('olcum/kilitsiz-surum/once')); sort($adlar); echo implode(', ', $adlar), "\n";
kilitsiz-surum 1 dosya cikis=0 cikti farkli -> ortuk 2026-10-15 -> 15.10.2026 bagimliliklar.json, cozucu.php, main.php, paketler/tarih-1.4.0/kaynak.php
Sınıf örtük: bir dosya eklendi, çıkış 0 kaldı, ama çıktı 2026-10-15 iken 15.10.2026 oldu. Sizin deponuzda tek satır düzenlenmedi, program çökmedi ve tarih biçimi değişti. Çözücü 1.5.0’ı kısıta uygun en yüksek sürüm olarak seçti; kısıt bunu yasakladığı için değil izin verdiği için oldu. Son satır tabanın dosya listesidir: kilit.json listede yok.
Kilit Dosyası: Yerinde
Karşıtı kilitli: aynı yayım, ama taban kilit.json dosyası taşıyor ve dosya tarih paketini 1.4.0 sürümüne sabitliyor. Çözücü kilit dosyasında kayıt varsa kısıta bakmadan o sürümü yükler. Değişiklik yine paketin 1.5.0 sürümünün yayımlanmasıdır.
<?php // kilitli.php — kilit dosyasi sabitlenen surumu korur require_once 'yayilim.php'; $taban = oku('olcum/kilitsiz-surum/once'); $taban['kilit.json'] = '{"tarih": "1.4.0"}'; $sonra = oku('olcum/kilitsiz-surum/sonra'); olc('kilitli', $taban, [ 'paketler/tarih-1.5.0/kaynak.php' => $sonra['paketler/tarih-1.5.0/kaynak.php'], ]); echo kostur('olcum/kilitli/once')[1], ' -> ', kostur('olcum/kilitli/sonra')[1], "\n";
kilitli 1 dosya cikis=0 cikti ayni -> yerinde 2026-10-15 -> 2026-10-15
Sınıf yerinde: bir dosya eklendi, çıkış 0 kaldı, çıktı 2026-10-15 kaldı. Kilit dosyası olmayan ölçümde aynı yayım çıktıyı değiştirmişti; burada kilit çözücüyü 1.4.0’da tuttu. Fark tek dosyadır: sabitleme kararı kilit dosyasında durur ve yeni sürümü ancak o dosyanın güncellenmesi alır.
Kısıtın İki Yazılışı: Şapka ve Dalga
Kısıt iki ayrı işaretle yazılabilir ve ikisi aynı şeyi söylemez. ^ işareti sol baştaki sıfır olmayan sürüm hanesini sabitler: ^1.4.0, 1.4.0 ve üstünü, 2.0.0’ın altında kabul eder. ~ işareti ise yazılan hane sayısına bakar. Üç haneli ~1.4.0, 1.4.0 ve üstünü yalnız 1.5.0’ın altında kabul eder; iki haneli ~1.4 ise ^1.4 ile aynı aralığı verir. Aralığı hesaplayan yardımcı dosya aşağıda; sonraki iki ölçüm onu kullanır. Bu bir ölçüm değil kaynaktır, çıktı bloğu yoktur.
<?php // surum.php — ^ ve ~ kisitlarini aralik olarak okur function aralik(string $kisit): array { $isaret = $kisit[0]; $parcalar = array_map('intval', explode('.', substr($kisit, 1))); $alt = implode('.', array_pad($parcalar, 3, 0)); if ($isaret === '^' || count($parcalar) < 3) { $ust = ($parcalar[0] + 1) . '.0.0'; } else { $ust = $parcalar[0] . '.' . ($parcalar[1] + 1) . '.0'; } return [$alt, $ust]; } function enYuksek(string $kisit, array $surumler): ?string { [$alt, $ust] = aralik($kisit); $uygun = array_filter( $surumler, fn(string $s) => version_compare($s, $alt, '>=') && version_compare($s, $ust, '<') ); usort($uygun, 'version_compare'); return $uygun === [] ? null : end($uygun); }
Aynı beş yayım üç kısıtla sınanıyor. Çıktıdaki ilk sütun kısıt, sonrası seçilen sürümdür.
<?php // ayrim.php — ayni yayimlar uc kisitla farkli surum secer require_once 'surum.php'; $yayimlar = ['1.4.0', '1.4.7', '1.5.0', '1.9.2', '2.0.0']; foreach (['^1.4.0', '~1.4.0', '~1.4'] as $kisit) { printf("%-7s -> %s\n", $kisit, enYuksek($kisit, $yayimlar)); }
^1.4.0 -> 1.9.2 ~1.4.0 -> 1.4.7 ~1.4 -> 1.9.2
Üç kısıt da 1.4.0 ile başlıyor, ama ~1.4.0 yama sürümlerinde kalıyor ve 1.4.7’de duruyor; ^1.4.0 alt sürümleri de alıyor ve 1.9.2’ye çıkıyor. Hane sayısını bir eksiltmek, yani ~1.4.0 yerine ~1.4 yazmak, kısıtı yeniden ^ genişliğine getiriyor. Hiçbiri 2.0.0’ı almıyor. Ölçüm, kilitsiz-surum gözlemindeki riskin bir kısıt seçimiyle daraltılabileceğini gösterir; ama risk ortadan kalkmaz, yalnız yama sürümlerine iner.
Bir Ekibin Dört Adımı
Kilit dosyasının neden deponuza girdiğini bir akış anlatır. Bildirim ^1.4.0, paket 1.4.0’da ve kilit dosyası henüz yok. Ekipte iki kişi var: siz ve bir arkadaşınız. Akış dosya sistemi kullanmaz; bildirimi, kilit kaydını ve paketin yayım listesini üç değişkende tutar.
<?php // akis.php — ekipte kilit dosyasi depoda ise herkes ayni surumu alir require_once 'surum.php'; function kur(string $kisit, array $yayimlar, ?string $kilit): array { if ($kilit !== null) { return [$kilit, $kilit, 'kilitten']; } $surum = enYuksek($kisit, $yayimlar); return [$surum, $surum, 'cozuldu']; } $kisit = '^1.4.0'; $yayimlar = ['1.4.0']; [$surum, $kilit, $kaynak] = kur($kisit, $yayimlar, null); printf("1 ilk kurulum %s %s\n", $surum, $kaynak); $yayimlar[] = '1.4.7'; [$surum, , $kaynak] = kur($kisit, $yayimlar, $kilit); printf("2 arkadas, kilitli %s %s\n", $surum, $kaynak); [$surum, , $kaynak] = kur($kisit, $yayimlar, null); printf("2 arkadas, kilitsiz %s %s\n", $surum, $kaynak); [$surum, $kilit, $kaynak] = kur($kisit, $yayimlar, null); printf("3 guncelleme %s %s\n", $surum, $kaynak); [$surum, , $kaynak] = kur($kisit, $yayimlar, $kilit); printf("4 arkadas tekrar %s %s\n", $surum, $kaynak);
1 ilk kurulum 1.4.0 cozuldu 2 arkadas, kilitli 1.4.0 kilitten 2 arkadas, kilitsiz 1.4.7 cozuldu 3 guncelleme 1.4.7 cozuldu 4 arkadas tekrar 1.4.7 kilitten
İlk kurulumda kilit yoktu, çözücü 1.4.0’ı seçti ve kilit kaydı 1.4.0 oldu. Paket 1.4.7’yi yayımlayınca kilidi depodan alan arkadaşınız yine 1.4.0’ı kurdu; kilit olmasaydı 1.4.7’yi kurardı ve iki makinede iki sürüm çalışırdı. Güncelleme, kilidi bilerek yok sayan adımdır: çözücü 1.4.7’yi seçti ve kilit kaydını yeniden yazdı. Arkadaşınız bu yeni kaydı depodan alınca 1.4.7’de buluştu.
Kilit dosyasının depoya girmesinin sebebi budur: sabitleme kararı kodla birlikte dağıtılmazsa her makine kendi çözümünü yapar. Güncellemek de ayrı bir düzenlemedir; kilit dosyasındaki fark, hangi sürümlerin değiştiğini bir commit içinde gösterir.
Anlamlı Sürümlemenin Üç Hanesi
^ ve ~ işaretlerinin ayırt ettiği haneler birer sözdür. Ana hane, geriye uyumsuz bir değişiklikte artar. Alt hane, geriye uyumlu bir özellik eklendiğinde artar. Yama hanesi, geriye uyumlu bir hata düzeltmesinde artar. ^ bu sözün ilk ikisine güvenir: ana hane sabit kaldıkça kodunuzun çalışmaya devam edeceğini varsayar. ~1.4.0 yalnız üçüncüsüne güvenir.
kilitsiz-surum ölçümünde 1.4.0’dan 1.5.0’a geçiş alt hane artışıydı ve söz, çıktının değişmemesini gerektirirdi. Tarih biçimi değiştiğine göre ya paket sözü tutmadı ya da sizin kodunuz, sözün kapsamadığı bir şeye, bu durumda çıktının metnine dayanıyordu. Bu ayrım pratikte önemlidir: söz bozulduysa sorun paketin sahibine bildirilir ve kısıt daraltılır; sizin koddan kaynaklandıysa düzeltme sizin deponuzda yapılır. İki durumda da önce hangisi olduğunu bilmek gerekir, bunun için de değişikliğin ne zaman geldiğini bilmek gerekir. Çözücü hangisi olduğunu bilemez. Bilmesi de gerekmez: kilit dosyası bu belirsizliği ortadan kaldırmaz, ama değişikliğin ne zaman ve sizin onayınızla geleceğini kesinleştirir.
Bu yüzden iki karar ayrı verilir. Kısıt, kodunuzun hangi aralıkta çalışmasını beklediğinizi söyler ve nadiren değişir. Kilit dosyası, o aralıkta bugün hangi sürümü kullandığınızı söyler ve her güncellemede değişir. Birincisini yanlış seçerseniz kilit dosyası sizi korur; kilit dosyasını depoya koymazsanız hiçbir kısıt sizi aynı sürümde tutmaz. Sınırlayıcı ölçüm, kilit yokken kısıtın neye karşı yine de koruduğunu sınar.
Sınırlayıcı Ölçüm
Kısıt, ana sürüm değişikliğine karşı korur. Bu ölçüm onu doğruluyor: kilit dosyası yok, taban kilitsiz-surum tabanı, ve yayımlanan paket 2.0.0. ^1.4 kısıtı 2.0.0’ı dışarıda bırakıyor.
<?php // ana-surum.php — kisit ana surum degisikligine karsi korur require_once 'yayilim.php'; $taban = oku('olcum/kilitsiz-surum/once'); olc('ana-surum', $taban, [ 'paketler/tarih-2.0.0/kaynak.php' => <<<'PHP' <?php class Tarih { public static function bicimle(int $yil, int $ay, int $gun): string { return "$gun/$ay/$yil"; } } PHP, ]); echo kostur('olcum/ana-surum/once')[1], ' -> ', kostur('olcum/ana-surum/sonra')[1], "\n";
ana-surum 1 dosya cikis=0 cikti ayni -> yerinde 2026-10-15 -> 2026-10-15
Sınıf yerinde: 2.0.0 yayımlandı, çözücü yine 1.4.0’ı seçti ve çıktı 2026-10-15 kaldı. ^1.4 kısıtı ana sürüm değişikliğine karşı korur; ama alt sürümdeki davranış değişikliğine karşı korumaz, kilitsiz-surum ölçümü bunu gösterdi. Alt sürümün çıktıyı değiştirmeyeceği anlamlı sürümleme denen bir sözdür: paketin sahibi verir, çözücü denetlemez. Söz tutulursa ana sürüm sınırı yeter; tutulmazsa yalnız kilit dosyası korur.
Özet
- Bu dersin ölçü sınıfı örtük.
kilitsiz-surumgözleminde paketin 1.5.0 sürümü yayımlandı, deponuzda hiçbir dosya düzenlenmedi, çıkış 0 kaldı, çıktı2026-10-15iken15.10.2026oldu. - Karşıtı
kilitliyerinde:kilit.jsonsürümü1.4.0’da tuttu, çıktı değişmedi. - Sınırlayıcı ölçüm
ana-surumyerinde:^1.4kısıtı 2.0.0’ı dışarıda bıraktı. Kısıt ana sürüme karşı korur, alt sürümün davranışına karşı korumaz. ayrimveakisölçümleri iki ayrımı gösterdi:~1.4.0yama sürümünde,^1.4.0alt sürümde durur; kilit dosyası depodaysa ekibin herkesi aynı sürümü kurar, güncelleme kilidi bilerek yeniden yazar.- Bildirim dosyası ne istediğinizi, kilit dosyası ne aldığınızı kaydeder; sürüm kısıtı bir aralık, anlamlı sürümleme bir sözdür.
Sonraki Adım
Paket yöneticisi, bağımlılığın hangi sürümünün yükleneceğini tek yerde topladı. Sınıfın bağımlılığını ise kim kuruyor? Bağımlılık değişince hangi dosya düzenleniyor? Bağımlılık Enjeksiyonu dersi bunu ölçüyor.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.