PHP 8.1 ile gelen Fiber sınıfı, çalışmasını istediğiniz noktada duraklatıp daha sonra kaldığı yerden sürdürebileceğiniz bir fonksiyonu temsil eder. Kılavuz fiber'ları "tam yığınlı, kesilebilir fonksiyonlar" olarak tanımlar: her fiber'ın kendi çağrı yığını vardır, bu yüzden Fiber::suspend() çağrısı fiber'ın içinden çağrılan, hatta çok derinde kalmış bir fonksiyondan bile yapılabilir. Fiber'ı start() ile başlatırsınız, fiber kendini Fiber::suspend() ile askıya alır, siz de resume() ile bir değer vererek ya da throw() ile bir istisna fırlatarak onu devam ettirirsiniz.
Bu yazıda fiber'lar arasında değerlerin nasıl taşındığını, durum sorgulama metotlarını, üreteçlerden (generator) temel farkını, birkaç fiber'ı sırayla çalıştıran basit bir zamanlayıcıyı, istisnaların fiber sınırını nasıl geçtiğini ve FiberError fırlatan hatalı kullanımları PHP 8.5.12 ile çalıştırılmış örneklerle görüyorsunuz. Fiber'lar üreteçlerle birlikte düşünüldüğünde daha kolay anlaşılır; önce PHP generator ve yield yazısını okumanız faydalı olabilir.
Temel kullanım: start, suspend, resume

