Lekcja 7: Pętle (while, do...while, for, foreach)

Witaj w siódmej lekcji kursu PHP! Po zrozumieniu, jak podejmować decyzje za pomocą instrukcji warunkowych, nadszedł czas na naukę, jak wielokrotnie wykonywać te same lub podobne bloki kodu. Służą do tego pętle – fundamentalne konstrukcje w programowaniu, które pozwalają na automatyzację powtarzalnych zadań. PHP oferuje kilka rodzajów pętli: while, do...while, for oraz foreach, każda z nich dostosowana do nieco innych scenariuszy. W tej lekcji dokładnie je omówimy.

Czym są pętle i dlaczego są potrzebne?

Pętla to struktura kontrolna, która pozwala na powtarzanie wykonania określonego bloku kodu tak długo, jak długo spełniony jest pewien warunek (lub dla określonej liczby iteracji). Wyobraź sobie, że chcesz wyświetlić liczby od 1 do 100, przetworzyć wszystkie elementy tablicy, lub wczytywać dane od użytkownika, dopóki nie wprowadzi on specjalnego polecenia zakończenia. Bez pętli musiałbyś wielokrotnie kopiować i wklejać ten sam kod, co byłoby nieefektywne, trudne w utrzymaniu i podatne na błędy. Pętle rozwiązują ten problem, umożliwiając zwięzłe i eleganckie zarządzanie powtórzeniami.

Każde pojedyncze wykonanie bloku kodu wewnątrz pętli nazywane jest iteracją.

Pętla while

Pętla while jest najprostszą formą pętli w PHP. Wykonuje ona blok kodu tak długo, jak długo podany warunek jest prawdziwy (true). Warunek jest sprawdzany przed każdą iteracją pętli.

Składnia:

<?php
while (warunek) {
    // Blok kodu do wykonania, dopóki warunek jest true
}
?>

Jeśli warunek jest fałszywy już na samym początku, blok kodu wewnątrz pętli while nie zostanie wykonany ani razu.

Ważne jest, aby wewnątrz bloku kodu pętli while znajdowała się instrukcja, która w pewnym momencie spowoduje, że warunek stanie się fałszywy. W przeciwnym razie pętla będzie wykonywać się w nieskończoność (tzw. pętla nieskończona), co zazwyczaj prowadzi do zawieszenia skryptu lub przekroczenia limitu czasu wykonania.

<?php
// Przykład 1: Wyświetlanie liczb od 1 do 5
$licznik = 1;
echo "<h3>Pętla while - liczby od 1 do 5:</h3>";
while ($licznik <= 5) {
    echo "Liczba: $licznik <br>";
    $licznik++; // Kluczowe: inkrementacja licznika, aby warunek w końcu stał się false
}

// Przykład 2: Pętla, która może się nie wykonać
$wartosc = 10;
echo "<h3>Pętla while - warunek początkowo fałszywy:</h3>";
while ($wartosc < 5) {
    echo "Ta wiadomość się nie wyświetli, bo $wartosc nie jest mniejsze od 5.<br>";
    $wartosc++;
}
echo "Koniec pętli while (warunek początkowo fałszywy).<br>";

// Przykład 3: Symulacja rzutu kostką, aż wypadnie 6
$rzut = 0;
$liczbaRzutow = 0;
echo "<h3>Pętla while - rzut kostką do 6:</h3>";
while ($rzut !== 6) {
    $rzut = rand(1, 6); // Losuje liczbę od 1 do 6
    $liczbaRzutow++;
    echo "Rzut nr $liczbaRzutow: $rzut <br>";
}
echo "Wyrzucono 6 po $liczbaRzutow rzutach!<br>";
?>

W pierwszym przykładzie zmienna $licznik jest inicjalizowana wartością 1. Pętla wykonuje się, dopóki $licznik jest mniejszy lub równy 5. W każdej iteracji wartość $licznik jest wyświetlana, a następnie zwiększana o 1. Gdy $licznik osiągnie wartość 6, warunek $licznik <= 5 staje się fałszywy i pętla kończy działanie.

Pętla do...while

Pętla do...while jest podobna do pętli while, z jedną kluczową różnicą: warunek jest sprawdzany po wykonaniu bloku kodu w każdej iteracji. Oznacza to, że blok kodu wewnątrz pętli do...while zostanie wykonany przynajmniej raz, nawet jeśli warunek jest fałszywy od samego początku.

