Wywołaj session_start() na górze strony, a potem czytaj i zapisuj $_SESSION['key'] jak każdą tablicę: PHP zapisuje ją na serwerze i znajduje ponownie przy następnym żądaniu odwiedzającego. Dla małej wartości, którą ma trzymać przeglądarka, wywołaj setcookie('name', 'value', time() + 86400) i odczytuj ją przy kolejnych żądaniach z $_COOKIE['name'].
<?php
session_start(); // before any output
setcookie('theme', 'dark', time() + 60 * 60 * 24 * 30); // 30 days, also before output
$_SESSION['visits'] = ($_SESSION['visits'] ?? 0) + 1;
$theme = $_COOKIE['theme'] ?? 'light'; // set on an earlier visit
echo "You have opened this page {$_SESSION['visits']} times.";
Sesje i ciasteczka mają sens tylko przy kilku żądaniach HTTP z przeglądarki, więc przykłady, które ich wymagają, to zwykły kod. Logika wokół nich to zwykłe PHP na tablicach i ta część działa tutaj: w każdym bloku do uruchomienia $session zastępuje $_SESSION, a każde wywołanie to jedno żądanie.
Jak działa sesja w PHP
- Pierwsze
session_start()tworzy losowy identyfikator, wysyła go do przeglądarki w ciasteczku o nazwiePHPSESSIDi daje ci puste$_SESSION. - Na koniec żądania PHP zapisuje
$_SESSIONna serwerze (domyślnie w plikach, albo w Redis, albo w bazie danych). - Przy następnym żądaniu przeglądarka odsyła ciasteczko,
session_start()odczytuje identyfikator i wczytuje zapisaną tablicę.
Przeglądarka nigdy nie widzi danych, tylko identyfikator. Dlatego identyfikator zalogowanego użytkownika należy do $_SESSION, a nigdy do własnego ciasteczka: odwiedzający może zmienić swoje ciasteczka na dowolną wartość, ale nie może zmienić $_SESSION.
Logowanie i wylogowanie przez sesję
Logowanie to: sprawdź hasło, wygeneruj nowy identyfikator sesji, zapisz identyfikator użytkownika. Wylogowanie to odwrotność i ma trzy kroki.
<?php
// login.php
session_start();
$user = find_user_by_email($_POST['email'] ?? '');
if ($user && password_verify($_POST['password'] ?? '', $user['password_hash'])) {
session_regenerate_id(true); // new id after login: stops session fixation
$_SESSION['user_id'] = $user['id'];
header('Location: /dashboard.php');
exit;
}
$error = 'Wrong email or password.';
<?php
// any protected page
session_start();
if (!isset($_SESSION['user_id'])) {
header('Location: /login.php');
exit;
}
<?php
// logout.php
session_start();
$_SESSION = []; // 1. forget the data
$p = session_get_cookie_params(); // 2. delete the cookie
setcookie(session_name(), '', time() - 3600, $p['path'], $p['domain'], $p['secure'], $p['httponly']);
session_destroy(); // 3. delete the stored session
header('Location: /');
exit;
session_regenerate_id(true) ma większe znaczenie, niż się wydaje. Jeśli atakujący zdoła sprawić, że ofiara użyje znanego mu identyfikatora sesji (na przykład przez link), a identyfikator pozostanie ten sam po zalogowaniu, atakujący też jest zalogowany. Nowy identyfikator przy logowaniu zamyka tę lukę. Samo sprawdzanie haseł opisuje strona o password_hash.
Wygaszanie sesji po okresie bezczynności
Własne sprzątanie PHP (session.gc_maxlifetime, domyślnie 1440 sekund) uruchamia się losowo i nie jest niezawodnym limitem czasu. Zapisuj czas ostatniego żądania i sprawdzaj go sam. Blok używa stałych znaczników czasu, żebyś widział każdy przypadek; zmień $timeout albo czasy.
Przerwa od 09:05 do 09:16:40 to 700 sekund, poniżej limitu 15 minut, więc użytkownik zostaje zalogowany. Następna przerwa to 1000 sekund, więc sesja zostaje wyczyszczona.
Komunikaty flash
Komunikat flash jest ustawiany w jednym żądaniu i pokazywany raz w następnym, zwykle „Zapisano.” po przekierowaniu. Zapisz go w sesji, a potem odczytaj i usuń w jednym kroku:
Ustawianie ciasteczka przez setcookie
setcookie() przyjmuje nazwę, wartość i tablicę opcji (PHP 7.3 i nowsze). Ustawiaj te cztery opcje w każdym ciasteczku, chyba że masz powód, by tego nie robić:
<?php
setcookie('theme', 'dark', [
'expires' => time() + 60 * 60 * 24 * 30, // 30 days; 0 means "until the browser closes"
'path' => '/', // send it on every page, not just this folder
'secure' => true, // only over HTTPS
'httponly' => true, // JavaScript cannot read it
'samesite' => 'Lax', // not sent on most cross-site requests
]);
$theme = $_COOKIE['theme'] ?? 'light';
Dwie rzeczy zaskakują:
$_COOKIEnie zmienia się w tym samym żądaniu. Zawiera to, co przeglądarka wysłała z tym żądaniem. Ciasteczko ustawione teraz pojawi się w$_COOKIEprzy następnym.- Ciasteczka to nagłówki, więc
setcookie()musi wykonać się przed jakimkolwiek wyjściem, dokładnie jaksession_start()iheader().
Aby usunąć ciasteczko, ustaw je ponownie z tą samą nazwą i path oraz datą wygaśnięcia w przeszłości: setcookie('theme', '', ['expires' => time() - 3600, 'path' => '/']).
Tablica w ciasteczku
Wartość ciasteczka to string o długości do około 4 KB, więc dane strukturalne zapisuj jako JSON, a to, co wraca, traktuj jako niezaufane: odwiedzający może to zmienić. Nigdy nie wywołuj unserialize() na ciasteczku: może utworzyć obiekty twoich klas i uruchomić ich metody magiczne, a json_decode() zwraca tylko tablice, stringi, liczby, wartości logiczne, null albo stdClass.
Flagi opisuje strona o json_encode. Dla czegokolwiek większego niż kilka krótkich wartości trzymaj dane w sesji (albo w bazie danych), a w ciasteczku zapisuj tylko klucz.
Tokeny „zapamiętaj mnie”
Ciasteczko „zapamiętaj mnie” nie może zawierać identyfikatora użytkownika ani skrótu hasła. Zapisz w ciasteczku długi losowy token, w bazie danych tylko jego skrót, i porównuj przez hash_equals():
Wartości zmieniają się przy każdym uruchomieniu, bo random_bytes() jest losowe. Jeśli baza danych wycieknie, skróty są bezużyteczne bez ciasteczek, a hash_equals() porównuje w stałym czasie, więc sprawdzenie nie zdradza informacji przez czas wykonania.
Sesja vs ciasteczko: czego użyć
Sesja ($_SESSION) | Ciasteczko ($_COOKIE) | |
|---|---|---|
| Przechowywane | Na serwerze | W przeglądarce |
| Odwiedzający może odczytać lub zmienić | Nie (tylko identyfikator) | Tak |
| Rozmiar | Ograniczony twoim magazynem danych | Około 4 KB na ciasteczko |
| Trwa | Do zamknięcia przeglądarki lub wygaśnięcia sesji | Do czasu expires |
| Używaj do | Zalogowany użytkownik, koszyk, token CSRF, komunikaty flash | Motyw, język, zgoda na pliki cookie, token „zapamiętaj mnie” |
Częsty błąd: headers already sent
session_start(), setcookie() i header() wysyłają nagłówki HTTP, a nagłówki można wysłać tylko przed treścią. Jakiekolwiek wcześniejsze wyjście, nawet pusta linia przed <?php w dołączonym pliku, daje:
Warning: session_start(): Session cannot be started after headers have already been sent in /var/www/page.php on line 3
Warning: Cannot modify header information - headers already sent by (output started at /var/www/page.php:2) in /var/www/page.php on line 4
Drugi komunikat mówi, gdzie zaczęło się wyjście (page.php:2): przejdź do tej linii i usuń wyjście albo przenieś session_start() nad nią. W plikach zawierających tylko PHP pominięcie zamykającego ?> zapobiega temu, by białe znaki po nim stały się wyjściem.
Najczęściej zadawane pytania
Czym różni się sesja od ciasteczka w PHP?
Ciasteczko jest przechowywane w przeglądarce odwiedzającego i wysyłane z każdym żądaniem, więc odwiedzający może je odczytać i zmienić. Sesja przechowuje dane na serwerze; przeglądarka trzyma tylko losowy identyfikator sesji w ciasteczku (PHPSESSID). Wszystko, czemu trzeba ufać, na przykład identyfikator zalogowanego użytkownika, umieszczaj w $_SESSION.
Jak zniszczyć sesję przy wylogowaniu w PHP?
Opróżnij dane, usuń ciasteczko sesji i zniszcz zapisaną sesję: $_SESSION = [];, potem setcookie(session_name(), '', time() - 3600, '/'), a potem session_destroy();. Następnie przekieruj, żeby kolejna strona zaczęła od zera.
Jak usunąć ciasteczko w PHP?
Ustaw je ponownie z tą samą nazwą, ścieżką i domeną oraz datą wygaśnięcia w przeszłości: setcookie('theme', '', ['expires' => time() - 3600, 'path' => '/']);. Wykonaj też unset($_COOKIE['theme']), jeśli reszta bieżącego skryptu je odczytuje, bo $_COOKIE zmienia się dopiero przy następnym żądaniu.
Jak długo trwa sesja w PHP?
Domyślnie ciasteczko sesji trwa do zamknięcia przeglądarki (session.cookie_lifetime = 0), a serwer może usunąć dane sesji po 1440 sekundach (24 minutach) bez aktywności (session.gc_maxlifetime). Dla stałego limitu zapisuj w sesji czas ostatniego żądania i sprawdzaj go sam.
Dlaczego session_start() zgłasza headers already sent?
Identyfikator sesji wędruje w nagłówku ciasteczka, a nagłówki muszą zostać wysłane przed jakimkolwiek wyjściem. echo, HTML, a nawet pusta linia albo BOM przed <?php wysyłają wyjście jako pierwsze. Wywołuj session_start() na samej górze skryptu, zanim cokolwiek zostanie wypisane.