PHP'de dört döngü yapısı vardır: for, foreach, while ve do-while. Kaç kez döneceğini baştan bildiğiniz sayaçlı işler için for, bir dizinin ya da nesnenin elemanlarını tek tek gezmek için foreach, bir koşul doğru kaldığı sürece tekrar etmek için while, gövdenin en az bir kez çalışması gereken durumlar için de do-while kullanırsınız. Döngünün akışını break (döngüden çık) ve continue (bu turu atla) ile yönetirsiniz; ikisi de iç içe döngülerde kaç seviye etkileneceğini belirten bir sayı alabilir.

Bu yazıda her döngü tipini kısa, çalıştırılabilir örneklerle anlatıyoruz: for ifadelerinin hangi sırayla çalıştığını, foreach ile anahtar ve değer almayı, iç içe dizileri doğrudan değişkenlere açmayı, referansla döngü kurmanın tuzağını ve break 2 / continue 2 kullanımını göreceksiniz. Örneklerin tamamı PHP 8.5.12 komut satırında çalıştırıldı; yorumlardaki çıktılar gerçek çıktılardır. Serinin geri kalanı için PHP öğrenme rehberi sayfasına göz atabilirsiniz.

for döngüsü: üç ifadenin çalışma sırası

for döngüsü: üç ifadenin çalışma sırası

for, PHP'deki en ayrıntılı döngüdür ve C dilindeki karşılığı gibi davranır. Parantez içinde noktalı virgülle ayrılmış üç ifade bulunur. Birincisi döngü başlamadan önce yalnızca bir kez çalışır; genellikle sayaç burada başlatılır. İkincisi her turun başında değerlendirilir: sonuç true ise gövde çalışır, false ise döngü biter. Üçüncüsü her turun sonunda çalışır ve çoğunlukla sayacı artırır.

Kılavuzun belirttiği daha az bilinen bir ayrıntı şudur: Bu üç bölümün her biri boş bırakılabilir ya da virgülle ayrılmış birden fazla ifade içerebilir. Koşul bölümünde birden çok ifade varsa hepsi çalışır ama sonucu yalnızca sonuncusu belirler. Koşul bölümü tamamen boşsa PHP bunu true kabul eder ve döngü sonsuza kadar sürer; böyle bir döngüden içerideki bir break ile çıkılır.

php
<?php
for ($i = 1; $i <= 5; $i++) {
    echo $i, ' ';
}
echo PHP_EOL;
// çıktı: 1 2 3 4 5

for ($i = 0, $j = 10; $i < $j; $i += 3, $j -= 3) {
    echo "$i/$j ";
}
echo PHP_EOL;
// çıktı: 0/10 3/7

$sehirler = ['Ankara', 'İzmir', 'Bursa'];
for ($i = 0, $n = count($sehirler); $i < $n; $i++) {
    echo $i, ': ', $sehirler[$i], PHP_EOL;
}
/* çıktı:
0: Ankara
1: İzmir
2: Bursa
*/

İkinci döngüde iki sayaç birlikte yönetiliyor: $i artarken $j azalıyor ve ikisi karşılaştığında döngü duruyor. Üçüncü döngüde ise count() sonucu ilk bölümde $n değişkenine bir kez yazılıyor. Kılavuz, koşul ve artırma bölümlerinin her turda yeniden çalıştığını hatırlatır ve koşula her seferinde aynı sonucu döndüren bir fonksiyon çağrısı koymak yerine değeri önceden bir değişkende tutmayı önerir. Aynı notta dizinin boyutunun dizinin kendisiyle birlikte saklandığı, bu yüzden count() çağrısının elemanları tek tek saymadığı da belirtilir. Asıl dikkat edilecek nokta şudur: Sayıyı önceden sakladıysanız ve döngü içinde diziye eleman ekleyip çıkarıyorsanız, sakladığınız sayı artık gerçek boyutu yansıtmaz.

foreach: dizileri ve nesneleri gezmek

foreach, diziler ve nesneler üzerinde dolaşmak için tasarlanmıştır. İki yazımı vardır: foreach ($dizi as $deger) her turda sıradaki elemanın değerini verir, foreach ($dizi as $anahtar => $deger) ise değerin yanında anahtarı da verir. Bir nesneyi gezdiğinizde, o anki kapsamdan görülebilen özellikler sırayla gelir. Dizi ya da nesne dışında bir değer verirseniz, örneğin null veya bir sayı, PHP uyarı üretir.