Składnia:

<?php
do {
    // Blok kodu do wykonania
} while (warunek);
?>

Zwróć uwagę na średnik ; po while (warunek) – jest on wymagany w tej konstrukcji.

<?php
// Przykład 1: Blok wykonany przynajmniej raz
$licznikDoWhile = 6;
echo "<h3>Pętla do...while - wykonana przynajmniej raz:</h3>";
do {
    echo "Licznik (do...while): $licznikDoWhile <br>";
    $licznikDoWhile++;
} while ($licznikDoWhile <= 5); // Warunek (6 <= 5) jest fałszywy, ale blok się wykonał raz

// Przykład 2: Wczytywanie danych od użytkownika, aż poda poprawne
// (To jest przykład koncepcyjny, w PHP webowym inaczej obsługuje się interakcję)
$hasloPoprawne = "secret";
$prob = 0;
echo "<h3>Pętla do...while - zgadywanie hasła (koncepcyjne):</h3>";
/*
do {
    $wprowadzone = readline("Podaj hasło: "); // Funkcja readline działa w CLI
    $prob++;
    if ($wprowadzone !== $hasloPoprawne) {
        echo "Błędne hasło. Spróbuj ponownie.\n";
    }
} while ($wprowadzone !== $hasloPoprawne && $prob < 3);

if ($wprowadzone === $hasloPoprawne) {
    echo "Hasło poprawne po $prob próbach!\n";
} else {
    echo "Przekroczono liczbę prób.\n";
}
*/
echo "Przykład z readline() jest dla CLI, w web używa się formularzy.<br>";

$i = 0;
echo "<h3>Pętla do...while - liczby od 0 do 2:</h3>";
do {
    echo "i = $i <br>";
    $i++;
} while ($i < 3);
?>

Pętla do...while jest użyteczna, gdy chcemy, aby pewna operacja została wykonana co najmniej raz, a następnie powtarzana w zależności od warunku (np. wyświetlenie menu i pobranie wyboru użytkownika).

Pętla for

Pętla for jest bardziej złożona, ale też bardzo potężna. Jest idealna do sytuacji, gdy znamy z góry liczbę iteracji, które chcemy wykonać, lub gdy potrzebujemy bardziej skomplikowanej logiki inicjalizacji i aktualizacji licznika pętli.

Składnia:

<?php
for (wyrazenie_inicjalizujace; warunek_kontynuacji; wyrazenie_aktualizujace) {
    // Blok kodu do wykonania
}
?>

Składa się z trzech części, oddzielonych średnikami, wewnątrz nawiasów:

  1. wyrazenie_inicjalizujace: Wykonywane jednorazowo na samym początku pętli. Zazwyczaj służy do zainicjowania zmiennej licznika (np. $i = 0;).
  2. warunek_kontynuacji: Sprawdzany przed każdą iteracją. Jeśli jest true, pętla kontynuuje; jeśli false, pętla kończy działanie.
  3. wyrazenie_aktualizujace: Wykonywane na końcu każdej iteracji, po wykonaniu bloku kodu. Zazwyczaj służy do aktualizacji licznika (np. $i++, $i -= 2).

Każda z tych trzech części jest opcjonalna. Jeśli pominiemy warunek_kontynuacji, PHP potraktuje go jako zawsze prawdziwy, co stworzy pętlę nieskończoną (chyba że użyjemy break wewnątrz).

<?php
// Przykład 1: Wyświetlanie liczb od 0 do 4
echo "<h3>Pętla for - liczby od 0 do 4:</h3>";
for ($i = 0; $i < 5; $i++) {
    echo "Wartość i: $i <br>";
}

// Przykład 2: Wyświetlanie liczb parzystych od 10 do 2 (malejąco)
echo "<h3>Pętla for - liczby parzyste od 10 do 2:</h3>";
for ($j = 10; $j >= 2; $j -= 2) {
    echo "Wartość j: $j <br>";
}

// Przykład 3: Iteracja przez znaki w stringu
$napis = "PHP";
echo "<h3>Pętla for - iteracja po znakach napisu '$napis':</h3>";
for ($k = 0; $k < strlen($napis); $k++) {
    echo "Znak na pozycji $k: " . $napis[$k] . "<br>";
}