Bir fiber, kurucuya verilen bir callable ile oluşturulur. Oluşturmak hiçbir kodu çalıştırmaz; callable ancak start() çağrıldığında, start() fonksiyonuna verilen argümanlarla çalışmaya başlar.
<?php
$fiber = new Fiber(function (string $ad): string {
echo "Fiber başladı: $ad\n";
$gelen = Fiber::suspend('ilk durak');
echo "Fiber devam ediyor, gelen: $gelen\n";
$gelen2 = Fiber::suspend('ikinci durak');
echo "Fiber son kez devam: $gelen2\n";
return 'bitti';
});
$deger = $fiber->start('işçi-1');
echo "Ana kod aldı: $deger\n";
$deger = $fiber->resume('merhaba');
echo "Ana kod aldı: $deger\n";
$deger = $fiber->resume('güle güle');
var_dump($deger);
echo 'Dönüş: ', $fiber->getReturn(), "\n";Fiber başladı: işçi-1
Ana kod aldı: ilk durak
Fiber devam ediyor, gelen: merhaba
Ana kod aldı: ikinci durak
Fiber son kez devam: güle güle
NULL
Dönüş: bittiDeğerlerin akışı başlangıçta kafa karıştırabilir, ama kural simetriktir. Fiber içinde Fiber::suspend($x) çağrıldığında $x, fiber'ı o an çalıştıran start() veya resume() çağrısının dönüş değeri olur. Ana kod resume($y) çağırdığında da $y, fiber içinde bekleyen Fiber::suspend() çağrısının dönüş değeri olur. Fiber callable'ı return ile bittiğinde son resume() çağrısı null döndürür; asıl dönüş değeri getReturn() ile okunur.
| Çağrı | Nerede | Ne döndürür |
|---|---|---|
| start(...$args) | Ana kod | İlk Fiber::suspend() değerini ya da fiber bittiyse null |
| Fiber::suspend($deger) | Fiber içi | Fiber'ı devam ettiren resume() argümanını |
| resume($deger) | Ana kod | Bir sonraki Fiber::suspend() değerini ya da fiber bittiyse null |
| throw($istisna) | Ana kod | resume() gibi; istisna suspend() noktasından fırlar |
| getReturn() | Ana kod | Fiber callable'ının return değerini (yalnızca bittikten sonra) |
| Fiber::getCurrent() | Her yer | Çalışan Fiber nesnesini ya da fiber dışındaysa null |
Burada önemli bir gözlem var: start() ve resume() çağrıları, fiber askıya alınana ya da bitene kadar geri dönmez. Yani aynı anda yalnızca bir kod parçası çalışır; fiber'lar iş parçacığı (thread) değildir ve kodu paralel çalıştırmaz. Kontrolün ne zaman el değiştireceğine tamamen siz karar verirsiniz. Bu işbirlikçi (cooperative) model, kilitler ve yarış durumlarıyla uğraşmadan birden çok işi aynı süreç içinde dönüşümlü ilerletmenizi sağlar.
Fiber'ın durumunu sorgulamak
Bir fiber yaşamı boyunca birkaç durumdan geçer. Dört metot bu durumu sorgular: isStarted(), isSuspended(), isRunning() ve isTerminated(). Aşağıdaki örnek her aşamada dördünü birden yazdırıyor:
<?php
function durum(Fiber $f): string
{
return sprintf('başladı=%s askıda=%s çalışıyor=%s bitti=%s',
var_export($f->isStarted(), true), var_export($f->isSuspended(), true),
var_export($f->isRunning(), true), var_export($f->isTerminated(), true));
}
$f = new Fiber(function () use (&$f): void {
echo 'içeride: ', durum($f), "\n";
Fiber::suspend();
});
echo 'önce: ', durum($f), "\n";
$f->start();
echo 'askıda: ', durum($f), "\n";
$f->resume();
echo 'sonra: ', durum($f), "\n";önce: başladı=false askıda=false çalışıyor=false bitti=false
içeride: başladı=true askıda=false çalışıyor=true bitti=false
askıda: başladı=true askıda=true çalışıyor=false bitti=false
sonra: başladı=true askıda=false çalışıyor=false bitti=trueKılavuzdaki tanımlar çıktıyla birebir örtüşüyor. isStarted() fiber başlatıldıktan sonra hep true kalır. isRunning() yalnızca henüz geri dönmemiş bir start(), resume() veya throw() çağrısı varken true olur; bu yüzden onu yalnızca fiber'ın içinden true olarak görebilirsiniz. isTerminated() ise fiber normal şekilde döndüğünde ya da bir istisna fırlatarak bittiğinde true olur. Bir zamanlayıcı yazarken en çok kullanacağınız ikili isSuspended() ve isTerminated() olacaktır.
Derindeki bir fonksiyondan askıya almak
Fiber'ların asıl gücü burada ortaya çıkar. Kılavuza göre Fiber::suspend() çağrı yığınının herhangi bir yerinden yapılabilir; askıya alma noktasını tanımlayan fonksiyonun dönüş tipini değiştirmesi gerekmez. Fiber'lar, array_map() gibi PHP'nin kendi fonksiyonlarına verilen callable'ların ya da bir Iterator nesnesi üzerinde foreach tarafından çağrılan metotların içinden bile askıya alınabilir.
<?php
function logla(string $mesaj): void
{
// Derindeki bir fonksiyon, imzası değişmeden fiber'ı askıya alır.
echo "log: $mesaj\n";
Fiber::suspend($mesaj);
}
function isle(array $kayitlar): int
{
$adet = 0;
array_walk($kayitlar, function (string $k) use (&$adet) {
logla("$k işlendi");
$adet++;
});
return $adet;
}
$f = new Fiber(fn() => isle(['A', 'B', 'C']));
$durak = $f->start();
while (!$f->isTerminated()) {
echo " ana kod araya girdi ($durak)\n";
$durak = $f->resume();
}
echo 'Toplam: ', $f->getReturn(), "\n";log: A işlendi
ana kod araya girdi (A işlendi)
log: B işlendi
ana kod araya girdi (B işlendi)
log: C işlendi
ana kod araya girdi (C işlendi)
Toplam: 3logla() sıradan bir void fonksiyon. array_walk() tarafından çağrılan bir closure'ın içinden çağrılıyor ve her seferinde tüm yığını, yani isle(), array_walk() ve closure çerçevelerini birlikte donduruyor. Ana kod her durakta araya girip bir şey yapabiliyor, sonra resume() ile fiber'ı kaldığı yerden sürdürüyor. Aynı şeyi bir üreteçle yapmak mümkün olmazdı: logla() içine yield yazsaydınız o fonksiyon bir üretece dönüşür ve çağrıldığında hiç çalışmazdı.
Generator ile Fiber arasındaki fark