php
<?php
$fiyatlar = ['elma' => 12.5, 'armut' => 18, 'kiraz' => 40];

foreach ($fiyatlar as $meyve => $fiyat) {
    echo "$meyve: $fiyat TL", PHP_EOL;
}
/* çıktı:
elma: 12.5 TL
armut: 18 TL
kiraz: 40 TL
*/

$siparisler = [
    ['Kalem', 3],
    ['Defter', 2],
];
foreach ($siparisler as [$urun, $adet]) {
    echo "$urun x $adet", PHP_EOL;
}
/* çıktı:
Kalem x 3
Defter x 2
*/

İkinci döngüde as [$urun, $adet] yazımı kullanılıyor. Bu, iç içe dizilerdeki her alt diziyi doğrudan değişkenlere açar; eski list($urun, $adet) yazımı da aynı işi görür. Kılavuza göre alt dizide değişkenden fazla eleman varsa fazlası yok sayılır, araya virgül koyarak ([, , $ucuncu]) eleman atlayabilirsiniz. Değişken sayısı eleman sayısından fazlaysa eksik kalan konum için tanımsız anahtar uyarısı alırsınız. Dizi açma (destructuring) kurallarının tamamı PHP dizi temelleri yazısında anlatılıyor.

foreach, current() ve key() gibi fonksiyonların kullandığı iç dizi işaretçisini değiştirmez. Ayrıca kılavuz, foreach ifadesinin önüne @ koyarak hata mesajını bastırmanın desteklenmediğini belirtir. Değerin null olabileceği durumlarda en temiz çözüm, döngüye boş dizi vermektir:

php
<?php
$liste = null;
foreach ($liste as $oge) {
    echo $oge;
}
echo "devam", PHP_EOL;
// Warning: foreach() argument must be of type array|object, null given in /yol/ornek.php on line 3
// devam

foreach ($liste ?? [] as $oge) {
    echo $oge;
}
echo "uyarı yok", PHP_EOL;
// çıktı: uyarı yok

?? operatörü sol taraf null ise sağdaki boş diziyi kullanır. Böylece döngü hiç çalışmaz ve uyarı oluşmaz. Tanımsız değişken ve dizi anahtarı uyarılarının genel çözümleri için Undefined variable ve array key uyarıları yazısına bakabilirsiniz.

foreach ve referans: son elemanı bozan tuzak

Döngü içinde dizinin elemanlarını yerinde değiştirmek istiyorsanız değer değişkeninin önüne & koyarsınız. Bu durumda $s bir kopya değil, dizideki elemanın kendisine bağlı bir referanstır. Kılavuz burada önemli bir uyarı yapar: Döngü bittikten sonra da değişken son elemana bağlı kalır. Aynı adla ikinci bir döngü kurarsanız, her turda son elemanın üzerine yazmış olursunuz.

php
<?php
$sayilar = [1, 2, 3, 4];
foreach ($sayilar as &$s) {
    $s = $s * 10;
}
unset($s); // referansı kopar
print_r($sayilar);
/* çıktı:
Array
(
    [0] => 10
    [1] => 20
    [2] => 30
    [3] => 40
)
*/

$liste = ['a', 'b', 'c'];
foreach ($liste as &$harf) {
}
foreach ($liste as $harf) { // unset yapılmadı
}
print_r($liste);
/* çıktı:
Array
(
    [0] => a
    [1] => b
    [2] => b
)
*/

İkinci örnekte ilk döngüden sonra $harf hâlâ $liste[2] ile aynı yeri gösteriyor. İkinci döngü sırasıyla 'a', 'b' ve son turda kendi değerini (artık 'b' olan) bu yere yazıyor; sonuçta son eleman 'c' yerine 'b' oluyor. Çözüm basittir: Referanslı döngüden hemen sonra unset($degisken) çağırın. Referansların nasıl çalıştığını ve bu davranışın nedenini PHP referanslar yazısında ayrıntılı ele alıyoruz.

while ve do-while: koşula bağlı tekrar

while ve do-while: koşula bağlı tekrar