// Przykład 4: Pętla for z wieloma zmiennymi w inicjalizacji i aktualizacji
echo "<h3>Pętla for - wiele zmiennych:</h3>";
for ($x = 0, $y = 10; $x <= 10; $x++, $y--) {
    echo "x=$x, y=$y <br>";
    if ($x === $y) {
        echo "Spotkanie w połowie drogi!<br>";
    }
}

// Przykład 5: Pętla for bez niektórych części (niezalecane dla czytelności)
$a = 0;
echo "<h3>Pętla for - opcjonalne części:</h3>";
for ( ; $a < 3; ) {
    echo "a = $a <br>";
    $a++;
}
?>

Pętla for jest bardzo elastyczna i często używana do iteracji po tablicach indeksowanych numerycznie, choć do tego celu często wygodniejsza jest pętla foreach.

Pętla foreach (Specjalnie dla Tablic i Obiektów)

Pętla foreach została zaprojektowana specjalnie do iterowania po elementach tablic oraz po publicznych właściwościach obiektów. Jest to najwygodniejszy i najczęściej zalecany sposób na przeglądanie zawartości tablic w PHP.

Istnieją dwie formy składni pętli foreach:

1. Iteracja po wartościach:

<?php
foreach (tablica_lub_obiekt as $wartosc) {
    // Blok kodu do wykonania dla każdej wartości
    // W każdej iteracji $wartosc przyjmuje kolejną wartość z tablicy/obiektu
}
?>

2. Iteracja po kluczach i wartościach:

<?php
foreach (tablica_lub_obiekt as $klucz => $wartosc) {
    // Blok kodu do wykonania dla każdej pary klucz-wartość
    // W każdej iteracji $klucz przyjmuje kolejny klucz, a $wartosc odpowiadającą mu wartość
}
?>
<?php
// Przykład 1: Iteracja po wartościach tablicy indeksowanej numerycznie
$kolory = ["czerwony", "zielony", "niebieski"];
echo "<h3>Pętla foreach - wartości z tablicy kolorów:</h3>";
foreach ($kolory as $kolor) {
    echo "Kolor: " . htmlspecialchars($kolor) . "<br>";
}

// Przykład 2: Iteracja po kluczach i wartościach tablicy asocjacyjnej
$osoba = [
    "imie" => "Anna",
    "nazwisko" => "Kowalska",
    "wiek" => 30
];
echo "<h3>Pętla foreach - klucze i wartości z tablicy osoby:</h3>";
foreach ($osoba as $klucz => $dana) {
    echo htmlspecialchars(ucfirst($klucz)) . ": " . htmlspecialchars($dana) . "<br>";
}

// Przykład 3: Modyfikacja elementów tablicy w pętli foreach (za pomocą referencji)
$liczby = [1, 2, 3, 4, 5];
echo "<h3>Pętla foreach - modyfikacja tablicy (referencja):</h3>";
// Aby zmodyfikować tablicę, musimy użyć referencji (&) przy $wartosc
foreach ($liczby as &$liczba) {
    $liczba *= 2; // Podwajamy każdą liczbę
}
unset($liczba); // Ważne: usunięcie referencji po pętli, aby uniknąć problemów

print_r($liczby);
// Wynik: Array ( [0] => 2 [1] => 4 [2] => 6 [3] => 8 [4] => 10 )
echo "<br>";

// Przykład 4: Iteracja po właściwościach obiektu
class Samochod {
    public $marka = "Toyota";
    public $model = "Corolla";
    public $rok = 2022;
    private $przebieg = 15000; // Prywatne właściwości nie są dostępne w foreach z zewnątrz
}

$mojSamochod = new Samochod();
echo "<h3>Pętla foreach - właściwości obiektu Samochod:</h3>";
foreach ($mojSamochod as $wlasciwosc => $wart) {
    echo htmlspecialchars($wlasciwosc) . ": " . htmlspecialchars($wart) . "<br>";
}
// Wyświetli tylko publiczne właściwości: marka, model, rok
?>

Pętla foreach automatycznie obsługuje iterację od pierwszego do ostatniego elementu tablicy (lub właściwości obiektu) zgodnie z ich wewnętrzną kolejnością. Nie musimy martwić się o indeksy czy rozmiar tablicy, co czyni kod bardziej czytelnym i mniej podatnym na błędy.

