Nette Documentation Preview

syntax
Nette Caching
*************

<div class=perex>

Önbellek, bir zamanlar hesaplaması pahalı olan veriyi saklayarak uygulamanızı hızlandırır ve gelecekte ona daha hızlı erişilmesini sağlar. Şunları ele alacağız:

- önbelleğin nasıl kullanılacağı
- depo arka ucunun nasıl değiştirileceği
- önbelleğin doğru biçimde nasıl geçersiz kılınacağı

</div>

Nette'de önbelleği kullanmak çok basittir, yine de gelişmiş önbellekleme gereksinimlerini karşılar. Başarım ve %100 dayanıklılık için tasarlanmıştır. En yaygın depo arka uçları için adaptörler içerir. Etikete dayalı geçersiz kılmayı, zaman aşımını, cache stampede'e karşı korumayı ve dahasını destekler.


Kurulum
=======

Paketi [Composer|best-practices:composer] kullanarak indirin ve kurun:

```shell
composer require nette/caching
```


Temel Kullanım
==============

Önbellekle çalışmanın çekirdek unsuru [api:Nette\Caching\Cache] nesnesidir. Onun bir örneğini oluşturur ve yapıcıya bir depo arka ucu nesnesi aktarırız. Bu depo nesnesi, verinin saklanacağı fiziksel konumu temsil eder (veritabanı, Memcached, diskteki dosyalar vb.). Depo nesnesini genellikle `Nette\Caching\Storage` tipini isteyerek [bağımlılık enjeksiyonuyla |dependency-injection:passing-dependencies] elde edersiniz. Temelleri [Depolar bölümünde |#Depolar] öğreneceksiniz.

.[warning]
3.0 sürümünde arayüzün hâlâ `I` öneki vardı, dolayısıyla adı `Nette\Caching\IStorage` idi. Ayrıca `Cache` sınıfının sabitleri büyük harfle yazılıyordu, örneğin `Cache::Expire` yerine `Cache::EXPIRE`.

Aşağıdaki örnekler için `Cache` adında bir alias'ımız ve `$storage` değişkeninde bir depo örneğimiz olduğunu varsayın.

```php
use Nette\Caching\Cache;

$storage = /* ... */; // Nette\Caching\Storage örneği
```

Önbellek özünde bir *anahtar-değer deposudur*; yani veriyi, ilişkisel dizilere benzer biçimde anahtarlarla okur ve yazarız. Uygulamalar birden çok bağımsız parçadan oluşur. Tüm parçalar tek bir depoyu kullansaydı (diskte tek bir dizin düşünün), er ya da geç anahtar çakışmaları olurdu. Nette Framework bunu, depo alanını ad alanlarına (kavramsal olarak alt dizinler gibi) bölerek çözer. Uygulamanın her parçası böylece benzersiz bir adla kendi ad alanında çalışır ve hiçbir çakışma olmaz.

Ad alanı adını `Cache` sınıfı yapıcısının ikinci argümanı olarak belirtin:

```php
$cache = new Cache($storage, 'Full Html Pages');
```

Gerekirse, var olan bir örnekten `derive()` metodunu kullanarak bir alt ad alanına kapsamlanmış yeni bir önbellek türetebilirsiniz:

```php
$subCache = $cache->derive('Images');
```

Artık `$cache` nesnesini önbellekten okumak ve ona yazmak için kullanabiliriz. `load()` metodu her iki amaca da hizmet eder. İlk argüman anahtar, ikincisi ise anahtar önbellekte bulunamazsa çağrılan bir PHP callback'idir. Callback değeri üretir, onu döndürür ve `load()` metodu onu önbelleğe alır:

```php
$value = $cache->load($key, function () use ($key) {
	$computedValue = /* ... */; // pahalı hesaplama
	return $computedValue;
});
```

İkinci parametre atlanırsa (`$value = $cache->load($key)`), öğe önbellekte bulunamadığında `load()` metodu `null` döndürür.

.[tip]
Yalnızca dizelerin değil, serileştirilebilir her yapının önbelleğe alınabilmesi harikadır. Aynısı anahtarlar için de geçerlidir.

Bir öğeyi önbellekten silmek için `remove()` metodunu kullanın:

```php
$cache->remove($key);
```

Bir öğeyi önbelleğe `$cache->save($key, $data, ?array $dependencies = null)` metoduyla da kaydedebilirsiniz. Ancak yukarıda gösterilen `load()` yaklaşımı genellikle yeğlenir.


Memoization
===========

Memoization, bir fonksiyon ya da metot çağrısının sonucunu önbelleğe almaktır; böylece bir dahaki sefere aynı argümanlarla çağrıldığında yeniden hesaplanmak yerine önbellekteki sonuç döndürülür.

Metotlar ve fonksiyonlar, `call(callable $callback, ...$args)` kullanılarak memoize edilmiş biçimde çağrılabilir:

```php
$result = $cache->call('gethostbyaddr', $ip);
```

`gethostbyaddr()` fonksiyonu böylece her benzersiz `$ip` argümanı için yalnızca bir kez çağrılır. Aynı `$ip` ile sonraki çağrılar önbellekteki değeri döndürür.

Bir metodun ya da fonksiyonun etrafında, sonradan çağrılabilecek memoize edilmiş bir sarmalayıcı oluşturmak da mümkündür:

```php
function factorial($num)
{
	return /* ... */;
}

$memoizedFactorial = $cache->wrap('factorial');

$result = $memoizedFactorial(5); // ilk seferde hesaplar
$result = $memoizedFactorial(5); // ikinci seferde önbellekten döndürür
```


Süre Dolması ve Geçersiz Kılma
==============================

Önbellekleme kullanırken, daha önce saklanmış verinin ne zaman geçersiz olacağı sorusunu ele almak gerekir. Nette Framework, verinin geçerliliğini sınırlamak ya da onu açıkça silmek için mekanizmalar sunar (framework'ün terminolojisinde buna "geçersiz kılma" denir).

Verinin geçerliliği kaydetme sırasında, genellikle `save()` metodunun üçüncü parametresiyle ayarlanır; örneğin:

```php
$cache->save($key, $value, [
	$cache::Expire => '20 minutes',
]);
```

Alternatif olarak, `load()` metodundaki callback'e başvuruyla aktarılan `$dependencies` parametresiyle ayarlanabilir; örneğin:

```php
$value = $cache->load($key, function (&$dependencies) {
	$dependencies[Cache::Expire] = '20 minutes';
	return /* ... */;
});
```

Ya da `load()` metodunun kendi 3. parametresi kullanılarak; örneğin:

```php
$value = $cache->load($key, function () {
	return /* ... */;
}, [Cache::Expire => '20 minutes']);
```

Aşağıdaki örneklerde, callback içinde `$dependencies` değişkenini kullanan ikinci çeşidi varsayacağız.


Süre Dolması
------------

Süre dolmasının en basit biçimi bir zaman sınırıdır. Bu, veriyi 20 dakikalık geçerlilikle önbelleğe alır:

```php
// saniye sayısını ya da bir UNIX zaman damgasını da kabul eder
$dependencies[Cache::Expire] = '20 minutes';
```

Geçerlilik süresinin her okumada uzamasını istiyorsanız (kayan süre dolması), bunu şöyle sağlayabilirsiniz, ama bunun önbellek ek yükünü artırdığını bilin:

```php
$dependencies[Cache::Sliding] = true;
```

Yararlı bir seçenek, belirli bir dosya ya da birkaç dosyadan biri değiştirildiğinde verinin süresini doldurmaktır. Bu, örneğin bu dosyaların işlenmesinden türeyen veriyi önbelleğe alırken yararlıdır. Mutlak yollar kullanın.

```php
$dependencies[Cache::Files] = '/path/to/data.yaml';
// ya da
$dependencies[Cache::Files] = ['/path/to/data1.yaml', '/path/to/data2.yaml'];
```

Bir önbellek öğesinin, başka belirli bir öğenin (ya da birkaçından birinin) süresi dolduğunda süresini doldurmasını sağlayabiliriz. Bu, örneğin bir HTML sayfasının tamamını ve parçalarını farklı anahtarlarla önbelleğe alırken yararlıdır. Bir parça değiştiğinde sayfanın tamamı geçersiz kılınmalıdır. Parçalar `frag1` ve `frag2` gibi anahtarlarla saklanıyorsa şunu kullanın:

```php
$dependencies[Cache::Items] = ['frag1', 'frag2'];
```

Süre dolması, özel fonksiyonlar ya da statik metotlar kullanılarak da denetlenebilir. Bunlar, öğenin hâlâ geçerli olup olmadığını saptamak için her okumada çağrılır. Örneğin, PHP sürümü her değiştiğinde bir öğenin süresini doldurabiliriz. Geçerli sürümü bir parametreyle karşılaştıran bir fonksiyon oluşturun ve kaydederken bağımlılıklara `[fonksiyon adı, ...argümanlar]` biçiminde bir dizi ekleyin:

```php
function checkPhpVersion($ver): bool
{
	return $ver === PHP_VERSION_ID;
}

$dependencies[Cache::Callbacks] = [
	['checkPhpVersion', PHP_VERSION_ID] // checkPhpVersion(...) === false olduğunda süresi dolar
];
```

Doğal olarak tüm bu ölçütler birleştirilebilir. Ölçütlerden en az biri artık karşılanmıyorsa önbellek öğesinin süresi dolar.

```php
$dependencies[Cache::Expire] = '20 minutes';
$dependencies[Cache::Files] = '/path/to/data.yaml';
```


Etiketlerle Geçersiz Kılma
--------------------------

Etiketler çok yararlı bir geçersiz kılma mekanizması sunar. Önbellekte saklanan her öğeye bir etiket listesi (rastgele dizeler) atayabiliriz. Örneğin, önbelleğe almak istediğimiz, bir makaleyi ve yorumlarını gösteren bir HTML sayfamız olduğunu varsayalım. Kaydederken ilgili etiketleri belirtiriz:

```php
$dependencies[Cache::Tags] = ["article/$articleId", "comments/$articleId"];
```

Şimdi yönetim bölümüne geçelim. Burada makaleleri düzenlemek için bir formumuz var. Makaleyi veritabanına kaydetmenin yanı sıra, önbellekteki öğeleri etiketlerine göre silmek için `clean()` metodunu çağırırız:

```php
$cache->clean([
	$cache::Tags => ["article/$articleId"],
]);
```

Benzer biçimde, yeni bir yorum eklerken (ya da bir yorumu düzenlerken) ilgili etiketi geçersiz kılmayı unutmamalıyız:

```php
$cache->clean([
	$cache::Tags => ["comments/$articleId"],
]);
```

Ne başardık? HTML önbelleğimiz artık ilişkili makale ya da yorumları her değiştiğinde geçersiz kılınacak (silinecek). ID = 10 olan makale düzenlendiğinde `article/10` etiketi geçersiz kılınır ve bu etiketi taşıyan önbellekteki HTML sayfası silinir. Aynısı, ilgili makalenin altına yeni bir yorum eklendiğinde de olur.

.[note]
Etiketler bir [#Journal] gerektirir.


Önceliğe Göre Geçersiz Kılma
----------------------------

Tek tek önbellek öğelerine öncelik atayabiliriz. Bu, örneğin önbellek belirli bir boyut sınırını aştığında denetimli silme olanağı sağlar:

```php
$dependencies[Cache::Priority] = 50;
```

Önceliği 100'e eşit ya da ondan küçük olan tüm öğeleri silmek için:

```php
$cache->clean([
	$cache::Priority => 100,
]);
```

.[note]
Öncelikler [#Journal] denen şeyi gerektirir.


Önbelleği Temizleme
-------------------

`Cache::All` parametresi her şeyi temizler:

```php
$cache->clean([
	$cache::All => true,
]);
```


Toplu Okuma
===========

Önbellekten toplu okuma ve ona toplu yazma için `bulkLoad()` metodunu kullanın. Ona bir anahtar dizisi aktarın; o da karşılık gelen değerlerden oluşan bir dizi döndürür:

```php
$values = $cache->bulkLoad($keys);
```

`bulkLoad()` metodu `load()` metoduna benzer çalışır ve ikinci bir callback parametresi de kabul eder. Bu callback, üretilmekte olan öğenin anahtarını alır:

```php
$values = $cache->bulkLoad($keys, function ($key, &$dependencies) {
	$computedValue = /* ... */; // pahalı hesaplama
	return $computedValue;
});
```

Tersine, birden çok öğeyi tek seferde yazmak için, `key => value` çiftlerinden oluşan bir dizi ve isteğe bağlı bağımlılıklar alan `bulkSave()` metodunu kullanın:

```php
$cache->bulkSave([
	$key1 => $value1,
	$key2 => $value2,
], [Cache::Expire => '20 minutes']);
```


PSR-16 ile Kullanım .{data-version:3.3.1}
=========================================

Nette Cache'i bir PSR-16 arayüzüyle kullanmak için `PsrCacheAdapter` sınıfından yararlanabilirsiniz. Bu, Nette Cache ile PSR-16 uyumlu bir önbellek gerçekleştirimi bekleyen her kod ya da kütüphane arasında kusursuz entegrasyon sağlar.

```php
$psrCache = new Nette\Bridges\Psr\PsrCacheAdapter($storage);
```

Artık `$psrCache` nesnesini standart bir PSR-16 önbelleği olarak kullanabilirsiniz:

```php
$psrCache->set('key', 'value', 3600); // değeri 1 saat saklar
$value = $psrCache->get('key', 'default');
```

Adaptör, `getMultiple()`, `setMultiple()` ve `deleteMultiple()` dahil PSR-16'da tanımlı tüm metotları destekler.


Çıktı Önbellekleme
==================

Çıktı çok zarif biçimde yakalanıp önbelleğe alınabilir:

```php
if ($capture = $cache->capture($key)) {

	// echo ... bir miktar veri yazdırılıyor

	$capture->end(); // çıktıyı önbelleğe kaydet
}
```

Çıktı önbellekte zaten varsa, `capture()` metodu onu yazdırır ve `null` döndürür, dolayısıyla `if` koşulunun bloğu atlanır. Aksi hâlde çıktıyı arabelleğe almaya başlar ve bir `$capture` nesnesi döndürür; yakalanan veriyi sonunda `end()` metoduyla önbelleğe kaydetmek için onu kullanırsınız.

.[note]
3.0 sürümünde bu metodun adı `$cache->start()` idi.


Latte'de Önbellekleme
=====================

[Latte|latte:] şablonlarında önbellekleme çok basittir. Şablonun önbelleğe almak istediğiniz bölümünü `{cache}...{/cache}` etiketleriyle sarmanız yeter. Kaynak şablon dosyası (önbelleğe alınan bloğun içine eklenen şablonlar dahil) her değiştiğinde önbellek otomatik geçersiz kılınır. `{cache}` etiketleri iç içe olabilir. İç içe bir blok geçersiz kılındığında (örneğin bir etiket aracılığıyla), onun ana bloğu da geçersiz kılınır.

Etiketin içinde, önbellek girdisinin bağlanacağı anahtarları (burada `$id` değişkeni) belirtebilir, bir süre dolma zamanı ayarlayabilir ve [geçersiz kılma etiketleri |#Etiketlerle Geçersiz Kılma] tanımlayabilirsiniz.

```latte
{cache $id, expire: '20 minutes', tags: [tag1, tag2]}
	...
{/cache}
```

Tüm bu parametreler isteğe bağlıdır, dolayısıyla süre dolmasını, etiketleri, hatta anahtarları belirtmeniz gerekmez.

Önbelleklemenin kullanımı `if` ile koşullu da kılınabilir; içerik yalnızca koşul karşılanırsa önbelleğe alınır:

```latte
{cache $id, if: !$form->isSubmitted()}
	{$form}
{/cache}
```


Depolar
=======

Depo, verinin saklandığı fiziksel konumu temsil eden bir nesnedir. Bir veritabanı, bir Memcached sunucusu ya da en kolay erişilebilen depoyu, yani diskteki dosyaları kullanabiliriz.

|----------------------
| Depo | Açıklama
|----------------------
| [#FileStorage] | Varsayılan depo, önbelleği diskteki dosyalara kaydeder.
| [#MemcachedStorage] | Saklama için bir `Memcached` sunucusu kullanır.
| [#MemoryStorage] | Veri bellekte geçici olarak saklanır (istek bitince yiter).
| [#SQLiteStorage] | Veri bir SQLite veritabanı dosyasında saklanır.
| [#DevNullStorage] | Veri aslında saklanmaz; sınama için yararlıdır.

Depo nesnesini, `Nette\Caching\Storage` tipini isteyerek [bağımlılık enjeksiyonuyla |dependency-injection:passing-dependencies] elde edersiniz. Nette varsayılan olarak, veriyi [geçici dosyalar |application:bootstrapping#Geçici dosyalar] dizinindeki `cache` alt dizininde saklayan bir `FileStorage` nesnesi sağlar.

Varsayılan depoyu yapılandırmada değiştirebilirsiniz:

```neon
services:
	cache.storage: Nette\Caching\Storages\DevNullStorage
```


FileStorage
-----------

Önbellek girdilerini diskteki dosyalara yazar. `Nette\Caching\Storages\FileStorage` deposu başarım açısından epey iyileştirilmiştir ve en önemlisi, işlemlerin tam atomikliğini güvence altına alır. Bu ne demek? Önbelleği kullanırken, başka bir iş parçacığının henüz tümüyle yazmadığı bir dosyayı okumanız ya da siz okurken birinin onu silmesi olanaksızdır. Bu yüzden bu önbellek deposunu kullanmak tümüyle güvenlidir.

Bu depo ayrıca, önbellek temizlendiğinde ya da hâlâ "soğuk" olduğunda (yani henüz oluşturulmadığında) CPU kullanımında aşırı bir sıçramayı önleyen önemli, yerleşik bir özellik içerir. Buna "cache stampede":https://en.wikipedia.org/wiki/Cache_stampede önleme denir. Bu durum, birden çok eşzamanlı istek aynı önbellek öğesini (örneğin pahalı bir SQL sorgusunun sonucunu) aynı anda istediğinde ortaya çıkar. Öğe o an önbellekte değilse, tüm bu süreçler aynı pahalı işlemi (SQL sorgusu gibi) yürütmeye başlayabilir. Bu, sunucu yükünü katlar ve hiçbir iş parçacığının zaman sınırı içinde yanıt verememesi, önbelleğin oluşmaması ve uygulamanın çökmesi bile olabilir. Neyse ki Nette'in önbelleği bunu ele alır: aynı öğe için birden çok eşzamanlı istek yapıldığında yalnızca ilk iş parçacığı onu üretir. Diğer iş parçacıkları bekler ve sonra ilkinin ürettiği sonucu kullanır.

`FileStorage` oluşturma örneği:

```php
// depo, diskteki '/path/to/temp' dizini olacak
$storage = new Nette\Caching\Storages\FileStorage('/path/to/temp');
```


MemcachedStorage
----------------

[Memcached |https://memcached.org] sunucusu, yüksek başarımlı, dağıtık bir bellek nesnesi önbellekleme sistemidir. Nette'deki adaptörü `Nette\Caching\Storages\MemcachedStorage` sınıfıdır. Yapılandırmada sunucunun IP adresini ve standart 11211'den farklıysa portunu belirtin.

.[caution]
`memcached` PHP uzantısını gerektirir.

```neon
services:
	cache.storage: Nette\Caching\Storages\MemcachedStorage('10.0.0.5')
```


MemoryStorage
-------------

`Nette\Caching\Storages\MemoryStorage`, veriyi bir PHP dizisinin içinde tutan bir depodur. Dolayısıyla istek bittiğinde veri yiter.


SQLiteStorage
-------------

SQLite veritabanı, `Nette\Caching\Storages\SQLiteStorage` adaptörüyle birlikte, veriyi diskteki tek bir dosyada önbelleğe almanın bir yolunu sunar. Yapılandırma, bu veritabanı dosyasının yolunu belirtir.

.[caution]
`pdo` ve `pdo_sqlite` PHP uzantılarını gerektirir.

```neon
services:
	cache.storage: Nette\Caching\Storages\SQLiteStorage('%tempDir%/cache.db')
```


DevNullStorage
--------------

Özel bir depo gerçekleştirimi, aslında hiçbir veri saklamayan `Nette\Caching\Storages\DevNullStorage` sınıfıdır. Bu yüzden önbelleklemenin etkilerini ortadan kaldırmak istediğinizde sınama amaçları için uygundur.


Kodda Önbelleği Kullanma
========================

Kodunuzda önbellekleme kullanırken iki ana yaklaşım vardır. İlki, depo nesnesini [bağımlılık enjeksiyonuyla |dependency-injection:passing-dependencies] elde etmek ve sonra `Cache` nesnesini kendiniz oluşturmaktır:

```php
use Nette;

class ClassOne
{
	private Nette\Caching\Cache $cache;

	public function __construct(Nette\Caching\Storage $storage)
	{
		$this->cache = new Nette\Caching\Cache($storage, 'my-namespace');
	}
}
```

İkinci seçenek, doğrudan `Cache` nesnesini istemektir:

```php
class ClassTwo
{
	public function __construct(
		private Nette\Caching\Cache $cache,
	) {
	}
}
```

`Cache` nesnesi o zaman yapılandırmada, örneğin şöyle tanımlanmalıdır:

```neon
services:
	- ClassTwo( Nette\Caching\Cache(namespace: 'my-namespace') )
```


Journal
=======

Nette, etiket ve öncelik bilgisini journal denen bir yerde saklar. Bunun için varsayılan olarak `journal.s3db` dosyası aracılığıyla SQLite kullanılır ve **`pdo` ile `pdo_sqlite` PHP uzantıları gereklidir.**

Journal gerçekleştirimini yapılandırmada değiştirebilirsiniz:

```neon
services:
	cache.journal: MyJournal
```


DI Servisleri
=============

DI container'a şu servisler eklenir:

| Ad | Tip | Açıklama
|----------------------------------------------------------
| `cache.journal` | [api:Nette\Caching\Storages\Journal] | Önbellek journal deposu
| `cache.storage` | [api:Nette\Caching\Storage] | Birincil önbellek deposu


Önbelleği Kapatma
=================

Uygulamanızda önbelleklemeyi kapatmanın bir yolu, depo arka ucunu [#DevNullStorage] olarak ayarlamaktır:

```neon
services:
	cache.storage: Nette\Caching\Storages\DevNullStorage
```

Bu ayar, Latte şablonlarının ya da DI container'ın önbelleklenmesini etkilemez; çünkü bu kütüphaneler `nette/caching` servislerini kullanmaz ve önbelleklerini bağımsız yönetir. Üstelik onların önbelleklerinin geliştirme kipinde [genellikle kapatılması gerekmez |nette:troubleshooting#Geliştirme Sırasında Önbellek Nasıl Kapatılır?].


Daha yeni bir sürüme yükseltiyorsanız [yükseltme |upgrading] sayfasına bakın.

Nette Caching

Önbellek, bir zamanlar hesaplaması pahalı olan veriyi saklayarak uygulamanızı hızlandırır ve gelecekte ona daha hızlı erişilmesini sağlar. Şunları ele alacağız:

  • önbelleğin nasıl kullanılacağı
  • depo arka ucunun nasıl değiştirileceği
  • önbelleğin doğru biçimde nasıl geçersiz kılınacağı

Nette'de önbelleği kullanmak çok basittir, yine de gelişmiş önbellekleme gereksinimlerini karşılar. Başarım ve %100 dayanıklılık için tasarlanmıştır. En yaygın depo arka uçları için adaptörler içerir. Etikete dayalı geçersiz kılmayı, zaman aşımını, cache stampede'e karşı korumayı ve dahasını destekler.

Kurulum

Paketi Composer kullanarak indirin ve kurun:

composer require nette/caching

Temel Kullanım

Önbellekle çalışmanın çekirdek unsuru Nette\Caching\Cache nesnesidir. Onun bir örneğini oluşturur ve yapıcıya bir depo arka ucu nesnesi aktarırız. Bu depo nesnesi, verinin saklanacağı fiziksel konumu temsil eder (veritabanı, Memcached, diskteki dosyalar vb.). Depo nesnesini genellikle Nette\Caching\Storage tipini isteyerek bağımlılık enjeksiyonuyla elde edersiniz. Temelleri Depolar bölümünde öğreneceksiniz.

3.0 sürümünde arayüzün hâlâ I öneki vardı, dolayısıyla adı Nette\Caching\IStorage idi. Ayrıca Cache sınıfının sabitleri büyük harfle yazılıyordu, örneğin Cache::Expire yerine Cache::EXPIRE.

Aşağıdaki örnekler için Cache adında bir alias'ımız ve $storage değişkeninde bir depo örneğimiz olduğunu varsayın.

use Nette\Caching\Cache;

$storage = /* ... */; // Nette\Caching\Storage örneği

Önbellek özünde bir anahtar-değer deposudur; yani veriyi, ilişkisel dizilere benzer biçimde anahtarlarla okur ve yazarız. Uygulamalar birden çok bağımsız parçadan oluşur. Tüm parçalar tek bir depoyu kullansaydı (diskte tek bir dizin düşünün), er ya da geç anahtar çakışmaları olurdu. Nette Framework bunu, depo alanını ad alanlarına (kavramsal olarak alt dizinler gibi) bölerek çözer. Uygulamanın her parçası böylece benzersiz bir adla kendi ad alanında çalışır ve hiçbir çakışma olmaz.

Ad alanı adını Cache sınıfı yapıcısının ikinci argümanı olarak belirtin:

$cache = new Cache($storage, 'Full Html Pages');

Gerekirse, var olan bir örnekten derive() metodunu kullanarak bir alt ad alanına kapsamlanmış yeni bir önbellek türetebilirsiniz:

$subCache = $cache->derive('Images');

Artık $cache nesnesini önbellekten okumak ve ona yazmak için kullanabiliriz. load() metodu her iki amaca da hizmet eder. İlk argüman anahtar, ikincisi ise anahtar önbellekte bulunamazsa çağrılan bir PHP callback'idir. Callback değeri üretir, onu döndürür ve load() metodu onu önbelleğe alır:

$value = $cache->load($key, function () use ($key) {
	$computedValue = /* ... */; // pahalı hesaplama
	return $computedValue;
});

İkinci parametre atlanırsa ($value = $cache->load($key)), öğe önbellekte bulunamadığında load() metodu null döndürür.

Yalnızca dizelerin değil, serileştirilebilir her yapının önbelleğe alınabilmesi harikadır. Aynısı anahtarlar için de geçerlidir.

Bir öğeyi önbellekten silmek için remove() metodunu kullanın:

$cache->remove($key);

Bir öğeyi önbelleğe $cache->save($key, $data, ?array $dependencies = null) metoduyla da kaydedebilirsiniz. Ancak yukarıda gösterilen load() yaklaşımı genellikle yeğlenir.

Memoization

Memoization, bir fonksiyon ya da metot çağrısının sonucunu önbelleğe almaktır; böylece bir dahaki sefere aynı argümanlarla çağrıldığında yeniden hesaplanmak yerine önbellekteki sonuç döndürülür.

Metotlar ve fonksiyonlar, call(callable $callback, ...$args) kullanılarak memoize edilmiş biçimde çağrılabilir:

$result = $cache->call('gethostbyaddr', $ip);

gethostbyaddr() fonksiyonu böylece her benzersiz $ip argümanı için yalnızca bir kez çağrılır. Aynı $ip ile sonraki çağrılar önbellekteki değeri döndürür.

Bir metodun ya da fonksiyonun etrafında, sonradan çağrılabilecek memoize edilmiş bir sarmalayıcı oluşturmak da mümkündür:

function factorial($num)
{
	return /* ... */;
}

$memoizedFactorial = $cache->wrap('factorial');

$result = $memoizedFactorial(5); // ilk seferde hesaplar
$result = $memoizedFactorial(5); // ikinci seferde önbellekten döndürür

Süre Dolması ve Geçersiz Kılma

Önbellekleme kullanırken, daha önce saklanmış verinin ne zaman geçersiz olacağı sorusunu ele almak gerekir. Nette Framework, verinin geçerliliğini sınırlamak ya da onu açıkça silmek için mekanizmalar sunar (framework'ün terminolojisinde buna „geçersiz kılma“ denir).

Verinin geçerliliği kaydetme sırasında, genellikle save() metodunun üçüncü parametresiyle ayarlanır; örneğin:

$cache->save($key, $value, [
	$cache::Expire => '20 minutes',
]);

Alternatif olarak, load() metodundaki callback'e başvuruyla aktarılan $dependencies parametresiyle ayarlanabilir; örneğin:

$value = $cache->load($key, function (&$dependencies) {
	$dependencies[Cache::Expire] = '20 minutes';
	return /* ... */;
});

Ya da load() metodunun kendi 3. parametresi kullanılarak; örneğin:

$value = $cache->load($key, function () {
	return /* ... */;
}, [Cache::Expire => '20 minutes']);

Aşağıdaki örneklerde, callback içinde $dependencies değişkenini kullanan ikinci çeşidi varsayacağız.

Süre Dolması

Süre dolmasının en basit biçimi bir zaman sınırıdır. Bu, veriyi 20 dakikalık geçerlilikle önbelleğe alır:

// saniye sayısını ya da bir UNIX zaman damgasını da kabul eder
$dependencies[Cache::Expire] = '20 minutes';

Geçerlilik süresinin her okumada uzamasını istiyorsanız (kayan süre dolması), bunu şöyle sağlayabilirsiniz, ama bunun önbellek ek yükünü artırdığını bilin:

$dependencies[Cache::Sliding] = true;

Yararlı bir seçenek, belirli bir dosya ya da birkaç dosyadan biri değiştirildiğinde verinin süresini doldurmaktır. Bu, örneğin bu dosyaların işlenmesinden türeyen veriyi önbelleğe alırken yararlıdır. Mutlak yollar kullanın.

$dependencies[Cache::Files] = '/path/to/data.yaml';
// ya da
$dependencies[Cache::Files] = ['/path/to/data1.yaml', '/path/to/data2.yaml'];

Bir önbellek öğesinin, başka belirli bir öğenin (ya da birkaçından birinin) süresi dolduğunda süresini doldurmasını sağlayabiliriz. Bu, örneğin bir HTML sayfasının tamamını ve parçalarını farklı anahtarlarla önbelleğe alırken yararlıdır. Bir parça değiştiğinde sayfanın tamamı geçersiz kılınmalıdır. Parçalar frag1 ve frag2 gibi anahtarlarla saklanıyorsa şunu kullanın:

$dependencies[Cache::Items] = ['frag1', 'frag2'];

Süre dolması, özel fonksiyonlar ya da statik metotlar kullanılarak da denetlenebilir. Bunlar, öğenin hâlâ geçerli olup olmadığını saptamak için her okumada çağrılır. Örneğin, PHP sürümü her değiştiğinde bir öğenin süresini doldurabiliriz. Geçerli sürümü bir parametreyle karşılaştıran bir fonksiyon oluşturun ve kaydederken bağımlılıklara [fonksiyon adı, ...argümanlar] biçiminde bir dizi ekleyin:

function checkPhpVersion($ver): bool
{
	return $ver === PHP_VERSION_ID;
}

$dependencies[Cache::Callbacks] = [
	['checkPhpVersion', PHP_VERSION_ID] // checkPhpVersion(...) === false olduğunda süresi dolar
];

Doğal olarak tüm bu ölçütler birleştirilebilir. Ölçütlerden en az biri artık karşılanmıyorsa önbellek öğesinin süresi dolar.

$dependencies[Cache::Expire] = '20 minutes';
$dependencies[Cache::Files] = '/path/to/data.yaml';

Etiketlerle Geçersiz Kılma

Etiketler çok yararlı bir geçersiz kılma mekanizması sunar. Önbellekte saklanan her öğeye bir etiket listesi (rastgele dizeler) atayabiliriz. Örneğin, önbelleğe almak istediğimiz, bir makaleyi ve yorumlarını gösteren bir HTML sayfamız olduğunu varsayalım. Kaydederken ilgili etiketleri belirtiriz:

$dependencies[Cache::Tags] = ["article/$articleId", "comments/$articleId"];

Şimdi yönetim bölümüne geçelim. Burada makaleleri düzenlemek için bir formumuz var. Makaleyi veritabanına kaydetmenin yanı sıra, önbellekteki öğeleri etiketlerine göre silmek için clean() metodunu çağırırız:

$cache->clean([
	$cache::Tags => ["article/$articleId"],
]);

Benzer biçimde, yeni bir yorum eklerken (ya da bir yorumu düzenlerken) ilgili etiketi geçersiz kılmayı unutmamalıyız:

$cache->clean([
	$cache::Tags => ["comments/$articleId"],
]);

Ne başardık? HTML önbelleğimiz artık ilişkili makale ya da yorumları her değiştiğinde geçersiz kılınacak (silinecek). ID = 10 olan makale düzenlendiğinde article/10 etiketi geçersiz kılınır ve bu etiketi taşıyan önbellekteki HTML sayfası silinir. Aynısı, ilgili makalenin altına yeni bir yorum eklendiğinde de olur.

Etiketler bir Journal gerektirir.

Önceliğe Göre Geçersiz Kılma

Tek tek önbellek öğelerine öncelik atayabiliriz. Bu, örneğin önbellek belirli bir boyut sınırını aştığında denetimli silme olanağı sağlar:

$dependencies[Cache::Priority] = 50;

Önceliği 100'e eşit ya da ondan küçük olan tüm öğeleri silmek için:

$cache->clean([
	$cache::Priority => 100,
]);

Öncelikler Journal denen şeyi gerektirir.

Önbelleği Temizleme

Cache::All parametresi her şeyi temizler:

$cache->clean([
	$cache::All => true,
]);

Toplu Okuma

Önbellekten toplu okuma ve ona toplu yazma için bulkLoad() metodunu kullanın. Ona bir anahtar dizisi aktarın; o da karşılık gelen değerlerden oluşan bir dizi döndürür:

$values = $cache->bulkLoad($keys);

bulkLoad() metodu load() metoduna benzer çalışır ve ikinci bir callback parametresi de kabul eder. Bu callback, üretilmekte olan öğenin anahtarını alır:

$values = $cache->bulkLoad($keys, function ($key, &$dependencies) {
	$computedValue = /* ... */; // pahalı hesaplama
	return $computedValue;
});

Tersine, birden çok öğeyi tek seferde yazmak için, key => value çiftlerinden oluşan bir dizi ve isteğe bağlı bağımlılıklar alan bulkSave() metodunu kullanın:

$cache->bulkSave([
	$key1 => $value1,
	$key2 => $value2,
], [Cache::Expire => '20 minutes']);

PSR-16 ile Kullanım

Nette Cache'i bir PSR-16 arayüzüyle kullanmak için PsrCacheAdapter sınıfından yararlanabilirsiniz. Bu, Nette Cache ile PSR-16 uyumlu bir önbellek gerçekleştirimi bekleyen her kod ya da kütüphane arasında kusursuz entegrasyon sağlar.

$psrCache = new Nette\Bridges\Psr\PsrCacheAdapter($storage);

Artık $psrCache nesnesini standart bir PSR-16 önbelleği olarak kullanabilirsiniz:

$psrCache->set('key', 'value', 3600); // değeri 1 saat saklar
$value = $psrCache->get('key', 'default');

Adaptör, getMultiple(), setMultiple() ve deleteMultiple() dahil PSR-16'da tanımlı tüm metotları destekler.

Çıktı Önbellekleme

Çıktı çok zarif biçimde yakalanıp önbelleğe alınabilir:

if ($capture = $cache->capture($key)) {

	// echo ... bir miktar veri yazdırılıyor

	$capture->end(); // çıktıyı önbelleğe kaydet
}

Çıktı önbellekte zaten varsa, capture() metodu onu yazdırır ve null döndürür, dolayısıyla if koşulunun bloğu atlanır. Aksi hâlde çıktıyı arabelleğe almaya başlar ve bir $capture nesnesi döndürür; yakalanan veriyi sonunda end() metoduyla önbelleğe kaydetmek için onu kullanırsınız.

3.0 sürümünde bu metodun adı $cache->start() idi.

Latte'de Önbellekleme

Latte şablonlarında önbellekleme çok basittir. Şablonun önbelleğe almak istediğiniz bölümünü {cache}...{/cache} etiketleriyle sarmanız yeter. Kaynak şablon dosyası (önbelleğe alınan bloğun içine eklenen şablonlar dahil) her değiştiğinde önbellek otomatik geçersiz kılınır. {cache} etiketleri iç içe olabilir. İç içe bir blok geçersiz kılındığında (örneğin bir etiket aracılığıyla), onun ana bloğu da geçersiz kılınır.

Etiketin içinde, önbellek girdisinin bağlanacağı anahtarları (burada $id değişkeni) belirtebilir, bir süre dolma zamanı ayarlayabilir ve geçersiz kılma etiketleri tanımlayabilirsiniz.

{cache $id, expire: '20 minutes', tags: [tag1, tag2]}
	...
{/cache}

Tüm bu parametreler isteğe bağlıdır, dolayısıyla süre dolmasını, etiketleri, hatta anahtarları belirtmeniz gerekmez.

Önbelleklemenin kullanımı if ile koşullu da kılınabilir; içerik yalnızca koşul karşılanırsa önbelleğe alınır:

{cache $id, if: !$form->isSubmitted()}
	{$form}
{/cache}

Depolar

Depo, verinin saklandığı fiziksel konumu temsil eden bir nesnedir. Bir veritabanı, bir Memcached sunucusu ya da en kolay erişilebilen depoyu, yani diskteki dosyaları kullanabiliriz.

Depo Açıklama
FileStorage Varsayılan depo, önbelleği diskteki dosyalara kaydeder.
MemcachedStorage Saklama için bir Memcached sunucusu kullanır.
MemoryStorage Veri bellekte geçici olarak saklanır (istek bitince yiter).
SQLiteStorage Veri bir SQLite veritabanı dosyasında saklanır.
DevNullStorage Veri aslında saklanmaz; sınama için yararlıdır.

Depo nesnesini, Nette\Caching\Storage tipini isteyerek bağımlılık enjeksiyonuyla elde edersiniz. Nette varsayılan olarak, veriyi geçici dosyalar dizinindeki cache alt dizininde saklayan bir FileStorage nesnesi sağlar.

Varsayılan depoyu yapılandırmada değiştirebilirsiniz:

services:
	cache.storage: Nette\Caching\Storages\DevNullStorage

FileStorage

Önbellek girdilerini diskteki dosyalara yazar. Nette\Caching\Storages\FileStorage deposu başarım açısından epey iyileştirilmiştir ve en önemlisi, işlemlerin tam atomikliğini güvence altına alır. Bu ne demek? Önbelleği kullanırken, başka bir iş parçacığının henüz tümüyle yazmadığı bir dosyayı okumanız ya da siz okurken birinin onu silmesi olanaksızdır. Bu yüzden bu önbellek deposunu kullanmak tümüyle güvenlidir.

Bu depo ayrıca, önbellek temizlendiğinde ya da hâlâ „soğuk“ olduğunda (yani henüz oluşturulmadığında) CPU kullanımında aşırı bir sıçramayı önleyen önemli, yerleşik bir özellik içerir. Buna cache stampede önleme denir. Bu durum, birden çok eşzamanlı istek aynı önbellek öğesini (örneğin pahalı bir SQL sorgusunun sonucunu) aynı anda istediğinde ortaya çıkar. Öğe o an önbellekte değilse, tüm bu süreçler aynı pahalı işlemi (SQL sorgusu gibi) yürütmeye başlayabilir. Bu, sunucu yükünü katlar ve hiçbir iş parçacığının zaman sınırı içinde yanıt verememesi, önbelleğin oluşmaması ve uygulamanın çökmesi bile olabilir. Neyse ki Nette'in önbelleği bunu ele alır: aynı öğe için birden çok eşzamanlı istek yapıldığında yalnızca ilk iş parçacığı onu üretir. Diğer iş parçacıkları bekler ve sonra ilkinin ürettiği sonucu kullanır.

FileStorage oluşturma örneği:

// depo, diskteki '/path/to/temp' dizini olacak
$storage = new Nette\Caching\Storages\FileStorage('/path/to/temp');

MemcachedStorage

Memcached sunucusu, yüksek başarımlı, dağıtık bir bellek nesnesi önbellekleme sistemidir. Nette'deki adaptörü Nette\Caching\Storages\MemcachedStorage sınıfıdır. Yapılandırmada sunucunun IP adresini ve standart 11211'den farklıysa portunu belirtin.

memcached PHP uzantısını gerektirir.

services:
	cache.storage: Nette\Caching\Storages\MemcachedStorage('10.0.0.5')

MemoryStorage

Nette\Caching\Storages\MemoryStorage, veriyi bir PHP dizisinin içinde tutan bir depodur. Dolayısıyla istek bittiğinde veri yiter.

SQLiteStorage

SQLite veritabanı, Nette\Caching\Storages\SQLiteStorage adaptörüyle birlikte, veriyi diskteki tek bir dosyada önbelleğe almanın bir yolunu sunar. Yapılandırma, bu veritabanı dosyasının yolunu belirtir.

pdo ve pdo_sqlite PHP uzantılarını gerektirir.

services:
	cache.storage: Nette\Caching\Storages\SQLiteStorage('%tempDir%/cache.db')

DevNullStorage

Özel bir depo gerçekleştirimi, aslında hiçbir veri saklamayan Nette\Caching\Storages\DevNullStorage sınıfıdır. Bu yüzden önbelleklemenin etkilerini ortadan kaldırmak istediğinizde sınama amaçları için uygundur.

Kodda Önbelleği Kullanma

Kodunuzda önbellekleme kullanırken iki ana yaklaşım vardır. İlki, depo nesnesini bağımlılık enjeksiyonuyla elde etmek ve sonra Cache nesnesini kendiniz oluşturmaktır:

use Nette;

class ClassOne
{
	private Nette\Caching\Cache $cache;

	public function __construct(Nette\Caching\Storage $storage)
	{
		$this->cache = new Nette\Caching\Cache($storage, 'my-namespace');
	}
}

İkinci seçenek, doğrudan Cache nesnesini istemektir:

class ClassTwo
{
	public function __construct(
		private Nette\Caching\Cache $cache,
	) {
	}
}

Cache nesnesi o zaman yapılandırmada, örneğin şöyle tanımlanmalıdır:

services:
	- ClassTwo( Nette\Caching\Cache(namespace: 'my-namespace') )

Journal

Nette, etiket ve öncelik bilgisini journal denen bir yerde saklar. Bunun için varsayılan olarak journal.s3db dosyası aracılığıyla SQLite kullanılır ve pdo ile pdo_sqlite PHP uzantıları gereklidir.

Journal gerçekleştirimini yapılandırmada değiştirebilirsiniz:

services:
	cache.journal: MyJournal

DI Servisleri

DI container'a şu servisler eklenir:

Ad Tip Açıklama
cache.journal Nette\Caching\Storages\Journal Önbellek journal deposu
cache.storage Nette\Caching\Storage Birincil önbellek deposu

Önbelleği Kapatma

Uygulamanızda önbelleklemeyi kapatmanın bir yolu, depo arka ucunu DevNullStorage olarak ayarlamaktır:

services:
	cache.storage: Nette\Caching\Storages\DevNullStorage

Bu ayar, Latte şablonlarının ya da DI container'ın önbelleklenmesini etkilemez; çünkü bu kütüphaneler nette/caching servislerini kullanmaz ve önbelleklerini bağımsız yönetir. Üstelik onların önbelleklerinin geliştirme kipinde genellikle kapatılması gerekmez.

Daha yeni bir sürüme yükseltiyorsanız yükseltme sayfasına bakın.