# Önbellek

https://dev.wisecp.com/tr/onbellek

Dosya tabanlı bir önbellek, bilinmeye değer tek bir metot, sessizce kırılması kolay bir üretici sözleşmesi ve içine asla girmemesi gereken şeylerin kısa listesi.

## Genel Bakış

Herkes için aynı olan tekrarlı okumalar önbelleklenir: bir katalog, bir menü ağacı, bir fiyat tablosu. Kayıtlar depolar halinde gruplanır, depo başına bir dosya tutulur ve hepsine tek bir okuma-geçişli çağrıyla erişilir.

Kurulum genelindeki önbellek anahtarı o çağrının içinde dikkate alınır; yani önbellek kapalıyken üretici her istekte çalışır. Çağırmadan önce anahtarı kendiniz kontrol etmeyin.

## Referans

```php
public static function remember(string $name, string $key, int $ttl, callable $producer);
public static function getInstance(): self;

// $config: string verilirse depo adıdır, dizi verilirse 'name', 'path', 'extension' kabul eder.
public function __construct($config = []);

public function store($key, $data, $expiration = 86400): bool;
public function retrieve($key, $timestamp = false);
public function isCached($key): bool;
public function erase($key): self;
public function eraseExpired(): int;
public function eraseAll(): self;
public function clear($keys = []): void;
```

- **Cache::remember()**: İstediğiniz metot. Saklanan değeri döndürür ya da üreticiyi çağırır, sonucunu `$ttl` saniye boyunca saklar ve onu döndürür.
- **Cache::getInstance()**: Aşağıdaki çağrılar için paylaşılan örnek. `new` ile bir örnek kurmak paylaşılan örneği de değiştirir; kabul edilmiş bir davranıştır ama bilinmesi gerekir.
- **store()**: Tek bir kaydı yazar. Değer serialize edilir ve `0` verilen `$expiration` hiç sona ermez demektir. Lisans alan adı host ile eşleşmezse `false` döner.
- **retrieve()**: Tek bir kaydı okur; yoksa ya da okunamıyorsa `null` döner. İkinci argüman değer yerine yazılma zamanını ister ama o alan serialize edilmeden saklandığı için `false` döner, üzerine bir şey kurmayın.
- **erase()**: Geçerli depodan tek bir kaydı siler. Depo varken anahtar yoksa exception fırlatır; `isCached()` ile koruyun ya da yakalayın.
- **eraseAll()**: Geçerli deponun dosyasını olduğu gibi siler, içindeki her kaydı.
- **clear()**: Depoları toptan boşaltır. Argüman **depo adları** listesidir ya da virgülle ayrılmış bir dizedir; argümansız çağrıldığında bütün depo dosyalarını siler.

### Depo, Anahtar, Ömür

| Argüman | Neyi adlandırır | Neye dönüşür |
| --- | --- | --- |
| `$name` | Depo | Storage önbellek dizini altında tek bir dosya: `<ad>.cache`. Küçük harfe çevrilir; harf, rakam, nokta, alt çizgi ve tire dışındaki her şey atılır. |
| `$key` | O deponun içindeki tek bir kayıt | Dosyadaki bir dizi anahtarı; serialize edilmiş değeri, yazılma zamanını ve ömrünü birlikte tutar. |
| `$ttl` | Saniye cinsinden ömür | Bir saat için `3600`, statik referans verisinde bir gün için `86400`, hiç sona ermemesi için `0`. |

### Üretici Ne Döndürmeli

`serialize()` işleminden geçip aynı şekilde geri gelen her şey: dizi, dize, sayı, düz nesne. Closure, resource ve açık bağlantılar gelmez. Closure'a dönüş tipini yazın; böylece yanlış biçim okunduğu yerde değil yazıldığı yerde patlar.

```php
// Uygun: bir dizi, ve tip de bunu söylüyor.
Cache::remember('website', 'tld_requirements', 3600, fn (): array => self::build_requirements());

// Uygun: render edilmiş bir dize.
Cache::remember('website', 'sitemap_' . $lang, 3600, fn (): string => $this->build($lang));

// Uygun: birden çok yakalanan değer, bloklu gövde.
Cache::remember('website', 'plans_' . $categoryId . '_' . $ucid, 3600, function () use ($categoryId, $ucid): array {
    $rows = self::plans($categoryId);

    return self::decorate($rows, $ucid);
});

// Bozuk: null yazılır ama "yok" olarak geri okunur; üretici sonsuza kadar her
// istekte çalışır ve önbellek sessizce hiçbir şey yapmaz.
Cache::remember('website', 'maybe_nothing', 3600, fn () => null);
```

### Anahtarda Ne Olmalı

