Kurs PHP (część 2): struktury kontrolne

Artykuł dodany: 27 grudnia 2011. Ostatnia modyfikacja: 27 grudnia 2011.

Stopień trudności (1 - dla początkujących, 5 - dla ekspertów): 1

W pierwszej części kursu zapoznałeś się Czytelniku z podstawowymi pojęciami związanymi z językiem PHP. Prostą konfiguracją serwera, komentarzami i definicją zmiennych. Jeśli czujesz się trochę znudzony, masz rację. Najciekawsze zagadnienia dopiero przed nami.

Operatory porównania

Wszystkie dane które do tej pory wprowadzałeś miały charakter raczej statyczny. Do zmiennej przypisywałeś wartość, całość wyświetlana była w przeglądarce. W niewielkim stopniu zetknąłeś się również z operatorami arytmetycznymi. To co sprawia że skrypty “żyją” to operatory porównania. Na ich podstawie testujesz czy dane spełniają określone warunki. Jeżeli warunek został spełniony zwracany jest typ boolean “true” (prawda), w przeciwnym wypadku “false” (fałsz):

<?php

# definicja zmiennych
$min = 3;
$max = 10;
$test = 3;

/* Operator "mniejszy niż" < 
   $a < $b - testuje czy $a jest mniejsze niż $b
    3 < 10 - prawda
*/
$wynik = ($min < $max);
echo "Test $min'<'$max: $wynik - ". gettype($wynik);

/* Operator "większy niż" > 
   $a > $b - testuje czy $a jest większe niż $b
    3 > 10 - fałsz
*/
$wynik = ($min > $max);
echo "<br>Test $min'>'$max: $wynik - ". gettype($wynik);

/* Operator "mniejszy lub równy" <= 
   $a <= $b - testuje czy $a jest mniejsze lub równe $b
    3 <= 3 - prawda
*/
$wynik = ($min <= $test);
echo "<br>Test $min'<='$test: $wynik - ". gettype($wynik);

/* Operator "większy lub równy" >= 
   $a >= $b - testuje czy $a jest większe lub równe $b
    3 >= 3 - prawda
*/
$wynik = ($min >= $test);
echo "<br>Test $min'>='$test: $wynik - ". gettype($wynik);

/* Operator "równości" == 
   $a == $b - testuje czy $a jest równe $b
    3 == 3 - prawda
*/
$wynik = ($min == $test);
echo "<br>Test $min'=='$test: $wynik - ". gettype($wynik);

/* Operator "różne" != , <>
   $a != $b - testuje czy $a nie jest równe $b
    3 != 3 - fałsz
    3 <> 3 - fałsz
*/
$wynik = ($min != $test);
echo "<br>Test $min'!='$test: $wynik - ". gettype($wynik);

Powyższe operatory testują wyłącznie wartość. W poprzedniej części kursu wspomniałem jednak że PHP jest językiem dynamicznie typowanym tak więc string ‘15’ przy porównaniu zostanie przekształcony na liczbę 15.

<?php

$str = '15';
$int = 15;

$wynik = ($str == $int);
echo "<br>Test $str:".gettype($str)."'=='$int:".gettype($int)." :: $wynik";

Istnieją sytuacje w których chcemy porównać nie tylko wartość ale też i typ zmiennych. Od PHP 4 wprowadzono dwa nowe operatory:

<?php

$str = '15';
$int = 15;

/* Operator "identyczne" ===
   $a === $b - testuje czy $a jest równe $b i czy jest tego samego typu
   15 === '15' - fałsz
   15 === 15   - prawda
*/
$wynik = ($str === $int);
echo "<br>Test $str'==='$int: $wynik - ". gettype($wynik);

/* Operator "nie identyczne" !==
   $a !== $b - testuje czy $a nie jest równe $b oraz czy nie są tego samego typu
   15 !== '15' - prawda
   15 !== 15   - fałsz
*/
$wynik = ($str !== $int);
echo "<br>Test $str'!=='$int: $wynik - ". gettype($wynik);