İki yapı da bir fonksiyonu duraklatıp sürdürmeye yarar, ama kılavuzun vurguladığı temel fark yığındır: üreteçler yığınsızdır (stackless), fiber'ların ise kendi çağrı yığını vardır. Aşağıdaki örnek bu farkın pratikte ne anlama geldiğini gösteriyor:
<?php
function uretec(): Generator
{
yield 1;
yardimci(); // yield içermeyen fonksiyondan askıya alma yapılamaz
yield 2;
}
function yardimci(): void
{
echo "yardımcı çalıştı, ama burada yield kullanılamaz\n";
}
foreach (uretec() as $v) {
echo "üreteç: $v\n";
}
$f = new Fiber(function (): void {
echo "fiber: 1\n";
fiberYardimci();
echo "fiber: 2\n";
});
function fiberYardimci(): void
{
Fiber::suspend(); // derindeki fonksiyon da fiber'ı durdurabilir
}
$f->start();
echo "ana kod: fiber yardımcıda durdu\n";
$f->resume();üreteç: 1
yardımcı çalıştı, ama burada yield kullanılamaz
üreteç: 2
fiber: 1
ana kod: fiber yardımcıda durdu
fiber: 2Üreteçte yardimci() fonksiyonu duraklatma yapamadı; yalnızca çalışıp bitti. Duraklatma ancak üretecin kendi gövdesindeki yield noktalarında olabilir. Fiber örneğinde ise duraklatma, fiber'ın çağırdığı ayrı bir fonksiyonun içinde gerçekleşti ve ana kod araya girebildi.
| Özellik | Generator | Fiber |
|---|---|---|
| Duraklatma noktası | Yalnızca kendi gövdesindeki yield | Çağrı yığınının herhangi bir yeri |
| Fonksiyon imzasına etkisi | Dönüş değeri Generator olur | İmza değişmez |
| Dışarıya değer verme | yield $deger | Fiber::suspend($deger) |
| İçeriye değer gönderme | send($deger) | resume($deger) |
| İçeriye istisna fırlatma | throw($istisna) | throw($istisna) |
| foreach ile dolaşma | Evet (Iterator uygular) | Hayır |
| Örnek kullanım | Tembel veri akışı, yineleyiciler | İşleri dönüşümlü ilerleten zamanlayıcılar |
| Sürüm | PHP 5.5 | PHP 8.1 |
Basit bir kural olarak: değer üreten ve foreach ile tüketilen bir veri kaynağı istiyorsanız üreteç daha uygun ve daha okunaklıdır. Var olan, imzası değiştirilemeyen kodun ortasından kontrolü geri almanız gerekiyorsa fiber gerekir.
Basit bir zamanlayıcı: işleri sırayla ilerletmek
Fiber'larla en sık kurulan yapı, birkaç işi dönüşümlü olarak ilerleten bir zamanlayıcıdır (scheduler). Aşağıda üç iş farklı sayıda adımdan oluşuyor ve her adımın sonunda kontrolü geri veriyor. Ana döngü askıdaki her fiber'ı sırayla devam ettiriyor, biteni listeden çıkarıyor.
<?php
$isler = [];
foreach (['A' => 3, 'B' => 2, 'C' => 1] as $ad => $adim) {
$isler[$ad] = new Fiber(function () use ($ad, $adim): string {
for ($i = 1; $i <= $adim; $i++) {
echo "$ad: adım $i/$adim\n";
Fiber::suspend();
}
return "$ad tamam";
});
}
foreach ($isler as $f) {
$f->start();
}
while ($isler) {
foreach ($isler as $ad => $f) {
if ($f->isTerminated()) {
echo $f->getReturn(), "\n";
unset($isler[$ad]);
} else {
$f->resume();
}
}
}A: adım 1/3
B: adım 1/2
C: adım 1/1
A: adım 2/3
B: adım 2/2
A: adım 3/3
C tamam
B tamam
A tamamÇıktıdaki sıra, işlerin aynı anda değil dönüşümlü ilerlediğini gösteriyor: önce her iş birinci adımını atıyor, sonra kalanlar ikinci adımlarını atıyor. En kısa iş olan C, ikinci turda bittiği için tamamlanma mesajı ilk onun için yazılıyor. Bu fikri dışarıdan veri bekleyen işlere uygulamak isterseniz, verinin hazır olup olmadığını denetleyen ve uygun anda resume() çağıran bir döngüyü sizin (ya da kullandığınız kütüphanenin) yazması gerekir. Fiber kendi başına hiçbir işlemi beklemesiz hale getirmez; örneğin fiber içinde çağrılan sleep() tüm süreci bekletir.
Uygulamalı örnek: veriyi dışarıdan parça parça beslemek
Fiber'ların zamanlayıcı dışındaki bir kullanımı da kontrolün yönünü tersine çevirmektir. Normalde bir ayrıştırıcı veriyi kendisi okur. Aşağıdaki örnekte ayrıştırıcı (topla()) her yeni satıra ihtiyaç duyduğunda sonrakiSatir() fonksiyonunu çağırıyor; bu fonksiyon fiber'ı askıya alıyor ve satırın dışarıdan, resume() ile gelmesini bekliyor. Ayrıştırıcının kodu, veriyi sanki sıradan bir fonksiyondan okuyormuş gibi düz bir döngü olarak kalıyor.
<?php
function sonrakiSatir(): string
{
return Fiber::suspend('veri bekleniyor');
}
function topla(): int
{
$toplam = 0;
while (($satir = sonrakiSatir()) !== '') {
$toplam += (int) $satir;
echo " ayrıştırıcı: $satir eklendi, toplam $toplam\n";
}
return $toplam;
}
$f = new Fiber(topla(...));
echo 'Fiber diyor ki: ', $f->start(), "\n";
foreach (['10', '20', '12', ''] as $parca) {
echo "Ana kod gönderiyor: '$parca'\n";
$f->resume($parca);
}
echo 'Sonuç: ', $f->getReturn(), "\n";Fiber diyor ki: veri bekleniyor
Ana kod gönderiyor: '10'
ayrıştırıcı: 10 eklendi, toplam 10
Ana kod gönderiyor: '20'
ayrıştırıcı: 20 eklendi, toplam 30
Ana kod gönderiyor: '12'
ayrıştırıcı: 12 eklendi, toplam 42
Ana kod gönderiyor: ''
Sonuç: 42Akışı izleyelim. start() fiber'ı başlattı; ayrıştırıcı ilk satırı istedi ve fiber 'veri bekleniyor' değeriyle askıya alındı, bu değer de start() çağrısının sonucu oldu. Ana kod her resume() çağrısında bir parça gönderdi; bu parça fiber içindeki Fiber::suspend() çağrısının dönüş değeri, dolayısıyla sonrakiSatir() fonksiyonunun sonucu oldu. Boş metin gönderildiğinde döngü bitti, fiber 42 değerini döndürerek sonlandı ve son resume() çağrısı null döndürdü. Fiber'a verilen callable burada topla(...) biçimindeki first-class callable sözdizimiyle oluşturuldu; bu sözdizimi PHP 8.1 ile geldi.
Aynı ayrıştırıcıyı bir üreteçle yazmak isteseydiniz sonrakiSatir() çağrısının yerine doğrudan yield yazmanız gerekirdi; yardımcı fonksiyonu korumanın yolu olmazdı ve topla() artık bir tamsayı değil bir Generator döndürürdü. Fiber'ın sağladığı şey tam olarak budur: ayrıştırıcının imzası ve iç yapısı değişmeden veri kaynağı dışarıya taşınır.
Fiber ve istisnalar
İstisnalar fiber sınırını iki yönde de geçebilir. Ana kod throw() ile fiber'ı bir istisnayla devam ettirebilir; istisna fiber içindeki Fiber::suspend() çağrısından fırlar ve fiber onu yakalayabilir. Ters yönde, fiber içinde yakalanmayan bir istisna fiber'ı sonlandırır ve onu çalıştıran start(), resume() veya throw() çağrısından dışarı fırlar.
<?php
$f = new Fiber(function (): void {
try {
Fiber::suspend('bekliyorum');
} catch (RuntimeException $e) {
echo 'Fiber içinde yakalandı: ', $e->getMessage(), "\n";
}
echo "Fiber temizlik yapıp bitiyor\n";
});
$f->start();
$f->throw(new RuntimeException('zaman aşımı'));
var_dump($f->isTerminated());
$g = new Fiber(function (): void {
Fiber::suspend();
throw new LogicException('fiber içinde patladı');
});
$g->start();
try {
$g->resume();
} catch (LogicException $e) {
echo 'Ana kodda yakalandı: ', $e->getMessage(), "\n";
}Fiber içinde yakalandı: zaman aşımı
Fiber temizlik yapıp bitiyor
bool(true)
Ana kodda yakalandı: fiber içinde patladıBirinci fiber zaman aşımı istisnasını kendi içinde yakaladı, temizliğini yaptı ve normal şekilde bitti. İkinci fiber istisnayı yakalamadı; istisna ana koddaki resume() satırından fırladı ve oradaki try/catch tarafından yakalandı. Bu durumda fiber sonlanmış sayılır ve bir dönüş değeri yoktur; getReturn() çağrısı FiberError fırlatır. İstisnaların genel kuralları için PHP try/catch ve exception yazısına bakabilirsiniz.
FiberError: geçersiz işlemler
FiberError, Error sınıfından türeyen final bir sınıftır ve fiber üzerinde geçersiz bir işlem yapıldığında fırlatılır. Kurucusu doğrudan örnek oluşturmayı engellemek için vardır; bu sınıfı siz fırlatmazsınız, PHP fırlatır. En sık karşılaşılan durumlar aşağıdaki örnekte:
<?php
$f = new Fiber(function (): int {
Fiber::suspend();
return 7;
});
$denemeler = [
'start öncesi getReturn' => fn() => $f->getReturn(),
'start öncesi resume' => fn() => $f->resume(),
'fiber dışında suspend' => fn() => Fiber::suspend(),
];
foreach ($denemeler as $ad => $is) {
try {
$is();
} catch (FiberError $e) {
echo "$ad: ", $e->getMessage(), "\n";
}
}
$f->start();
try {
$f->start();
} catch (FiberError $e) {
echo 'ikinci start: ', $e->getMessage(), "\n";
}
$f->resume();
echo 'dönüş: ', $f->getReturn(), "\n";
var_dump(Fiber::getCurrent());start öncesi getReturn: Cannot get fiber return value: The fiber has not been started
start öncesi resume: Cannot resume a fiber that is not suspended
fiber dışında suspend: Cannot suspend outside of a fiber
ikinci start: Cannot start a fiber that has already been started
dönüş: 7
NULLMesajlar kendi kendini açıklıyor: başlamamış bir fiber'ın dönüş değeri okunamaz, askıda olmayan bir fiber devam ettirilemez, fiber dışında Fiber::suspend() çağrılamaz ve bir fiber iki kez başlatılamaz. Son satır da Fiber::getCurrent() fonksiyonunun ana kodda null döndürdüğünü gösteriyor; bir fonksiyonun bir fiber içinde çalışıp çalışmadığını bu yolla anlayabilirsiniz. FiberError bir Error olduğu için catch (Exception $e) bloğu onu yakalamaz.
Fiber::getCurrent() özellikle hem fiber içinde hem dışında çağrılabilecek yardımcı fonksiyonlarda işe yarar. Örneğin yukarıdaki logla() gibi bir fonksiyon, Fiber::getCurrent() !== null kontrolüyle yalnızca bir fiber içinde çalışıyorsa askıya alma yapabilir, ana koddan çağrıldığında ise FiberError fırlatmak yerine işini doğrudan bitirebilir. Böylece aynı fonksiyon iki bağlamda da güvenle kullanılır. Bu kontrolü atlamak, fiber dışında Fiber::suspend() çağrısının neden olduğu "Cannot suspend outside of a fiber" hatasının en yaygın kaynağıdır.
Sınırlar ve sürüm notları
Kılavuz ve sürüm geçiş notları iki önemli ayrıntı veriyor. Birincisi, PHP 8.4'ten önce bir nesnenin yıkıcı metodu (__destruct()) çalışırken fiber değiştirmek yasaktı; 8.4 ile bu kısıt kaldırıldı. 8.4 notlarına göre çöp toplayıcının (garbage collector) çağırdığı yıkıcılar artık ayrı bir fiber içinde çalıştırılabiliyor. İkincisi, Fiber sınıfı final olduğu için genişletilemez; davranış eklemek istiyorsanız fiber'ı kendi sınıfınızın içinde bir özellik olarak tutun.
Kılavuzun belirttiği gibi askıya alma noktası içeren bir fonksiyonun imzası değişmez. Bunun bir yan etkisi var: kullandığınız bir kütüphane arka planda fiber'larla çalışıyorsa bunu fonksiyon imzalarından anlayamazsınız. Mekanizmayı bilmek, bir istisnanın neden beklenmedik bir resume() satırından fırladığını ya da bir işin neden başka bir işin ortasında ilerlediğini anlamanıza yardım eder. Fiber'a verilen callable genellikle bir closure ya da arrow function olur; bunların use ile dış değişken yakalama kuralları için PHP anonim fonksiyon ve arrow function yazısına bakabilirsiniz.
Sık yapılan hatalar