Ważna uwaga dotycząca modyfikacji tablicy w foreach: Domyślnie foreach operuje na kopii tablicy (lub wartości). Jeśli chcesz modyfikować oryginalną tablicę wewnątrz pętli, musisz użyć referencji, dodając znak & przed zmienną wartości (np. foreach ($tablica as &$wartosc)). Po zakończeniu takiej pętli zaleca się usunięcie referencji za pomocą unset($wartosc), aby uniknąć przypadkowej modyfikacji ostatniego elementu tablicy, jeśli zmienna $wartosc byłaby używana później.

Instrukcje break i continue w Pętlach

Czasami chcemy mieć większą kontrolę nad przebiegiem pętli. PHP dostarcza dwie instrukcje, które pozwalają na modyfikację standardowego przepływu iteracji: break i continue.

break

Instrukcja break natychmiast przerywa wykonanie bieżącej pętli (for, foreach, while, do...while) lub instrukcji switch. Program kontynuuje wykonywanie od pierwszej instrukcji znajdującej się po zakończonej pętli/switch.

break może opcjonalnie przyjąć argument numeryczny, który określa, ile poziomów zagnieżdżonych pętli ma zostać przerwanych (np. break 2; przerwie bieżącą pętlę i pętlę ją otaczającą).

<?php
echo "<h3>Instrukcja break:</h3>";
// Przykład z break w pętli for
for ($i = 0; $i < 10; $i++) {
    if ($i == 5) {
        echo "Przerywam pętlę, gdy i = 5.<br>";
        break; // Pętla zakończy się tutaj
    }
    echo "Iteracja numer: $i <br>";
}
echo "Po pętli for z break.<br>";

// Przykład z break w pętli while (szukanie elementu)
$szukanaLiczba = 7;
$licznikWhileBreak = 1;
while (true) { // Potencjalnie pętla nieskończona
    $losowa = rand(1, 10);
    echo "Wylosowano: $losowa (próba $licznikWhileBreak)<br>";
    if ($losowa === $szukanaLiczba) {
        echo "Znaleziono $szukanaLiczba! Przerywam.<br>";
        break;
    }
    if ($licznikWhileBreak >= 15) { // Zabezpieczenie przed zbyt długim działaniem
        echo "Nie znaleziono po 15 próbach. Przerywam.<br>";
        break;
    }
    $licznikWhileBreak++;
}
?>

continue

Instrukcja continue przerywa bieżącą iterację pętli i natychmiast przechodzi do następnej iteracji. Wszelki kod znajdujący się po continue w bieżącej iteracji jest pomijany.

continue również może przyjąć opcjonalny argument numeryczny, określający, ile poziomów zagnieżdżonych pętli ma dotyczyć (tj. przeskoczy do następnej iteracji tej zewnętrznej pętli).

<?php
echo "<h3>Instrukcja continue:</h3>";
// Przykład z continue w pętli for (pomijanie liczb nieparzystych)
for ($i = 0; $i < 10; $i++) {
    if ($i % 2 != 0) { // Jeśli $i jest nieparzyste
        continue; // Pomiń resztę tej iteracji i przejdź do następnej
    }
    echo "Liczba parzysta: $i <br>";
}

// Przykład z continue w pętli foreach
$produkty = [
    ["nazwa" => "Jabłka", "cena" => 5.00, "dostepny" => true],
    ["nazwa" => "Banany", "cena" => 7.50, "dostepny" => false],
    ["nazwa" => "Pomarańcze", "cena" => 6.20, "dostepny" => true],
    ["nazwa" => "Winogrona", "cena" => 12.00, "dostepny" => true],
];

echo "<h4>Dostępne produkty:</h4>";
foreach ($produkty as $produkt) {
    if (!$produkt["dostepny"]) {
        continue; // Pomiń niedostępne produkty
    }
    echo htmlspecialchars($produkt["nazwa"]) . " - Cena: " . number_format($produkt["cena"], 2) . " zł<br>";
}
?>

Instrukcje break i continue dają dużą elastyczność w kontrolowaniu przepływu pętli, ale należy ich używać z rozwagą, aby kod pozostał czytelny i łatwy do zrozumienia. Nadmierne ich stosowanie może prowadzić do tzw. "spaghetti code".