Ważną sprawą jest też oczywiście kolejność w jakiej operacje są wykonywane. Proste równanie matematyczne 1 + 2 * 3 daje nam wynik 7 a nie 9 dlatego, że w pierwszej kolejności wykonywane jest mnożenie (2*3) i dopiero do jego wyniku dodawane jest 1 (6+1). Jak widzisz w dokumentacji, operatory mnożenia, dzielenia i modulo stoją wyżej niż sumy i różnicy. Ale też operator porównania ‘<>’ ma większe znaczenie niż ‘!=’. W pewnych sytuacjach należy o tym pamiętać.

Wyrażenie ‘if’ oraz operatory logiczne

Małymi krokami doszliśmy do jednego z najważniejszych elementów programowania – testowania warunków. To na podstawie testów będziesz w stanie określić jaką akcję należy podjąć i co przesłać do odwiedzającego Twoją stronę. Pierwszym i najważniejszym wyrażeniem jest ‘if’ (jeżeli):

<?php

if (true) {
  # jeżeli warunek jest spełniony (prawdziwy) wykonaj kod
} elseif (true) { # alernatywnie składnia "else if"
  # jeżeli poprzedni warunek nie został spełniony (wyrażenie zwróciło false)
  # i aktualny warunek jest prawdziwy wykonaj kod
} else {
  # wykonaj kod jeżeli poprzednie warunki nie zostały spełnione
}

Spróbujmy zatem, bazując na poprzednich przykładach, wypisać różne wartości użytkownikowi.

<?php

$min = 3;
$max = 10;

if ($min > 5) {
  echo "<p>Udało się! $min jest większe niż 5.</p>";
} else {
  echo "<p>Niestety $min nie jest większe niż 5.</p>";
}

if ($max >= $min) {
  echo "<p>Sukces. $max jest większe lub równe $min.</p>";
}

# testujemy wykorzystując operatory logiczne
if ($max >= 1 and $max <= 3) {
  echo "$max zawiera się w przediale 1-3.";
} elseif ($max > 3 && $max < 10) {
  echo "$max zawiera się w przediale 4-9.";
} else {
  echo "$max nie zawiera się w przedziale 1-9.";
}

Spróbuj przetestować działanie skryptu modyfikując zakresy zmiennych $min, $max czy podstawiając inne niż integer wartości. W przykładzie wprowadziłem również operatory logiczne. Służą one do testowania różnych warunków w jednym wyrażeniu.

Podsumowanie operatorów logicznych:
$a and $b, $a && $b - true, jeżeli $a i $b są prawdziwe
$a or $b , $a || $b - true, jeżeli $a lub $b jest prawdziwa
!$a (negacja) - true, jeżeli $a nie jest prawdziwe
$a xor $b - true, jeżeli $a lub $b jest prawdziwa, ale nie obie równocześnie

Zgodnie z wcześniej prezentowaną tabelą operatory ‘&&’ oraz ‘||’ mają większą wartość niż słowny zapis ‘and’, ‘or’.

Wyrażenia można dowolnie w sobie zagnieżdżać. Nic zatem nie stoi na przeszkodzie aby wewnątrz ‘if-else’ znalazły się kolejne wyrażenia tego typu. Kod będzie przez to mniej czytelny ale oczywiście w zależności od naszych potrzeb zagnieżdżanie może być nieuniknione. Aby poprawić czytelność można wykorzystać tak zwany operator ‘ternary’ (?:) – istnieją różne tłumaczenia na polski, może pozostańmy jednak jako programiści przy nazwie oryginalniej.

<?php
$wiek = 13;
# jeżeli warunek spełniony zwróć wyrażenie po '?', w przeciwnym razie po ':'
echo ($wiek >= 18) ? 'Dozwolone od lat 18.' : 'Jesteś jeszcze za młody.';

Konstrukcja taka jest bardzo czytelna i zmniejsza ilość kodu. Jak widzisz, możesz też od razu, bez przypisywania do zmiennej zwrócić wartość na podstawie testu. Nie zawsze potrzebujesz pełen warunek if-else dlatego od wersji 5.3 PHP można również stosować konstrukcję skróconą:

<?php
# Zwróć expr1 jeżeli expr1 jest prawdą, expr3 w innym wypadku
expr1 ?: expr3

Być może zauważyłeś że stosowałem do tej pory nadmiarowe nawiasy (). W żadnej części dotychczasowego kodu nie były one potrzebne. Równie dobrze kod można zapisać jako:

<?php
$wiek = 13;
echo $wiek >= 18 ? 'Dozwolone od lat 18.' : 'Jesteś jeszcze za młody.';

Dobrą praktyką jest dodawać takie nawiasy jeżeli wykonujemy skomplikowane operacje na wielu operatorach. Człowiek popełnia błędy (to prawda którą należy zapamiętać), możemy przeoczyć jakiś znak i w efekcie otrzymamy zupełnie inny wynik. Koszt tego niewielki w porównaniu do czasu spędzonego później przy sprawdzaniu wszystkich znaków i warunków. Tak samo pamiętaj o średnikach na końcu wyrażeń oraz (to w szczególności może sprawiać problem na początku) że znak równości ‘=’ oznacza przypisanie wartości a nie sprawdzanie czy zmienna jest równa.

Istnieje jeszcze jeden sposób zapisu warunków.

<?php
$wiek = 10;

if ($wiek == 5):
  echo 'Twój wiek to 5 lat';
elseif ($wiek == 10):
  echo 'Twój wiek to 10 lat';
else:
  echo 'Nie wiem ile masz lat.';
endif;

Warunek ten w działaniu nie różni się niczym od przedstawionego wcześniej. Jest to wyłącznie inna składnia która w pewnych sytuacjach może być bardziej czytelna i wygoda. Dla przykładu gdybyś chciał stworzyć własny system szablonów z kodem wynikowym mieszającym HTML i PHP.

Przełączanie czyli wyrażenie ‘switch / case’

Bardzo podobną funkcjonalność do konstrukcji ‘if-else’ oferuje struktura kontrolna switch. W pewnych sytuacjach potrafi być szybsza – warunek przetwarzany jest tylko raz i porównywany po kolei z każdą wartością ‘case’. Wszystkie wyrażenia alternatyw ‘elseif’ są zawsze ponownie parsowane.

<?php
$wiek = 10;

switch ($wiek) {
  case 5:
    echo 'Twój wiek to 5 lat';
    break;
  case 10:
    echo 'Twój wiek to 10 lat';
    break;
  default:
    echo 'Nie wiem ile masz lat.';
}

Warto zrozumieć dokładniej jak działa ‘switch’. Parser przetwarza je warunek po warunku dopóki nie natrafi na pasującą wartość. Od tego momentu przetwarza dalej cały blok ‘switch’ (do końca), lub do pierwszego napotkanego wyrażenia ‘break’. W tym momencie wychodzi z całego bloku. Zobacz jaki będzie wynik działania kodu bez ‘break’:

<?php
$wiek = 10;

switch ($wiek) {
  case 5:
    echo 'Twój wiek to 5 lat. ';
  case 10:
    echo 'Twój wiek to 10 lat. ';
  case 11:
    echo 'Twój wiek to 11 lat. ';
  default:
    echo 'Nie wiem ile masz lat.';
}

Aby lepiej zrozumieć zagadnienie przypisz do zmiennej $wiek wartość 11, a następnie 12. Specjalna konstrukcja ‘default’ wykonywana jest, jeżeli w żadnym wyrażeniu ‘case’ parser nie dopasował wartości.

Dołączanie zewnętrznych plików za pomocą ‘include / require’

Dotychczas pracowaliśmy wyłącznie na jednym, głównym pliku ‘index.php’. Jednak już na początek oczyma wyobraźni możesz zobaczyć szkielet własnej strony podzielony na kilka części. Statyczny nagłówek i stopkę oraz dynamicznie generowaną zawartość właściwą. Istnieją dwie specjalne konstrukcje do tego typu operacji. Pierwsza include dołącza i przetwarza zawartość pliku (czyli może on zawierać dowolny kod PHP). Gdyby plik nie został odnaleziony, wyświetla się komunikat E_WARNING, konstrukcja zwraca false:

<?php
include('nie_istnieje.php');
echo 'Przetwarzamy dalej.';
# PHP Warning: include(nie_istneje.php): failed to open stream: No such file or directory in /www/index.php on line 2