- Fiber'ın paralel çalıştığını sanmak. Aynı anda yalnızca bir fiber ya da ana kod çalışır;
resume()fiber durana kadar geri dönmez. - Bitmemiş fiber'da getReturn() çağırmak. Önce
isTerminated()ile kontrol edin; fiber istisnayla bittiyse dönüş değeri yoktur. - Askıda olmayan fiber'ı devam ettirmek. Bir zamanlayıcıda
resume()öncesindeisSuspended()kontrolü yapın. - FiberError'ı Exception ile yakalamaya çalışmak.
FiberErrorbirErroralt sınıfıdır; gerekiyorsaThrowableya daFiberErroryazın. - Fiber'dan engelleyen bir çağrının beklenmesiz olmasını beklemek.
sleep()veya senkron bir dosya okuması fiber içinde de süreci bekletir. - Askıdaki fiber'ları yarım bırakmak. Hiç devam ettirilmeyen bir fiber'ın askıya alma noktasından sonraki kodu çalışmaz. PHP 8.5.12 ile yaptığımız denemede askıdaki fiber nesnesi yok edilirken
finallybloğu çalıştı, ama kalan iş mantığı yarım kaldı; zamanlayıcınızın her fiber'ı sonuna kadar götürdüğünden emin olun.
Sık Sorulan Sorular
Fiber hangi PHP sürümünde geldi?
Fiber desteği PHP 8.1 ile eklendi. Destekleyen bir sürümde ek bir eklenti kurmanız gerekmez; Fiber sınıfı çekirdekte bulunur.
Fiber ile çok iş parçacıklı (multithread) programlama aynı şey mi?
Hayır. Fiber'lar aynı süreç ve aynı iş parçacığı içinde çalışır; kontrolü bilinçli olarak Fiber::suspend() ile devredersiniz. Bu yüzden iki fiber aynı anda aynı değişkeni değiştiremez ve kilit mekanizmasına ihtiyaç duyulmaz, ama işlemci çekirdeklerinden aynı anda yararlanılmaz.
Fiber içinde Fiber oluşturabilir miyim?
Evet. Bir fiber içinden başka bir fiber başlatıp devam ettirebilirsiniz. Fiber::suspend() her zaman o anda çalışan fiber'ı askıya alır ve kontrolü onu en son başlatan ya da devam ettiren çağrıya geri verir.
Generator mı kullanmalıyım, Fiber mı?
Bir veri akışını foreach ile tüketmek, büyük veriyi parça parça işlemek gibi ihtiyaçlarda üreteç daha basit ve yeterlidir. Var olan fonksiyonların imzasını değiştirmeden derinden duraklatma gerekiyorsa fiber kullanın. Karşılaştırmanın ayrıntısı yukarıdaki tabloda; üreteçler için generator yazısına bakabilirsiniz.
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: PHP öğrenme rehberi · PHP generator ve yield · Anonim fonksiyon ve arrow function · PHP try/catch · TypeError ve diğer Error türleri