PHP'den Redis'e bağlanmak için Redis belgelerinin önerdiği kütüphane Predis'tir: composer require predis/predis ile kurar, new Predis\Client([...]) ile bir istemci oluşturur ve $r->set('foo', 'bar'), $r->get('foo') gibi komut adlarıyla aynı isimdeki metotları çağırırsınız. Predis üçüncü taraf bir kütüphanedir; Redis ekibi onu doğrudan geliştirmez ama katkı verir ve sorunlarda yardımcı olur, belgeler bunu "en iyi çaba" (best effort) desteği olarak tanımlar. Saf PHP ile yazıldığı için C eklentisi olan phpredis'e göre performansı sınırlıdır; buna karşılık Redis belgelerinde çok daha iyi belgelenmiştir.
Bu yazıda Predis'in kurulumunu, bağlantı parametrelerini, URL ile bağlanırken dikkat edilecek noktaları, cluster ve Sentinel bağlantılarını, pipeline ve transaction kullanımını, cas seçeneğiyle iyimser kilidi, Predis 3 ile değişen hset çağrısını ve istisna hiyerarşisini belgelerdeki örneklerle anlatıyoruz. Yazı PHP dilini değil, Redis kütüphanesini konu alıyor; dil tarafı için PHP öğrenme rehberi yazısına bakabilirsiniz. Serinin geneli Redis öğrenme rehberi sayfasında.
Predis mi phpredis mi?