Alternatywna Składnia dla Pętli (Przydatna w Szablonach HTML)

Podobnie jak w przypadku instrukcji warunkowych, PHP oferuje alternatywną składnię dla pętli, która może być bardziej czytelna przy mieszaniu z HTML:

(Pętla do...while nie ma tak powszechnie używanej alternatywnej składni w tym stylu).

<?php
$listaZakupow = ["Chleb", "Masło", "Ser", "Szynka"];
?>

<h3>Lista zakupów (alternatywna składnia foreach):</h3>
<ul>
    <?php foreach ($listaZakupow as $produkt): ?>
        <li><?= htmlspecialchars($produkt) ?></li>
    <?php endforeach; ?>
</ul>

<h3>Liczby od 1 do 3 (alternatywna składnia for):</h3>
<?php for ($i = 1; $i <= 3; $i++): ?>
    <p>Numer: <?= $i ?></p>
<?php endfor; ?>

Podsumowanie Lekcji

W tej lekcji zgłębiliśmy temat pętli w PHP, które są niezbędne do wykonywania powtarzalnych zadań. Nauczyliśmy się używać pętli while (warunek sprawdzany na początku), pętli do...while (warunek sprawdzany na końcu, blok wykonany przynajmniej raz), oraz wszechstronnej pętli for (z inicjalizacją, warunkiem i aktualizacją). Szczególną uwagę poświęciliśmy pętli foreach, idealnej do iteracji po tablicach i obiektach. Omówiliśmy również instrukcje break (do całkowitego przerwania pętli) i continue (do przejścia do następnej iteracji), a także alternatywną składnię pętli przydatną w szablonach HTML.

Opanowanie pętli jest kluczowe dla efektywnego programowania. W połączeniu z instrukcjami warunkowymi, pętle pozwalają tworzyć złożone i dynamiczne algorytmy. W następnej lekcji skupimy się bardziej szczegółowo na jednym z najważniejszych typów danych w PHP, z którym pętle (zwłaszcza foreach) często współpracują – na tablicach.


Zadanie praktyczne

Napisz skrypt PHP, który:

  1. Używając pętli while, wyświetl wszystkie liczby nieparzyste od 1 do 20.
  2. Używając pętli for, oblicz i wyświetl sumę liczb od 1 do 100.
  3. Zadeklaruj tablicę asocjacyjną przechowującą nazwy kilku państw jako klucze i ich stolice jako wartości (np. ["Polska" => "Warszawa", "Niemcy" => "Berlin", ...]). Używając pętli foreach, wyświetl każdą parę państwo-stolica w formacie: "Stolicą [Państwo] jest [Stolica].".
  4. Używając dowolnej pętli, wyświetl tabliczkę mnożenia dla liczby 7 (od 7*1 do 7*10). Wynik powinien wyglądać np. tak: "7 * 1 = 7", "7 * 2 = 14", ..., "7 * 10 = 70".
  5. W pętli for iterującej od 1 do 10, użyj instrukcji continue, aby pominąć wyświetlanie liczby 5, oraz instrukcji break, aby zakończyć pętlę, gdy liczba osiągnie 8.

Pokaż przykładowe rozwiązanie
<?php
// 1. Liczby nieparzyste od 1 do 20 (pętla while)
echo "<h3>Liczby nieparzyste od 1 do 20:</h3>";
$i = 1;
while ($i <= 20) {
    if ($i % 2 != 0) {
        echo $i . " ";
    }
    $i++;
}
echo "<br>";

// 2. Suma liczb od 1 do 100 (pętla for)
echo "<h3>Suma liczb od 1 do 100:</h3>";
$suma = 0;
for ($j = 1; $j <= 100; $j++) {
    $suma += $j;
}
echo "Suma: $suma <br>";

// 3. Państwa i stolice (pętla foreach)
echo "<h3>Państwa i stolice:</h3>";
$panstwaStolice = [
    "Polska" => "Warszawa",
    "Niemcy" => "Berlin",
    "Francja" => "Paryż",
    "Czechy" => "Praga"
];
foreach ($panstwaStolice as $panstwo => $stolica) {
    echo "Stolicą kraju " . htmlspecialchars($panstwo) . " jest " . htmlspecialchars($stolica) . ".<br>";
}