Önbelleklenmiş değer, aynı anahtarı hesaplayan herkes tarafından paylaşılır; dolayısıyla anahtar, değerin bağlı olduğu her şeyi adlandırmalıdır.

| Değer şuna bağlıysa | Anahtar şunu taşımalı | Tipik parça |
| --- | --- | --- |
| Para ya da biçimlendirme | Hizmet edilen para birimi | `Money::selected()` |
| Herhangi bir metin ya da bağlantı | Aktif dil | `Language::selected()` |
| Bir kayıt, kategori ya da filtre | O kimlik | `$categoryId` |
| Giriş yapmış ziyaretçi | Hiçbir şey, çünkü önbelleklenmemeli | Geçerli değil |

## Örnek

```php
static function catalog_plans(int $categoryId, string $type, string $layout = 'grid'): array
{
    $ucid = Money::selected();

    // Fiyatlar para birimine, etiketler dile bağlı; anahtar ikisini de taşıyor,
    // ayrıca satırları seçen her şeyi.
    $plans = Cache::remember('website', 'plans_' . $categoryId . '_' . $type . '_' . $layout . '_' . $ucid . '_' . Language::selected(), 3600,
        fn (): array => self::catalog_plans_fresh($categoryId, $type, $layout, $ucid));

    // Stok her siparişle değişir; bu yüzden önbelleğin parçası olmaz, önbellekten
    // gelen yükün üzerine canlı tazelenir.
    if ($plans) {
        $stocks = self::catalog_stocks(array_map(fn ($p) => (int) $p["id"], $plans));
        foreach ($plans as &$plan) {
            $stock = trim((string) ($stocks[(int) $plan["id"]] ?? ''));
            $plan["in_stock"] = ($stock === '' || (int) $stock > 0);
        }
        unset($plan);
    }

    // Kancalar HER istekte çalışmalıdır, bu yüzden üreticinin dışındadır. Bağlam
    // önce bir değişkene alınır: runRefs bütün argümanlarını referansla geçer.
    $ctx = ['category_id' => $categoryId, 'type' => $type, 'layout' => $layout];
    Hook::runRefs('filter:product.catalog_plans', $plans, $ctx);

    return $plans;
}
```

```php
trait AdminManageWebsite
{
    public function save_menu_changes(Operation $operation): bool
    {
        $operation->demo();

        // ... satırları yaz ...

        // Hedefli: yalnız 'menus' deposu, depo adıyla adreslenir.
        Cache::getInstance()->clear(['menus']);

        return $operation->output(['status' => "successful"]);
    }
}

// Tek bir kaydı silmek diğer metottur ve bulamadığında fırlatır.
$cache = new Cache('website');
if ($cache->isCached('plans_5_hosting_grid_1_tr')) $cache->erase('plans_5_hosting_grid_1_tr');
```

## Tuzaklar

> **clear() depoyu, erase() kaydı adlandırır**
> 
> `clear()` metoduna bir kayıt anahtarı geçmek o kaydı silmez. Dizeyi depo adı sayar, yani büyük ihtimalle var olmayan bir dosyayı siler ve bayat kaydı olduğu yerde bırakır, üstelik hata da vermez. Tek kayıt için `erase()` kullanın ve anahtar yokken fırlattığını unutmayın.

> **Kimin sorduğuna bağlı hiçbir şeyi önbelleklemeyin**
> 
> Stok, sepet, girişin arkasındaki her şey. Ziyaretçiye özel önbelleklenmiş bir değer sıradaki ziyaretçiye sunulur; bu bayat sayfa değil veri sızıntısıdır.

> **Kancaları üreticinin dışında tutun**
> 
> Üreticinin içindeki bir kanca yalnız önbellek boşken ateşlenir; dinleyici kayıt soğukken çalışıyor görünür ve ömrün geri kalanında sessizce durur. Veriyi önbellekleyin, sonra dinleyicilerin onun üzerinde çalışmasına izin verin.

> **Bir şey onu geçersiz kılmalı**
> 
> Operatör değeri bir ekrandan düzenleyebiliyorsa o ekran depoyu temizlemelidir. Düzenleme ekranı temizlemeyen yeni önbelleklenmiş veri, ömrü boyunca eski sürümü sunmaya devam eder ve size ulaşan şikayet önbellekle değil o ekranla ilgili olur.

## İlgili Makaleler

- [Yapılandırma Okuma ve Yazma](https://dev.wisecp.com/tr/yapilandirma-okuma-ve-yazma)
- [Tema Performansı ve Önbellek](https://dev.wisecp.com/tr/tema-performansi-ve-onbellek)
- [Kanca Dinleyicisi Yazma](https://dev.wisecp.com/tr/kanca-dinleyicisi-yazma)
- [WDB ile Sorgulama](https://dev.wisecp.com/tr/wdb-ile-sorgulama)