ale skrypt jest przetwarzany dalej. Zupełnie inaczej zachowuje się require. Powoduje ona błąd E_COMPILE_ERROR i parsowanie skryptu jest przerywane.

<?php
require('nie_istnieje.php');
echo 'To nie zostanie wyświetlone.';
# PHP Warning: require(nie_istneje.php): failed to open stream: No such file or directory in /www/index.php on line 2

Dwie bardzo ważne uwagi:

  • Windows nie rozróżnia wielkości znaków. Zarówno pliki test.php oraz Test.php zostaną dołączone. Inaczej jest w systemie Linux. Ponieważ na 99% przegrasz swoją stronę na serwer postawiony na Linuksie / Uniksie zwróć na to uwagę. Warto już na początku przyjąć jednolitą konwencję nazewniczą dla plików projektu.
  • Zawsze pamiętaj o zabezpieczaniu plików. Istnieje możliwość dołączania zdalnego kodu z innego serwera. Jeżeli nie masz pewności że kod ten jest bezpieczny pod żadnym pozorem nie wczytuj go.

Jest jeszcze trzecia kwestia którą omówię w kolejnej części kursu. Dotyczy przyjmowania danych od użytkownika. Teraz aby nie mieszać podkreślę tylko, że wszystkie dane zewnętrzne należy traktować jako niepewne i sprawdzać na wszystkie możliwe sposoby.

Wracając do spraw prostych i przyjemnych podzielmy naszą stronę na dwa pliki. Główny ‘index.php’ oraz zewnętrzny plik, zawierający całe menu strony:

menu.php

<ul id="menu">
  <li><a href="index.php" title="Strona główna">Strona główna</a></li>
</ul>
<?php
  echo 'Treść generowana przez PHP: '.$test;

Zauważ że pojawia się tutaj kod PHP ze zmienną $test zadeklarowaną w pliku głównym.

index.php

<!doctype html>
<html>
<head></head>
<body>
<?php
  $test = 'zmienna z pliku index.php';
  require_once('menu.php');
?>

<div id="content">
  <p>Nasza pierwsza strona.</p>
</div>

</body>
</html>

Konstrukcja require_once / include_once sprawdza, czy plik nie został wcześniej wczytany i pomija go jeżeli tak się stało. Dzięki temu przykładowo nie przypiszesz ponownie zmiennych. Pliki wczytywane są względnie w stosunku do pliku źródłowego, z którego wywołałeś instrukcję.

index.php

<?php
require('t/test.php');

t/test.php

<?php
require('test2.php');

Plik test2.php zostanie wczytany o ile znajduje się w katalogu t. W strukturze plików kropka (.) oznacza aktualny plik (./test2.php), dwie kropki (..) folder nadrzędny. Możesz też zdefiniować katalogi w których PHP ma szukać plików za pomocą dyrektywy include_path. Ze względów bezpieczeństwa dobrze jest trzymać pliki wykonywalne poza główną strukturą katalogową. Nie wszystkie darmowe, zwłaszcza gorzej napisane skrypty (czy takie, których czas ewolucji wynosi już kilka lat – wiele zmieniło się w podejściu do zabezpieczeń przez ten okres) zapewniają rzeczoną funkcjonalność jednak jeżeli zamierzasz na razie pisać wszystko od podstaw, uwzględnij tę uwagę. Nasza struktura katalogów może mieć postać:

/www (lub htdocs) - główny katalog do umieszczania skryptów ustawiony w konfiguracji serwera
    /css - pliki CSS
    /images - pliki zdjęć
    /index.php
/pliki - nasze pliki wykonywalne
      /t/menu.php

Tym samym, przenosząc plik ‘menu.php’ do folderu ‘pliki’ pokażmy parserowi gdzie ma dokonywać przeszukania:

<?php
  ini_set('include_path', realpath(dirname(__FILE__)).'/../pliki/');
  $test = 'zmienna z pliku index.php';
  require_once('t/menu.php');
# bez include_path: ../pliki/t/menu.php
?>

W kodzie pojawiło się kilka ciekawych i zarazem nowych funkcji. ini_set() służy do zmiany niektórych wartości parametrów konfiguracyjnych z pliku php.ini. include_path jest jedną z nich. realpath() zwraca absolutną ścieżkę do pliku po usunięciu wszystkich dowiązań. Stosowane aby poprawić bezpieczeństwo. dirname() zwraca ścieżkę do katalogu bieżącego pliku, w kodzie odgadywanego za pomocą wbudowanej w PHP stałej magicznej FILE.

