Podstawy PHP w pigułce: Zmienne oraz stałe
Zanim zaczniemy
Zakładam, że programujesz już w czymś innym — w JavaScripcie, Pythonie, C#, Javie, czy też może Go. Wiesz czym jest zmienna, funkcja, pętla czy też klasa i nie zamierzam w mojej serii tłumaczyć takich zagadnień. Pokażę Ci natomiast, jak elementy powszechnie występujące w innych językach programowania wyglądają i działają w PHP — oraz, przede wszystkim, gdzie PHP zachowuje się inaczej, niż podpowiada intuicja czy też doświadczenie z innego języka programowania.
Cały kurs dotyczy PHP w wersjach 8.0–8.3. W każdym przykładzie zakładam, że korzystamy z
declare(strict_types=1); na początku pliku, nawet jeśli jawnie tego nie napisałem. Za chwilę wyjaśnię, dlaczego ta
deklaracja nie powinna być opcjonalna w żadnym Twoim pliku.
W tej części omówimy sobie budowę pliku PHP, tryb ścisłego typowania, trzy funkcje przydatne do „podglądania” wartości zmiennych, zmienne, referencje oraz stałe. Czyli wszystko, co potrzebne, żeby ruszyć dalej z nawet najprostszym kodem PHP.
Plik PHP
Każdy plik PHP zaczyna się od znacznika otwierającego. Bez niego interpreter potraktuje zawartość jako zwykły tekst i w takiej też postaci wyśle do przeglądarki nic w niej nie zmieniając.
<?php // znacznik otwierającydeclare(strict_types=1);
echo 'Hello';
/* Dzięki znacznikowi w przeglądarce zobaczymy "Hello" a bez niego zobaczylibyśmy declare(strict_types=1); echo 'Hello';*/Znacznik zamykający: warty pominięcia
Znacznik ?> jest opcjonalny i w plikach zawierających wyłącznie PHP należy go pominąć. To nie jest kwestia
gustu — to zabezpieczenie przed konkretnym, wyjątkowo irytującym błędem.
<?phpdeclare(strict_types=1);
return ['db' => 'mysql'];?> ← ta pusta linia zostanie wysłana do przeglądarkiWszystko, co znajdzie się po ?> — spacja, tabulator, znak nowej linii — PHP wysyła do przeglądarki jako
treść strony. A razem z pierwszym znakiem treści leci komplet nagłówków HTTP, bo PHP dopisuje je automatycznie.
Gdy potem spróbujesz którykolwiek z nich ustawić — header(), setcookie(), session_start() — dostaniesz
błąd headers already sent.
Pomijanie
?>to jeden z tych nawyków, które warto wyrobić sobie na początku swojej przygody z PHP. Standard PSR-12 wprost tego wymaga.
Rodzaje znaczników
| Znacznik | Status |
|---|---|
<?php ... ?> |
Standardowy, zawsze dostępny |
<?= $x ?> |
Skrócony echo — dostępny zawsze od 5.4, bezpieczny w szablonach HTML |
<? ... ?> |
Krótki znacznik — zależny od dyrektywy short_open_tag w php.ini, unikaj |
<% %>, <script language="php"> |
Usunięte w PHP 7 |
Znacznika <?= używaj tylko w szablonach, tam gdzie mieszasz HTML z PHP:
<h1><?= htmlspecialchars($user->name) ?></h1>Komentarze i średniki
// jednolinijkowy# też jednolinijkowy (styl shellowy — działa, ale w PHP prawie nikt go nie używa)
/* wielolinijkowy komentarz */
/** DocBlock — czyta go IDE, generatory dokumentacji i refleksja (o niej w dalszych częściach) */Każda instrukcja w kodzie PHP kończy się średnikiem. Jedyny wyjątek to ostatnia instrukcja przed ?> — ale skoro ?> pomijamy,
wyjątek ten co do zasady nie powinien występować.
declare(strict_types=1) — najważniejsza linijka w pliku
Jeśli przychodzisz z języka typowanego statycznie, to jest miejsce, w którym PHP najbardziej Cię zaskoczy. Deklaracje typów w PHP wyglądają znajomo:
function addItems(int $count): int{ return $count;}Wygląda jak kontrakt. Domyślnie nim nie jest. Bez trybu ścisłego PHP nie odrzuca złego typu — po cichu go konwertuje.
// bez declare(strict_types=1)addItems('5'); // int(5) — string zamieniony na liczbęaddItems(5.9); // int(5) — UCIĘTE, bez ostrzeżeniaaddItems(true); // int(1)addItems('abc'); // TypeError — tego już nie da się skonwertowaćZwróć uwagę na drugą linijkę. 5.9 staje się 5. Bez ostrzeżenia, bez notice’a, bez śladu w logach. Jeżeli tą
liczbą była kwota, rabat albo waga przesyłki — właśnie straciłeś dane i dowiesz się o tym za trzy miesiące
z reklamacji.
Jedna linijka na początku pliku zamienia te konwersje w błędy:
<?phpdeclare(strict_types=1);
// teraz każde z tych wywołań rzuca TypeErroraddItems('5');addItems(5.9);addItems(true);| Konwersja | Tryb domyślny | strict_types=1 |
|---|---|---|
'5' → int |
Rzutowane na 5 |
TypeError |
5.9 → int |
Rzutowane na 5 — cicha utrata danych |
TypeError |
true → int |
Rzutowane na 1 |
TypeError |
5 → float |
Poszerzone do 5.0 |
Poszerzone do 5.0 — zawsze dozwolone |
| Obejmuje | parametry, typy zwracane, przypisania do typowanych właściwości | to samo |
Poszerzenie int → float jest jedynym wyjątkiem i działa również w trybie ścisłym. Jest bezstratne, więc nie ma
powodu, żeby go blokować.
Trzy rzeczy, o których musisz pamiętać
Po pierwsze: to ustawienie dotyczy pliku, nie projektu. Nie ma globalnego przełącznika. Nie dziedziczy się przez
require. Każdy nowy plik zaczynasz od tej linijki — bez wyjątków.
Po drugie: musi być pierwszą instrukcją w pliku. Przed nią może stać tylko <?php i ewentualnie deklaracja
namespace w formie blokowej. Cokolwiek innego to błąd krytyczny.
Po trzecie — i to jest naprawdę nieoczywiste — liczy się plik, w którym napisano wywołanie, nie ten, w którym zadeklarowano funkcję.
<?php// biblioteka bez trybu ścisłego
function calculate(int $value): int{ return $value * 2;}A teraz plik, który tę funkcję wywołuje:
<?phpdeclare(strict_types=1);
require 'vendor/legacy/Calculator.php';calculate('10'); // TypeError — mimo że biblioteka trybu ścisłego nie maTo działa na Twoją korzyść: włączając tryb ścisły u siebie, chronisz się także przed własnymi błędami w wywołaniach kodu zewnętrznego. Odwrotnie to nie działa — biblioteka z trybem ścisłym nie wymusi niczego na Twoim pliku bez niego.
Tryb ścisły obowiązuje również funkcje wbudowane.
strlen(123)przejdzie domyślnie, a podstrict_types=1rzuciTypeError.
Wypisywanie i podglądanie wartości
echo i print
echo 'a', 'b'; // przyjmuje WIELE argumentów, nic nie zwracaprint 'a'; // zwraca int(1), więc da się jej użyć w wyrażeniuW praktyce używaj echo. print ma sens tylko wtedy, gdy potrzebujesz wartości zwracanej — na przykład
w wyrażeniu warunkowym — co jest niezwykle rzadkim scenariuszem.
Żadne z nich nie nadaje się do sprawdzania zawartości zmiennych ani typu przypisanych do nich danych:
$arr = ['a' => 1];echo $arr; // "Array" + warning "Array to string conversion"Trzy funkcje do podglądania wartości
$v = ['a' => 1, 'b' => [2]];
print_r($v); // czytelnie dla człowieka, BEZ typów// Array// (// [a] => 1// [b] => Array// (// [0] => 2// )// )
var_dump($v); // typ + wartość + długość stringa + liczność tablicy// array(2) {// ["a"]=>// int(1)// ["b"]=>// array(1) {// [0]=>// int(2)// }// }
var_export($v); // poprawny KOD PHP, który można wkleić z powrotem do pliku// array (// 'a' => 1,// 'b' =>// array (// 0 => 2,// ),// )Na tablicach różnica wygląda kosmetycznie. Na wartościach skalarnych robi się dramatyczna — tylko var_dump
rozróżnia typy:
| Wartość | print_r |
var_dump |
var_export |
|---|---|---|---|
'hi' |
hi |
string(2) "hi" |
'hi' |
1.0 |
1 |
float(1) |
1.0 |
null |
(nic) | NULL |
NULL |
true |
1 |
bool(true) |
true |
false |
(nic) | bool(false) |
false |
print_r(false)iprint_r(null)nie wypisują niczego. W tym przypadku pusty ekran nie oznacza, że coś nie zadziałało — oznacza, że wartość zmiennej jest fałszywa. Fakt ten potrafi niejednego początkującego przysporzyć o ból głowy.
Kiedy czego używać:
| Pokazuje typy | Zwraca kod PHP | Potrafi zwrócić wynik zamiast go wypisać | |
|---|---|---|---|
print_r |
❌ | ❌ | print_r($v, true) |
var_dump |
✅ | ❌ | ❌ (potrzebne buforowanie wyjścia) |
var_export |
częściowo | ✅ | var_export($v, true) |
Ostatnia kolumna mówi, czy funkcja potrafi zwrócić podgląd jako zwykły string, zamiast od razu wypisać go na ekran. Przydaje się, gdy chcesz zapisać go do logu, a nie pokazać użytkownikowi.
Praktyczna reguła: var_dump() gdy debugujesz wartość, której nie ufasz, var_export() gdy chcesz zamienić wartość
w fixture do testu, print_r() gdy chcesz tylko rzucić okiem na kształt tablicy.
Zmienne
$name = 'Paweł'; // nazwy zaczynają się od $, są WRAŻLIWE na wielkość liter$Name = 'ktoś inny'; // to inna zmiennaZmiennych nie deklaruje się osobno — deklaracja jest w PHP nierozłączna z przypisaniem wartości. Pierwsze
przypisanie tworzy zmienną i nie ma tutaj odpowiednika let foo; z JavaScriptu, które tworzy niezdefiniowaną zmienna do której potem
możemy przypisać wartość.
Nazwy zmiennych
Po znaku $ nazwa musi zaczynać się od litery albo podkreślenia, a dalej mogą iść litery, cyfry i podkreślenia.
Cyfra na pierwszym miejscu to błąd składni.
$name; $_internal; $user2; // ✅$2fast; // ❌ błąd składniZarezerwowana jest dokładnie jedna nazwa: $this. Wewnątrz klasy wskazuje bieżący obiekt, a próba
przypisania do niej czegokolwiek to błąd krytyczny.
Zmienne zmiennych
PHP pozwala użyć wartości jednej zmiennej jako nazwy drugiej:
$name = 'Paweł';$varName = 'name';
echo $$varName; // 'Paweł' — to samo co $name$$varName = 'nowe'; // ustawia $name = 'nowe'W interpolacji stringów trzeba to jednoznacznie ograniczyć klamrami:
echo "{$$varName}"; // poprawnieecho "${$varName}"; // przestarzałe od 8.2, usunięte w PHP 9Wspominam o tym, bo oba te zapisy spotkasz w starym kodzie — a nie dlatego, że masz ich używać. Zmienne zmiennych łamią analizę statyczną, autouzupełnianie i refaktoryzację: ani IDE, ani żadne narzędzie nie ma jak zgadnąć, jaka nazwa powstanie dopiero w czasie działania programu.
Jeśli kusi Cię ta konstrukcja, prawie zawsze chodzi o dane, które lepiej trzymać w tablicy:
$values = ['name' => 'Paweł'];
echo $values['name']; // zamiast echo $$varName$values['name'] = 'nowe'; // zamiast $$varName = 'nowe'Zysk jest konkretny: nazwy kluczy widać w kodzie, więc da się je wyszukać, podmienić i sprawdzić.
Referencje
Domyślnie PHP przypisuje przez wartość. Wyjątkiem są obiekty, ale to temat na część ósmą — na razie zostańmy przy skalarach (czyli pojedynczych wartościach: liczbach, tekstach, wartościach logicznych).
$a = 1;$b = $a; // kopia WARTOŚCI — $b dostaje własne miejsce w pamięci$c = &$a; // przypisanie REFERENCJI — $c wskazuje na ten sam adres w pamięci co $a
$a = 2;
var_dump($b); // int(1) — kopia żyje własnym życiemvar_dump($c); // int(2) — ten sam adres co $aunset() na takiej zmiennej odłącza ją od tego adresu, ale nie usuwa samej wartości z pamięci — dopóki
wskazuje na nią jakakolwiek inna zmienna, wartość żyje dalej:
unset($c); // $c przestaje istnieć, ale $a nadal wskazuje na ten adres i ma wartość 2Referencja w PHP to nie to samo co wskaźnik z C. Nie jest wartością — nie policzysz na niej niczego, nie zapiszesz jej do tablicy „na potem” i nie zrobisz referencji do referencji. To po prostu druga nazwa dla tego samego adresu w pamięci i na tym koniec. W praktyce potrzebujesz jej rzadziej, niż się wydaje, a najłatwiej potknąć się o nią w pętli
foreach— wrócimy do tego w części czwartej.
Stałe
PHP ma dwa sposoby definiowania stałych i nie są one wymienne.
define('APP_NAME', 'deftcode'); // wywołanie funkcji — działa w czasie wykonaniaconst APP_VERSION = '1.0'; // rozstrzygane w czasie kompilacjiRóżnica sprowadza się do momentu, w którym stała powstaje. const przyjmuje wyrażenie stałe i nie może być
warunkowa ani wyliczona w locie. define() to zwykłe wywołanie funkcji, więc można je opakować w if albo oprzeć
na zmiennej:
if ($env === 'dev') { define('DEBUG', true); // ✅ działa}
if ($env === 'dev') { const DEBUG = true; // ❌ błąd — const nie może być warunkowa}define() |
const |
|
|---|---|---|
| Przestrzeń nazw | ignoruje bieżącą — stała należy do globalnej przestrzeni nazw | trafia do bieżącej przestrzeni nazw |
| Definicja warunkowa | ✅ | ❌ |
| Szybkość | wolniejsza (czas wykonania) | szybsza (czas kompilacji) |
| Wewnątrz klasy | ❌ | ✅ |
| Opcja ignorowania wielkości liter | usunięta w PHP 8 | nie dotyczy |
Pierwszy wiersz wymaga słowa wyjaśnienia. Przestrzeń nazw to przedrostek doklejany do nazw z danego pliku — mniej więcej to, czym dla plików jest katalog. Pozwala mieć w projekcie dwie klasy czy stałe o tej samej nazwie. Deklaruje się ją na górze pliku i od tej chwili obowiązuje wszystko, co niżej:
namespace App;
const VERSION = '1.0'; // powstaje App\VERSIONdefine('NAME', 'deftcode'); // powstaje NAME — w globalnej przestrzeni nazw
echo VERSION; // '1.0' — PHP znajduje App\VERSIONecho NAME; // 'deftcode' — App\NAME nie istnieje, więc PHP sięga do globalnejRóżnica robi się widoczna dopiero w innym pliku, bo decyduje o tym, jak stałą trzeba zaimportować:
namespace App\Model;
use const App\VERSION;
echo VERSION; // '1.0' — działa dzięki importowi powyżejecho \App\VERSION; // '1.0' — to samo, tylko pełną ścieżkąecho NAME; // 'deftcode' — globalna, więc żaden import nie jest potrzebnyOdczyt stałej PHP zaczyna od bieżącej przestrzeni nazw, a gdy jej tam nie znajdzie, sprawdza globalną — dlatego
NAME działa wszędzie bez importu. Wygoda ma jednak cenę: define() tworzy nazwę widoczną w całym projekcie,
więc dwie biblioteki chcące tej samej nazwy wejdą sobie w drogę. const zostaje w swojej przestrzeni i wędruje
razem z resztą kodu.
Tablice jako stałe są dozwolone w obu formach:
const ROLES = ['admin', 'editor']; // od PHP 5.6define('LIMITS', ['api' => 100]); // od PHP 7.0I dwie funkcje pomocnicze, które przydają się przy konfiguracji:
defined('DEBUG'); // bool — czy stała istniejeconstant('APP_NAME'); // odczyt po nazwie podanej jako stringPraktycznie: w kodzie aplikacyjnym używaj const (wewnątrz klas i przestrzeni nazw), a define() zostaw dla
plików bootstrapowych, gdzie coś naprawdę zależy od środowiska.
Stałe magiczne
To nie są prawdziwe stałe — interpreter podmienia je na wartość w czasie kompilacji, więc ta sama nazwa daje inny
wynik w zależności od miejsca użycia. Wszystkie zwracają string, poza __LINE__, które zwraca liczbę.
| Stała | Co zwraca | Przykładowa wartość |
|---|---|---|
__LINE__ |
numer linii, w której stoi | 42 |
__FILE__ |
pełną ścieżkę do pliku wraz z nazwą | /app/src/Order.php |
__DIR__ |
katalog pliku, bez ukośnika na końcu | /app/src |
__FUNCTION__ |
nazwę funkcji albo metody, w której stoi | getName |
__CLASS__ |
nazwę klasy razem z przestrzenią nazw | App\Model\User |
__METHOD__ |
nazwę metody poprzedzoną nazwą klasy | App\Model\User::getName |
__NAMESPACE__ |
bieżącą przestrzeń nazw | App\Model |
__DIR__ to ta, której będziesz używał najczęściej — do budowania ścieżek niezależnych od katalogu roboczego:
require __DIR__ . '/../vendor/autoload.php';::class zamiast stringów z nazwami klas
Osobno warto zapamiętać ::class. To nie jest stała magiczna z powyższej tabeli, ale rozwiązuje pokrewny problem —
zwraca pełną nazwę klasy jako string:
use App\Model\User;
echo User::class; // 'App\Model\User'echo $user::class; // to samo, na instancji (od PHP 8.0)Dlaczego to ważne: 'App\Model\User' wpisane ręcznie jest zwykłym tekstem. IDE go nie zrefaktoryzuje, analiza
statyczna nie sprawdzi, a literówka wyjdzie dopiero w czasie wykonania.
User::class działa inaczej, bo korzysta z importu use na górze pliku. Gdy przeniesiesz klasę do innej
przestrzeni nazw albo zmienisz jej nazwę, IDE poprawi ten jeden import, a User::class samo zacznie zwracać nową
nazwę. Ręcznie wpisany string zostanie taki, jaki był — i wskaże na klasę, której już nie ma.
Zostaje pytanie, po co w ogóle podawać nazwę klasy, skoro zwykle pracuje się na gotowych obiektach. Przydaje się wtedy, gdy przekazujesz dalej nie sam obiekt, tylko informację, jaką klasę ma coś utworzyć albo z czym ją porównać:
$class = User::class;$user = new $class(); // obiekt powstaje dopiero tutaj
if ($someObject::class === User::class) { // ten obiekt jest dokładnie klasy User}Co dalej
Masz już plik, tryb ścisły, narzędzia do podglądania zawartości zmiennych i sposoby na przechowywanie danych w zmiennych i stałych. Brakuje najważniejszego: co tak naprawdę siedzi w tych zmiennych.
W następnej części zajmiemy się systemem typów PHP — dlaczego 'abc' == 0 przestało być prawdą dopiero w PHP 8,
dlaczego (bool) '0' to false, a (bool) '0.0' to true, i której z tych rzeczy naprawdę trzeba się nauczyć
na pamięć.