Redis belgeleri PHP için iki seçenekten söz eder ve aralarındaki farkı açıkça koyar. Seçimi yaparken şu tabloyu kullanabilirsiniz:
| Özellik | Predis | phpredis |
|---|---|---|
| Uygulama biçimi | Saf PHP kütüphanesi | C ile yazılmış PHP eklentisi |
| Kurulum | Composer paketi | PHP eklentisi olarak |
| Redis ekibinin desteği | En iyi çaba desteği, belgelerde iyi kapsanıyor | Redis ekibi tarafından desteklenmiyor ve belgelenmiyor |
| Performans | PHP ile sınırlı | C eklentisi olduğu için daha yüksek |
Belgelerdeki karar ağacının PHP sorusu da tam olarak budur: en yüksek performans, tam destek ve belgeden daha önemliyse phpredis; değilse Predis. Bu yazıdaki tüm örnekler Predis içindir.
Predis kurulumu ve bağlantı
composer require predis/predisBelgelerdeki temel bağlantı örneği parametreleri bir dizi olarak verir. scheme alanı düz TCP için tcp, TLS için tls olur; database mantıksal veritabanı numarasıdır:
<?php
require 'vendor/autoload.php';
use Predis\Client as PredisClient;
$r = new PredisClient([
'scheme' => 'tcp',
'host' => '127.0.0.1',
'port' => 6379,
'password' => '',
'database' => 0,
]);
echo $r->set('foo', 'bar'), PHP_EOL;
// >>> OK
echo $r->get('foo'), PHP_EOL;
// >>> barHash ile çalışmak için hset() ve hgetall() kullanılır. hgetall() bir PHP ilişkisel dizisi döndürür ve belgelerdeki çıktıda görüldüğü gibi sayı olarak yazdığınız 29 değeri '29' metni olarak geri gelir:
$r->hset('user-session:123', 'name', 'John');
$r->hset('user-session:123', 'surname', 'Smith');
$r->hset('user-session:123', 'company', 'Redis');
$r->hset('user-session:123', 'age', 29);
echo var_export($r->hgetall('user-session:123')), PHP_EOL;
/* >>>
array (
'name' => 'John',
'surname' => 'Smith',
'company' => 'Redis',
'age' => '29',
)
*/Hash komutlarının sunucu tarafındaki davranışını Redis hash komutları yazısında anlatıyoruz.
Predis 3 ve hset çağrısı
Belgelerdeki kullanım örnekleri önemli bir sürüm farkına dikkat çeker. Önceden getirme (prefetch) önbelleği örneğine göre Predis 1.x, hset için ['alan' => 'değer'] biçiminde ilişkisel bir diziyi doğrudan kabul ediyordu; aynı çağrı Predis 3.x'e karşı wrong number of arguments for 'hset' hatasıyla sonuçlanır. Özellik deposu örneği de Predis 3'ün hset metodunun alan ve değerleri sırayla, değişken sayıda argüman olarak beklediğini söyler. Bu yüzden belgeler ilişkisel diziyi önce düz bir listeye çevirir:
$flat = [];
foreach ($fields as $name => $value) {
$flat[] = $name;
$flat[] = $value;
}
$pipe->hset($key, ...$flat);Aynı örnek, alan listesi boşken HSET'in hiç argümansız çağrılmasının Predis 3.x'te hata verdiğini de not eder; bu nedenle boş dizi gelmesi ihtimaline karşı çağrıdan önce kontrol koymak gerekir. Belgelerin bazı sayfalarında hâlâ ilişkisel dizili çağrılar göründüğü için, kendi projenizde hangi Predis sürümünü kullandığınızı composer.json dosyanızdan kontrol etmeniz iyi olur.
URL ile Predis bağlantısı ve dikkat edilecekler
Predis, bağlantı URL'sini doğrudan Predis\Client yapıcısına almanıza izin verir: new Predis\Client('rediss://default:şifre@sunucu:6379/0'). Ancak bağlantı URL'leri sayfası Predis için birkaç istisnayı ayrıca listeler:
- Predis kimlik bilgilerini ve veritabanını adresten yalnızca
redisveredissşemalarında okur.tcp,tlsveunixşemalarındausername,passwordvedatabasesorgu parametrelerini kullanmanız gerekir. - Adresteki kullanıcı adı ve şifrede yüzde kodlamasını çözmez. Şifreniz
@veya:gibi özel karakterler içeriyorsa onu kodlanmış birpasswordsorgu parametresi olarak verin. - Veritabanı parametresinin adı
dbdeğildatabase'dir. - Bütün sorgu parametrelerini bağlantıya iletir; örneğin
timeout,read_write_timeout,persistentveprotocol. RESP3 için?protocol=3yazılabilir. - TLS seçenekleri
sslparametreleriyle verilebilir, örneğin?ssl[cafile]=<ca-dosyası>&ssl[verify_peer]=1.
Bu kurallara göre, şifresi p@ss:w+rd olan uygulama kullanıcısıyla 2 numaralı veritabanına bağlanan bir tcp URL'si şöyle yazılabilir. Kodlama, bağlantı URL'leri sayfasındaki örnekle aynıdır:
$r = new Predis\Client('tcp://10.0.0.5:6379?username=uygulama&password=p%40ss%3Aw%2Brd&database=2');Diğer dillerde de geçerli olan genel URL kuralları, yani küçük harfli şema, portun her zaman yazılması ve aynı ayarın hem URL'de hem kodda verilmemesi, Redis bağlantı URL ve connection pool yazısında.
Cluster, Sentinel ve TLS bağlantıları