Iteracja po tablicach za pomocą ‘foreach’

Robiąc sobie krótki odpoczynek – do operacjach na plikach wrócimy jeszcze w kolejnym rozdziale – zajmijmy się na trochę obsługą typu tablicowego. Jak było napisane w poprzedniej części, tablice możemy definiować na kilka sposobów. Jednym z nieomówionych pozostała definicja klucz – wartość. Jest dość prosta:

<?php

$a = array(1, 2, 20);

# definicja tablicy klucz => wartosc
$b = array(
  "jeden" => 1,
  "dwa" => 2,
  "dwadziescia" => 20
);

/* Definicja ogólna 'foreach'
foreach (typ_tablicowy as $wartość) {
  operacje
}
*/

foreach ($a as $v) {
  echo $v.'<br>';
}

foreach ($b as $k => $v) {
  echo "$k - $v<br>";
}

Foreach jest bardzo wygodną strukturą kontrolną w PHP. Umożliwia szybką iterację po elementach tablicy, bez definicji liczników czy zerowania wskaźników. Jako ciekawostkę podam jeszcze dwie funkcje. Czasami, gdy chcemy tylko na szybko sprawdzić jakie elementy przechowuje zmienna, bardzo przydatne:

<?php

$a = array();
$a[0][0] = "a";
$a[0][1] = "b";
$a[1][0] = "c";
$a[1][1] = "d";

# bardziej czytelne będzie źródło strony
print_r($a);
var_dump($a);

Kwintesencja pętli, struktura ‘for’

O ile ‘foreach’ jest bardzo wygodne i niestety nie występuje we wszystkich językach programowania, o tyle pętla ‘for’ to “ziemi naszej sól”. Jest tak samo ważna jak ‘if-else’ ale też wymaga więcej zastanowienia przy pisaniu warunków. Jestem pewien że częściej będziesz korzystał z ‘foreach’ jako szybsze i łatwiejsze. Podstawowa składnia wygląda następująco:

<?php

for (wyrazenie1; wyrazenie2; wyrazenie3) {
# blok kodu
}

wyrazenie1 wykonywane jest raz, na początku pętli. Wyrażeniem może być też np reguła $i = 1, $j = 0 czyli ustawienie kilku zmiennych. wyrazenie2 następuje na początku każdej iteracji. Dopóki warunek jest prawdziwy pętla trwa, false oznacza zakończenie pętli. wyrazenie3 wykonywane jest po zakończeniu każdej iteracji. Zobaczmy jak wyglądałby wcześniej zapisany kod:

<?php

$a = array(1, 2, 20);

for ($i = 0; $i < count($a); $i++) { # kod fatalny wydajnościowo
  echo $a[$i].'<br>';
}

Pojawia się jednak jeden problem wydajnościowy i zarazem bardzo często popełniany błąd. Funkcja count (lub alias sizeof) zwracająca liczbę elementów tablicy, w powyższym przykładzie zlicza elementy tablicy $a trzykrotnie – podczas każdej iteracji. Może dla tych przykładowych danych nie jest to tak widoczne, ale dla większej porcji pobranej np z bazy danych mogłoby mieć znaczenie. Prawidłowo zapisany kod:

<?php

$a = array(1, 2, 20);

for ($i = 0, $size = count($a); $i < $size; ++$i) {
  echo $a[$i].'<br>';
}