while, PHP'nin en sade döngüsüdür: Koşul doğru olduğu sürece gövdeyi tekrar çalıştırır. Koşul her turun başında kontrol edilir. Bu yüzden gövde içinde koşul yanlışa dönse bile o tur sonuna kadar tamamlanır; döngü ancak bir sonraki kontrol anında biter. Koşul en baştan yanlışsa gövde bir kez bile çalışmaz.

do-while ise koşulu turun sonunda kontrol eder. Bu nedenle gövdenin en az bir kez çalışması garantidir. Kullanıcıdan geçerli bir değer gelene kadar tekrar sormak, bir işlemi en az bir kez deneyip sonra yeniden denemeye karar vermek gibi senaryolarda işinize yarar. do-while için tek bir yazım vardır ve sonunda noktalı virgül gerekir.

php
<?php
$kuyruk = ['ilk', 'ikinci', 'üçüncü'];
while ($kuyruk !== []) {
    $is = array_shift($kuyruk);
    echo "İşleniyor: $is", PHP_EOL;
}
/* çıktı:
İşleniyor: ilk
İşleniyor: ikinci
İşleniyor: üçüncü
*/

$deneme = 0;
do {
    $deneme++;
    echo "Deneme $deneme", PHP_EOL;
} while ($deneme < 0);
// çıktı: Deneme 1

İlk örnekte array_shift() her turda kuyruğun başından bir eleman çıkarıyor; kuyruk boşaldığında koşul yanlış oluyor ve döngü bitiyor. İkinci örnekte koşul daha ilk kontrolde yanlış olmasına rağmen gövde bir kez çalıştı. Aynı koşulu while ile yazsaydınız hiçbir çıktı görmezdiniz.

Kaç kez döneceği belli olmayan işlerde while (true) ile sonsuz bir döngü kurup çıkışı içeride break ile yapmak yaygın bir kalıptır. Burada dikkat etmeniz gereken tek şey, çıkış koşulunun gerçekten bir gün sağlanacağından emin olmaktır. Çıkış koşulu hiç gerçekleşmezse betik takılır; komut satırında Ctrl+C ile durdurmanız gerekir.

break ve continue: akışı kontrol etmek

break, içinde bulunduğu for, foreach, while, do-while veya switch yapısını hemen sonlandırır. continue ise turun geri kalanını atlar ve döngünün bir sonraki turuna geçer. İkisi de isteğe bağlı bir sayı alır: break 2 iç içe iki yapıdan birden çıkar, continue 2 dıştaki döngünün sonraki turuna atlar. Sayı verilmezse varsayılan değer 1'dir.

php
<?php
$tablo = [
    [1, 2, 3],
    [4, -1, 6],
    [7, 8, 9],
];
foreach ($tablo as $satir => $hucreler) {
    foreach ($hucreler as $deger) {
        if ($deger < 0) {
            echo "Negatif değer bulundu, satır $satir", PHP_EOL;
            break 2;
        }
        echo $deger, ' ';
    }
    echo PHP_EOL;
}
/* çıktı:
1 2 3
4 Negatif değer bulundu, satır 1
*/

for ($i = 1; $i <= 3; $i++) {
    foreach (['a', 'b', 'c'] as $h) {
        if ($h === 'b') {
            continue 2;
        }
        echo "$i$h ";
    }
}
echo PHP_EOL;
// çıktı: 1a 2a 3a

İlk örnekte negatif değer bulunduğu anda break 2 hem iç hem dış döngüyü bitiriyor; üçüncü satıra hiç geçilmiyor. Yalnızca break yazsaydınız iç döngü biterdi, dış döngü üçüncü satırla devam ederdi. İkinci örnekte continue 2, 'b' görüldüğü anda iç döngünün geri kalanını ve dış döngünün o turunu atlıyor; bu yüzden 'c' hiç yazdırılmıyor.

Kılavuzun özellikle vurguladığı bir ayrıntı var: PHP, continue açısından switch yapısını da bir döngü sayar. Döngü içindeki bir switch bloğunda düz continue yazarsanız bu, break gibi davranır ve yalnızca switch bloğundan çıkar. PHP 7.3'ten beri bu durum için uyarı verilir, çünkü büyük ihtimalle bir hatadır:

php
<?php
foreach ([1, 2] as $x) {
    switch ($x) {
        case 1:
            continue; // niyet: döngünün sonraki turu
    }
    echo $x, PHP_EOL;
}
/* çıktı:
Warning: "continue" targeting switch is equivalent to "break". Did you mean to use "continue 2"? in /yol/ornek.php on line 5
1
2
*/

Çıktıda 1 de yazdırıldı, yani döngünün o turu atlanmadı. Niyetiniz dış döngünün sonraki turuna geçmekse continue 2 yazmalısınız. switch ile match arasındaki farklar için if, else, switch ve match yazısına bakın.

Alternatif sözdizimi: HTML şablonlarında döngü

PHP; for, foreach ve while için süslü parantez yerine iki nokta üst üste ile açılıp endfor;, endforeach;, endwhile; ile kapanan alternatif bir yazım sunar. Bu yazım özellikle PHP kodunun HTML içine gömüldüğü şablonlarda okunabilirliği artırır, çünkü hangi bloğun nerede kapandığı açıkça görünür. do-while için alternatif yazım yoktur.

php
<?php
$menu = ['Ana Sayfa', 'Blog', 'İletişim'];
?>
<ul>
<?php foreach ($menu as $baslik): ?>
  <li><?= $baslik ?></li>
<?php endforeach; ?>
</ul>
text
<ul>
  <li>Ana Sayfa</li>
  <li>Blog</li>
  <li>İletişim</li>
</ul>

Gerçek bir sayfada kullanıcıdan gelen metni HTML içine yazmadan önce mutlaka kaçırmanız gerekir; bu konu filter_var ve htmlspecialchars yazısında anlatılıyor. Etiketlerin ve <?= kısayolunun ayrıntıları için PHP temel sözdizimi yazısına bakabilirsiniz.

Hangi döngüyü ne zaman kullanmalı?

Hangi döngüyü ne zaman kullanmalı? Her turun başında, foreach, while, do-while
DöngüKoşul kontrolüEn az bir kez çalışır mı?Tipik kullanım
forHer turun başındaHayırSayaçlı tekrar, belirli adım sayısı
foreachEleman kalmayınca biterHayır (boş dizide çalışmaz)Dizi ve nesne elemanlarını gezmek
whileHer turun başındaHayırKoşul sağlandıkça tekrar, kuyruk işleme
do-whileHer turun sonundaEvetEn az bir deneme gereken işler

Pratikte PHP kodunda en sık göreceğiniz döngü foreach'tir, çünkü PHP dizileri hem liste hem sözlük görevi görür ve foreach ikisini de doğal biçimde gezer. Sayısal bir aralıkta dönmeniz gerekiyorsa for ya da foreach (range(1, 10) as $i) tercih edilebilir. Bitiş anı önceden bilinmeyen işler, örneğin bir dosyayı satır satır okumak, while ile daha doğal yazılır. Dosya okuma döngüleri için PHP dosya okuma ve yazma yazısına, çok büyük veri kümelerini bellek dostu biçimde gezmek için generator ve yield yazısına bakabilirsiniz.

Küçük uygulama: bütçeli alışveriş sepeti

Aşağıdaki örnek, aynı döngüde continue ve break kullanımını bir arada gösteriyor. Sepetteki ürünler sırayla geziliyor; stokta olmayan ürün atlanıyor, bütçeyi aşacak ilk üründe ise döngü tamamen bitiyor. Bu kalıp, bir listeyi belirli bir sınıra kadar işleyip duran her türlü iş için uyarlanabilir.

php
<?php
$sepet = [
    ['ad' => 'Kulaklık', 'fiyat' => 750, 'stok' => 4],
    ['ad' => 'Mouse', 'fiyat' => 300, 'stok' => 0],
    ['ad' => 'Klavye', 'fiyat' => 900, 'stok' => 2],
    ['ad' => 'Monitör', 'fiyat' => 6500, 'stok' => 1],
];
$butce = 2000;
$toplam = 0;