Cluster'a bağlanmak için ilk parametrede bir veya daha fazla düğüm adresi verir, seçeneklerde 'cluster' => 'redis' ayarlarsınız:
$clusterNodes = [
'tcp://127.0.0.1:30001', // Node 1
'tcp://127.0.0.1:30002', // Node 2
'tcp://127.0.0.1:30003', // Node 3
];
$options = ['cluster' => 'redis'];
// Create a Predis client for the cluster
$rc = new PredisClient($clusterNodes, $options);
echo $rc->set('foo', 'bar'), PHP_EOL;
// >>> OK
echo $rc->get('foo'), PHP_EOL;
// >>> barSentinel için ilk parametre Sentinel adreslerinin listesidir; seçeneklerde 'replication' => 'sentinel' ve service alanına izlenen birincil sunucunun adı yazılır. Belgelerde birkaç önemli ayrıntı var. Sentinel'e bağlanma zaman aşımı varsayılan olarak 100 milisaniyedir; Sentinel'leriniz uzak sunuculardaysa her adrese tcp://<sunucu>:26379?timeout=0.5 gibi daha uzun bir süre ekleyin. Sentinel kimlik bilgileri her Sentinel adresine sorgu parametresi olarak, veri düğümlerininki ise parameters seçeneğinde verilir; Sentinel kullanıcı adı default olsa bile yazılmalıdır, aksi hâlde Predis parameters içindeki kullanıcı adını Sentinel'ler için de kullanır.
Sentinel kipinde Predis, ilk yazma komutuna kadar salt okunur komutları bir kopyaya gönderir; ilk yazmadan sonra her şeyi birincile yollar. Okumaları yeniden kopyaya göndermek için $r->getConnection()->switchToSlave() çağrılır. Replikasyon asenkron olduğu için kopya eski veri döndürebilir. Ayrıca Predis Sentinel'lerin yük devretme duyurularını dinlemez; yük devretmeden sonra Sentinel eski birincili kopyaya çevirip bağlantıyı kapatana kadar onu kullanmaya devam eder ve bu aralıkta kabul edilen yazmalar kaybolur.
TLS bağlantısında scheme değeri tls olur ve sertifika bilgileri options içindeki ssl dizisinde verilir: verify_peer, cafile, local_cert ve local_pk.
Predis ile pipeline
Predis'te pipeline iki biçimde kurulabilir. Birincisinde pipeline() bir nesne döndürür, komutları ona eklersiniz ve execute() çağırdığınızda hepsi tek gidiş dönüşte gönderilir; execute() tüm sonuçları sırasıyla içeren bir dizi döndürür. Belgelerdeki önceden getirme örneği binlerce kaydı bu yolla tek seferde yükler:
$pipe = $this->redis->pipeline();
foreach ($records as $record) {
$cacheKey = $this->cacheKey($record['id']);
$pipe->del([$cacheKey]);
$pipe->hset($cacheKey, ...self::flattenFields($record));
$pipe->expire($cacheKey, $this->ttlSeconds);
}
$pipe->execute();İkincisinde pipeline() metoduna bir geri çağırma fonksiyonu verirsiniz; Predis fonksiyon dönünce topluluğu kendiliğinden çalıştırır ve sonuç dizisini döndürür. Oturum deposu örneği oturumun son erişim zamanını günceller, süresini yeniler ve güncel hash'i aynı pipeline'da okur; üçüncü komutun sonucu dizinin 2 numaralı elemanıdır:
$result = $this->redis->pipeline(function ($pipe) use ($key, $sessionTtl): void {
$pipe->hset($key, 'last_accessed_at', $this->timestamp());
$pipe->expire($key, $sessionTtl);
$pipe->hgetall($key);
});
$refreshed = $result[2] ?? [];Belgeler bir ayrıntıyı vurgular: Predis'in pipeline'ı varsayılan olarak transaction değildir. Komutlar kuyruğa girip tek gidiş dönüşte gönderilir ama MULTI/EXEC bloğu içinde çalışmaz. Kayıtları atomik olarak uygulamanın gerekmediği toplu yüklemelerde bu doğru tercihtir ve yüklemeyi hızlı tutar. Önbellek zaten okunuyorsa ve okuyanların yarım yüklenmiş bir kayıt görmemesi gerekiyorsa, ya yazanları durdurmanız ya da işi bir transaction'a almanız gerekir. Örneklerdeki del([$cacheKey]) çağrısının anahtarları dizi olarak aldığına da dikkat edin. Pipelining'in sunucu tarafındaki mantığı Redis pipelining yazısında.
Pipeline mı transaction mı?
Belgelerdeki üç PHP örneği bu kararı farklı durumlarda farklı veriyor ve aradaki mantık öğretici. Oturum deposu, oturum hash'ini ve süresini callback'li bir pipeline ile yazar; burada amaç gidiş dönüş sayısını azaltmaktır. Özellik deposu, binlerce kullanıcının özelliklerini toplu yüklerken yine transaction'sız pipeline kullanır, çünkü her kullanıcının HSET ve EXPIRE çifti diğerlerinden bağımsızdır; belgeler çiftin ayrılamaz olması gereken nadir durumlar için transaction() ya da bir Lua betiği önerir. Önceden getirme önbelleği ise canlı eşitleme sırasında bir kaydı silip yeniden yazarken transaction kullanır, çünkü o anda önbellekten okuma yapılmaktadır ve okuyan biri eski alanlarla yeni alanların karışımını görmemelidir. Kısacası soru şudur: komutların arasına başka bir istemcinin komutu girerse bir sorun olur mu? Olmuyorsa pipeline yeterlidir ve daha hafiftir; oluyorsa transaction gerekir. Okuduğunuz değere göre karar verip yazıyorsanız transaction da yetmez, aşağıdaki iyimser kilide ihtiyacınız vardır.
transaction() ile MULTI ve EXEC
Transaction da benzer biçimde kurulur. transaction() metoduna bir geri çağırma verirseniz Predis içindeki komutları kendiliğinden MULTI ve EXEC ile sarar. Argümansız çağırırsanız bir transaction nesnesi döner; komutları ekleyip execute() çağırırsınız. Belgelerdeki eşitleme örneği bir kaydı önce siler, sonra yeniden yazar ve süresini yeniler; üç komut atomik çalıştığı için önbellekte hiçbir zaman eski ve yeni alanların karışımı görünmez:
$tx = $this->redis->transaction();
$tx->del([$cacheKey]);
$tx->hset($cacheKey, ...self::flattenFields($fields));
$tx->expire($cacheKey, $this->ttlSeconds);
$tx->execute();cas ve watch ile iyimser kilit
Bir değeri okuyup ona göre yazmak istediğinizde iyimser kilit kullanılır. Predis'te transaction() metoduna seçenek dizisi verirsiniz: cas kontrol et ve ayarla (check-and-set) davranışını açar, watch izlenecek anahtarı belirtir, retry ise izlenen anahtar EXEC'ten önce değişirse transaction'ın kaç kez kendiliğinden yeniden deneneceğini söyler. Geri çağırma içinde önce okuma yapılır, ardından $tx->multi() ile kuyruğa alma başlar. Transaction iptal edildiğinde Predis AbortedMultiExecException fırlatır; hata yönetimi sayfasındaki örnek bunu üç denemeyle ele alır:
use Predis\Transaction\AbortedMultiExecException;
for ($attempt = 0; $attempt < 3; $attempt++) {
try {
return $r->transaction(['cas' => true, 'watch' => $key], function ($tx) use ($key) {
$current = $tx->get($key);
$tx->multi();
$tx->set($key, strtoupper($current));
});
} catch (AbortedMultiExecException $e) {
if ($attempt === 2) {
throw $e;
}
}
}Transaction'ların sunucu tarafında nasıl çalıştığı, neden geri alma (rollback) olmadığı ve EXECABORT hatasının anlamı MULTI, EXEC ve WATCH yazısında.
Lua betiği çalıştırma
Önbellek örneği, aynı anda süresi dolan popüler bir anahtarı tek bir işçinin yeniden yüklemesi için kısa ömürlü bir kilidi Lua betiğiyle alır. Predis'te eval() metodunun imzasına dikkat edin: önce betik, sonra anahtar sayısı, ardından anahtarlar ve argümanlar gelir:
$acquired = (int) $this->redis->eval(
self::ACQUIRE_LOCK_SCRIPT, 1, $lockKey, $token, (string) $this->lockTtlMs
);Burada 1, kendisinden sonra gelen ilk değerin anahtar, geri kalanların argüman olduğunu belirtir. Betik tarafında bunlar KEYS[1] ve ARGV dizisi olarak okunur. Betiklerin yazımını Lua script, EVAL ve EVALSHA yazısında, kilit deseninin güvenlik tarafını Redis ile dağıtık kilit yazısında anlatıyoruz.
Predis istisna hiyerarşisi