W kodzie tym przed wykonaniem pętli sprawdzamy rozmiar naszej tablicy, iterację wykonujemy na wszystkich elementach tablicy ($i < $size) i dla każdej iteracji wypisujemy wartość konkretnego elementu tablicy. Jeżeli chciałbyś Czytelniku sprawdzić jaki wpływ mają pewne konstrukcje na wydajność zobacz stronę http://www.phpbench.com/. Dobra lektura na początek, umożliwia porównanie podstawowych konstrukcji języka. Warto jednak wspomnieć, że na dłuższą metę zyskiwanie 10ms na pętli w porównaniu do np. 2s zapytania na bazie nie będzie miało oczywiście sensu. W programowaniu istnieje taka zasada, że głębsze optymalizacje wykonuje się w momencie gdy są one potrzebne. Aktualnie najprostszy serwer dedykowany za niewielkie pieniądze jest w stanie podołać z wyświetleniem kilku milionów odsłon dziennie. Do tego dochodzą serwery cache, load-balancing itd. Z każdym rokiem ceny części spadają przy jednoczesnym wzroście wydajności. Trzeba tylko umieć znaleźć najlepsze rozwiązanie na dany czas i wiedzieć o dobrych praktykach w kodzie, które pozwolą nam wyeliminować podstawowe błędy. A na początek nauki, z całą pewnością będzie tych błędów sporo.

Wracając do wątku głównego. Uzupełnijmy jeszcze kod o drugą tablicę zdefiniowaną wcześniej:

<?php

$b = array(
  "jeden" => 1,
  "dwa" => 2,
  "dwadziescia" => 20
);

for (reset($b); list($key, $value) = each($b);) {
  echo "$key - $value<br>";
}

Za pomocą funkcji reset() ustawiamy wewnętrzny wskaźnik na początek tablicy, następnie każdemu elementowi tablicy przypisujemy parę klucz i wartość. Mam nadzieję że wyrobiłeś sobie już dobry nawyk zaglądania do dokumentacji gdy pojawia się coś nowego. Metodę tę raczej stosujemy z inną strukturą kontrolną, mianowicie ‘while’.

Iteracje za pomocą ‘while’ oraz ‘do-while’

Według dokumentacji PHP, pętla while jest najprostszą ze wszystkich. Występuje w dwóch odmianach:

<?php

while (wyrażenie jest prawdą) {
# wykonaj blok kodu
}

do {
# blok kodu
} while (wyrażenie jest prawdą);

Konstrukcja ‘while’ sprawdza na samym początku czy wyrażenie jest prawdą i jeśli tak wykonuje zawarty kod. Odwrotnie działa ‘do-while’ które sprawdza prawdziwość warunku po dokonaniu iteracji – nastąpi minimum jedno przejście przez pętle.

<?php

echo 'Odliczamy do zera: ';

$i = 9;

while ($i >= 0) {
  echo $i--;
}

‘echo $i—’ oznacza: wypisz wartość zmiennej $i i odejmij od niej 1. Zapis w drugą stronę ‘echo —$i’ mówiłby: pomniejsz aktualną wartość o 1 i dopiero wypisz wynik. Podobnie jak ma to miejsce z innymi konstrukcjami, PHP oferuje składnię alternatywną:

<?php

while (warunek_prawdziwy):
# blok kodu
endwhile;

Na marginesie. Weź pod uwagę iż w przypadku pętli łatwo wykonać “test CPU”.

<?php

while (true) {
  echo 1;
}

Z racji tego że warunek zawsze jest spełniony PHP wpadnie w nieskończoną pętlę. Skrypt będzie wykonywał się tak długo na ile zezwala czas ustawiony przez funkcję set_time_limit() lub zmienną konfiguracyjną max_execution_time.

Kolejna część kursu za nami. Tym razem zapoznaliśmy się z podstawowymi konstrukcjami języka, potrafimy iterować po elementach, sprawdzać warunki i dołączać zawartość innych plików. Poznajemy również niektóre dyrektywy konfiguracyjne (php.ini) oraz uczymy się wykorzystywać dokumentację jako podstawowe źródło wiedzy na temat języka. Część kolejna obejmować będzie formularze HTML oraz obsługę danych pochodzących od klienta (przeglądarki, roboty wyszukiwarek, automatyczne skrypty).

Komentarze

Nie ma jeszcze żadnych komentarzy do wyświetlenia. Może chcesz zostać pierwszą osobą która podzieli się swoją opinią?

Dodaj komentarz

*
Nazwa zostanie wyświetlona wraz z komentarzem. Możesz też utworzyć nowe konto w serwisie, dzięki czemu uzyskasz dodatkową funkcjonalność.
*
Akceptowana jest ograniczona składnia Textile. Wszystkie tagi HTML zostaną usunięte.