foreach ($sepet as $urun) {
    if ($urun['stok'] === 0) {
        echo "{$urun['ad']}: stokta yok, atlandı", PHP_EOL;
        continue;
    }
    if ($toplam + $urun['fiyat'] > $butce) {
        echo "{$urun['ad']}: bütçe aşılıyor, döngü bitti", PHP_EOL;
        break;
    }
    $toplam += $urun['fiyat'];
    echo "{$urun['ad']} eklendi, ara toplam $toplam", PHP_EOL;
}
echo "Toplam: $toplam TL", PHP_EOL;
/* çıktı:
Kulaklık eklendi, ara toplam 750
Mouse: stokta yok, atlandı
Klavye eklendi, ara toplam 1650
Monitör: bütçe aşılıyor, döngü bitti
Toplam: 1650 TL
*/

Stok kontrolünde === kullanıldığına dikkat edin: Değer veritabanından metin olarak gelseydi '0' === 0 yanlış olurdu ve kontrol işe yaramazdı. Böyle durumlarda değeri önce (int) ile dönüştürmek ya da gevşek karşılaştırmanın kurallarını bilmek gerekir; ayrıntılar PHP eşitlik ve karşılaştırma yazısında.

Sık yapılan hatalar

  • Referanslı foreach sonrası unset unutmak: Döngü değişkeni son elemana bağlı kalır ve sonraki bir döngü veya atama son elemanı sessizce değiştirir.
  • Koşulda her turda pahalı bir fonksiyon çağırmak: for koşulu her turda yeniden çalışır; sabit kalan değeri ilk bölümde bir değişkene alın.
  • Anahtarları ardışık olmayan dizide for kullanmak: array_filter() sonrası gibi boşluklu anahtarlarda $dizi[$i] tanımsız anahtar uyarısı verir; foreach kullanın.
  • switch içinde düz continue yazmak: Döngüye değil switch bloğuna etki eder; dış döngü için continue 2 gerekir.
  • Sonsuz döngü: while koşulundaki değişkeni gövdede hiç güncellememek ya da while (true) içinde ulaşılamayan bir break bırakmak.
  • null değeri foreach'e vermek: Uyarıyı @ ile bastıramazsınız; ?? [] ile boş dizi verin.

Sık Sorulan Sorular

foreach döngüsü diziyi kopyalar mı, içeride diziyi değiştirirsem ne olur?

Değerle gezdiğinizde (as $v) $v değişkenine yaptığınız atama diziyi değiştirmez. Diziyi gerçekten değiştirmek istiyorsanız ya as &$v ile referansla gezin ve döngüden sonra unset($v) yapın ya da $dizi[$k] = ... biçiminde anahtar üzerinden yazın. Gezinme sırasında aynı diziye eleman ekleyip çıkarmak okunması zor kod üretir; yeni bir dizi oluşturmak çoğu zaman daha anlaşılırdır.

break ile return arasındaki fark nedir?

break yalnızca içinde bulunduğu döngüden (veya switch bloğundan) çıkar; kod döngüden sonraki satırdan devam eder. return ise bulunduğu fonksiyonun tamamını bitirir ve değeri çağırana döndürür. Bir fonksiyon içindeki aramada sonucu bulduğunuz anda return yazmak, hem döngüden hem fonksiyondan tek adımda çıkmanın en kısa yoludur. Fonksiyonların ayrıntıları için PHP fonksiyon tanımlama yazısına bakın.

for mu foreach mi daha hızlı?

PHP kılavuzu iki döngü arasında hız karşılaştırması yapmaz; dizileri gezmek için foreach'i önerir, çünkü bu iş için tasarlanmıştır ve genellikle daha rahattır. Kılavuzun performansla ilgili tek somut önerisi, for koşuluna her turda aynı sonucu veren bir fonksiyon çağrısı koymamaktır. Okunabilirliği esas alıp ölçmeden optimizasyon yapmamak en güvenli yaklaşımdır.

İç içe üç döngüden tek seferde nasıl çıkarım?

break 3 yazarak. Sayı, kaç seviye dışarı çıkılacağını belirtir ve switch blokları da bu sayıma dahildir. Çok derin iç içe döngüler genellikle kodun bir fonksiyona ayrılması gerektiğinin işaretidir; iç kısmı ayrı bir fonksiyona taşıyıp return ile çıkmak çoğu zaman daha okunaklı olur.

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 · if, else, switch ve match · PHP dizi temelleri · PHP referanslar · PHP generator ve yield