İçeriğe geç
academia.sh

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-surum gözleminde paketin 1.5.0 sürümü yayımlandı, deponuzda hiçbir dosya düzenlenmedi, çıkış 0 kaldı, çıktı 2026-10-15 iken 15.10.2026 oldu.
  • Karşıtı kilitli yerinde: kilit.json sürümü 1.4.0’da tuttu, çıktı değişmedi.
  • Sınırlayıcı ölçüm ana-surum yerinde: ^1.4 kı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.
  • ayrim ve akis ölçümleri iki ayrımı gösterdi: ~1.4.0 yama sürümünde, ^1.4.0 alt 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.

Aramak için yazmaya başlayın.

↑↓ ↵ Esc gezin · aç · kapat