Predis hataları istisnalarla bildirir ve hepsini PredisException altında toplar. CommunicationException bunun altındadır ve ConnectionException ile ProtocolException'ı içerir; ClientException, ServerException ve AbortedMultiExecException da diğer alt sınıflardır.
| İstisna | Ne zaman oluşur | Önerilen davranış |
|---|---|---|
| Predis\Connection\ConnectionException | Bağlanamadı veya bağlantı koptu | Bekleyerek yeniden dene veya yedeğe dön |
| Predis\CommunicationException | Okuma ya da yazma sırasında taşıma katmanı hatası | Bekleyerek yeniden dene ve yeniden bağlan |
| Predis\Response\ServerException | Redis WRONGTYPE gibi bir hata yanıtı döndürdü | Komutu, argümanları veya veri modelini düzelt |
| Predis\Transaction\AbortedMultiExecException | İzlenen transaction iptal edildi | Durumu yeniden oku ve transaction'ı tekrarla |
Belgeler üretim kodunda geçici taşıma hatalarıyla sunucu tarafı komut hatalarının ayırt edilmesini ister. Zarif gerileme deseninde yalnızca bağlantı hatasında veritabanına dönülür; yeniden deneme deseninde ise CommunicationException yakalanıp bekleme süresi her seferinde ikiye katlanır:
use Predis\Connection\ConnectionException;
use Predis\CommunicationException;
try {
$cachedValue = $r->get($key);
if ($cachedValue !== null) {
return $cachedValue;
}
} catch (ConnectionException $e) {
$logger->warning('Cache unavailable, using database');
}
$delayMs = 100;
for ($attempt = 0; $attempt < 3; $attempt++) {
try {
return $r->get($key);
} catch (CommunicationException $e) {
if ($attempt === 2) {
throw $e;
}
usleep($delayMs * 1000);
$delayMs *= 2;
}
}ServerException için ise hızlı başarısız olma deseni önerilir: yakalayıp yeniden fırlatın, çünkü bu genellikle kodunuzda ya da şemanızda bir hata olduğunu gösterir. Sunucu hata mesajlarının tek tek anlamı Redis hata mesajları yazısında.
Uygulama yapısı açısından da belgelerden bir not: özellik deposu örneği PHP'nin yerleşik geliştirme sunucusunun (php -S) tek iş parçacıklı olduğunu ve üretime uygun olmadığını belirtir. Gerçek bir dağıtımda PHP-FPM, nginx veya Apache arkasında çalışır ve rota işleyicileri Predis\Client bağlantılarını işçi başına yöneten bir PHP çatısına taşınır.
Sık yapılan hatalar
- Predis 3'te hset'e ilişkisel dizi vermek. Alan ve değerleri düz listeye çevirip
...$flatile geçin. - Özel karakterli şifreyi adreste kodlayıp Predis'in çözeceğini beklemek. Predis adresteki kodlamayı çözmez;
passwordsorgu parametresini kullanın. - Veritabanı için db parametresi yazmak. Predis'te adı
database'dir. - pipeline()'ın atomik olduğunu varsaymak. Predis pipeline'ı varsayılan olarak transaction değildir; atomiklik gerekiyorsa
transaction()kullanın. - Uzak Sentinel'lerde varsayılan 100 ms zaman aşımıyla kalmak. Adreslere
?timeout=0.5gibi daha uzun bir süre ekleyin.
Sık Sorulan Sorular
Predis resmî bir Redis kütüphanesi mi?
Hayır, üçüncü taraf bir kütüphanedir. Redis ekibi katkı verir ve sorunlarda yardımcı olur, belgelerde de iyi kapsanır; ancak doğrudan Redis tarafından geliştirilmez ve desteklenmez.
Pipeline sonuçlarına nasıl erişirim?
execute() ya da geri çağırmalı pipeline() sonuçları komut sırasına göre bir dizi olarak döndürür. Üçüncü komutun sonucu $result[2]'dir.
WATCH ile transaction iptal olursa ne olur?
cas ve watch seçenekleriyle kurulan transaction iptal edildiğinde Predis AbortedMultiExecException fırlatır. Durumu yeniden okuyup transaction'ı tekrarlayın ya da retry seçeneğiyle bunu Predis'e bırakın.
RESP3 kullanabilir miyim?
Bağlantı URL'leri sayfasına göre Predis URL'de ?protocol=3 parametresini kabul eder ve bunu bağlantıya iletir.
Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.
İlgili içerikler: Redis öğrenme rehberi · Redis bağlantı URL ve connection pool · MULTI, EXEC ve WATCH · Redis pipelining · Lua script ve EVAL · PHP öğrenme rehberi