// 4. Tabliczka mnożenia dla 7
echo "<h3>Tabliczka mnożenia dla 7:</h3>";
$liczbaDoMnozenia = 7;
for ($k = 1; $k <= 10; $k++) {
    echo $liczbaDoMnozenia . " * " . $k . " = " . ($liczbaDoMnozenia * $k) . "<br>";
}

// 5. Pętla for z continue i break
echo "<h3>Pętla for z continue i break (1-10, pomiń 5, zakończ na 8):</h3>";
for ($l = 1; $l <= 10; $l++) {
    if ($l == 5) {
        echo "(Pominięto 5 za pomocą continue)<br>";
        continue;
    }
    if ($l == 8) {
        echo "(Zakończono pętlę na 8 za pomocą break)<br>";
        break;
    }
    echo "Liczba: $l <br>";
}
echo "Koniec pętli z continue/break.<br>";
?>
            

Zadanie do samodzielnego wykonania

Napisz skrypt, który generuje prostą listę rozwijaną (element HTML <select>) zawierającą lata od bieżącego roku do 10 lat wstecz. Użyj pętli for do wygenerowania opcji (<option>). Bieżący rok powinien być domyślnie zaznaczony (atrybut selected). Do pobrania bieżącego roku użyj funkcji date("Y").


FAQ - Pętle w PHP

Jak uniknąć pętli nieskończonej?

Aby uniknąć pętli nieskończonej, upewnij się, że warunek kontynuacji pętli (w while, do...while, for) w pewnym momencie stanie się fałszywy. Zazwyczaj oznacza to modyfikowanie zmiennej używanej w warunku wewnątrz ciała pętli (np. inkrementacja licznika). Warto też testować pętle z różnymi danymi wejściowymi.

Czy mogę użyć break lub continue poza pętlą lub instrukcją switch?

Nie, użycie break lub continue poza kontekstem pętli (for, foreach, while, do...while) lub instrukcji switch (tylko break) spowoduje błąd fatalny (Fatal error).

Jaka jest różnica w działaniu continue w pętli for vs while?

W pętli while (i do...while), continue natychmiast przechodzi do sprawdzenia warunku pętli. W pętli for, po continue najpierw wykonywane jest wyrażenie aktualizujące (trzecia część nagłówka for, np. $i++), a dopiero potem sprawdzany jest warunek kontynuacji.

Czy mogę modyfikować tablicę, po której iteruję w pętli foreach?

Tak, ale należy to robić ostrożnie. Jeśli chcesz modyfikować wartości elementów tablicy, użyj referencji (foreach ($tablica as &$wartosc)). Jeśli dodajesz lub usuwasz elementy z tablicy podczas iteracji foreach, zachowanie może być nieprzewidywalne, ponieważ foreach operuje na wewnętrznym wskaźniku tablicy. W takich przypadkach bezpieczniej jest iterować po kopii tablicy lub użyć pętli for z indeksami.

Czy pętla foreach działa na obiektach?

Tak, pętla foreach może iterować po publicznych właściwościach obiektu. Kluczem będzie nazwa właściwości, a wartością – jej wartość. Prywatne i chronione właściwości nie są domyślnie dostępne w foreach z zewnątrz obiektu, chyba że klasa implementuje specjalne interfejsy (np. Iterator).

Czy istnieje limit głębokości zagnieżdżania pętli?

PHP samo w sobie nie narzuca sztywnego limitu głębokości zagnieżdżania pętli. Jednak bardzo głębokie zagnieżdżanie (np. więcej niż 2-3 poziomy) zazwyczaj prowadzi do kodu, który jest trudny do zrozumienia i utrzymania. W takich przypadkach warto rozważyć refaktoryzację kodu, np. poprzez wydzielenie części logiki do osobnych funkcji.

Co jeśli warunek w pętli for jest pominięty?

Jeśli druga część nagłówka pętli for (warunek kontynuacji) zostanie pominięta, PHP traktuje go jako zawsze prawdziwy. Oznacza to, że pętla for (inicjalizacja; ; aktualizacja) będzie pętlą nieskończoną, chyba że w jej ciele zostanie użyta instrukcja break.