Trzy sposoby na to, żeby dane przetrwały do następnego żądania: plik na dysku serwera, ciasteczko w przeglądarce i sesja. Tryby fopen, setcookie przed treścią strony, session_start i koszyk w tablicy $_SESSION. Lekcja 12.7 kursu INF.03.
Bezstanowość (stateless, brak pamięci między żądaniami) to cecha PHP, przez którą każdy skrypt zaczyna pracę z pustymi zmiennymi i po wysłaniu odpowiedzi traci wszystko, co policzył. Żeby dane przetrwały do następnego kliknięcia, trzeba je odłożyć w jednym z trzech miejsc: do pliku na dysku serwera, do ciasteczka w przeglądarce albo do sesji. W części pisemnej wracają pytania o to, gdzie leżą dane sesji, czym ciasteczko różni się od sesji, po co jest session_start, dlaczego setcookie musi stać przed treścią strony i który tryb fopen dopisuje treść na końcu pliku, a informator CKE wymienia ciasteczka i sesje wprost na liście innych zadań praktycznych.
Pliki: otwarcie, zapis i tryby
Uchwyt pliku (file handle, wskaźnik otwartego pliku) to wartość zwracana przez fopen (OTWÓRZ PLIK), którą podaje się potem każdej funkcji czytającej albo piszącej. Praca z plikiem ma trzy kroki: otwórz, zapisz albo przeczytaj, zamknij. Drugi argument fopen to tryb i to on rozstrzyga, czy stara treść przetrwa oraz czy plik powstanie, gdy go nie ma.
r
tylko odczyt, plik musi istnieć, wskaźnik staje na początku
w
tylko zapis, kasuje całą starą treść, tworzy plik, gdy go nie ma
a
tylko zapis, dopisuje na końcu, starej treści nie rusza
x
tworzy nowy plik do zapisu, kończy się błędem, gdy plik już istnieje
r+
odczyt i zapis, plik musi istnieć, wskaźnik staje na początku
w+
odczyt i zapis, kasuje całą starą treść
a+
odczyt i zapis, dopisuje na końcu
Zapis prowadzi fwrite (ZAPISZ DO PLIKU), która bierze uchwyt i tekst; jej drugą nazwą jest fputs, w bankach pytań częstsza. Na koniec fclose (ZAMKNIJ PLIK) zwalnia plik. Rejestr zamówień ze sklepu komputerowego dopisuje się trybem a, bo nowe zamówienie ma dołączyć do poprzednich, a nie skasować całego pliku.
<?php
$plik = fopen('zamowienia.txt', 'a');
fwrite($plik, "Mysz;2;90\n");
fwrite($plik, "Klawiatura;1;120\n");
fclose($plik);
echo 'Zapisano 2 pozycje';
Po dwóch takich uruchomieniach plik zamowienia.txt ma cztery wiersze. Ten sam kod z trybem w zostawiłby tylko dwa ostatnie, bo w kasuje zawartość już przy otwarciu. Znak \n robi przejście do nowego wiersza i działa wyłącznie w cudzysłowie podwójnym. Ścieżka względna liczy się od katalogu skryptu, więc plik powstaje obok pliku PHP.
Pliki: odczyt, funkcje skrótowe i porządki
Odczyt ma dwa style. Pierwszy czyta plik wiersz po wierszu funkcją fgets (POBIERZ WIERSZ), a o końcu pliku mówi feof (KONIEC PLIKU); w pamięci trzyma naraz jeden wiersz, więc radzi sobie z dużymi plikami. Całą zawartość oddaje fread, której podaje się liczbę bajtów, zwykle wyliczoną przez filesize.
<?php
$plik = fopen('zamowienia.txt', 'r');
while (!feof($plik)) {
$wiersz = fgets($plik);
echo $wiersz . '<br>';
}
fclose($plik);
Mysz;2;90
Klawiatura;1;120
Wiersze rozdziela w oknie znacznik <br> doklejany w pętli, bo sam znak nowego wiersza z pliku przeglądarka zwija do spacji.
Pułapka: feof zgłasza koniec pliku dopiero po nieudanym odczycie, więc pętla wykonuje się o jeden raz za dużo i dokłada pusty wiersz. Bezpieczny warunek to while (($wiersz = fgets($plik)) !== false).
Drugi styl pomija uchwyt i zamyka operację w jednym wywołaniu. file_get_contents czyta plik do zmiennej tekstowej, file_put_contents zapisuje zmienną do pliku, a file czyta plik od razu do tablicy, w której jeden element to jeden wiersz. Licznik odwiedzin mieści się dzięki nim w kilku wierszach.
<?php
$ile = 0;
if (file_exists('licznik.txt')) {
$ile = (int) file_get_contents('licznik.txt');
}
$ile = $ile + 1;
file_put_contents('licznik.txt', $ile);
echo 'Odwiedziny: ' . $ile;
Przy pierwszym uruchomieniu pliku jeszcze nie ma, więc licznik startuje od zera i strona pokazuje
przeczytany tekst na liczbę. Domyślnie file_put_contents nadpisuje plik, a dopisywanie na końcu włącza trzeci argument FILE_APPEND.
fopen($n, $tryb)
otwiera plik i oddaje uchwyt
fwrite($p, $t)
zapisuje tekst przez uchwyt, druga nazwa fputs
fgets($p)
czyta jeden wiersz
fread($p, $ile)
czyta podaną liczbę bajtów
feof($p)
czy wskaźnik doszedł do końca pliku
file_get_contents($n)
czyta cały plik do zmiennej tekstowej
file_put_contents($n, $t)
zapisuje zmienną do pliku, nadpisując go
file($n)
czyta plik do tablicy, jeden wiersz to jeden element
file_exists($n)
czy plik albo katalog istnieje
filesize($n)
rozmiar pliku w bajtach
rename($s, $c)
zmienia nazwę pliku albo go przenosi
copy($s, $c)
kopiuje plik
mkdir($n)
tworzy katalog
is_writable($n)
czy da się do pliku zapisać
Dane tabelaryczne wygodniej trzymać w formacie CSV, gdzie jeden wiersz to jeden rekord, a pola rozdziela średnik albo przecinek. fputcsv zapisuje tablicę jako jeden wiersz, wstawiając separatory tam, gdzie trzeba, a fgetcsv czyta wiersz i oddaje gotową tablicę pól, więc nie trzeba rozbijać tekstu funkcją explode z lekcji o tablicach. Do włączenia kodu z innego pliku PHP nie służy żadna funkcja plikowa, tylko include albo require. Ciasteczka
Ciasteczko (cookie, ciastko) to mała porcja danych, którą serwer każe przeglądarce zapisać u siebie, a przeglądarka odsyła ją potem w nagłówku każdego kolejnego żądania do tej witryny. Nadaje się do rzeczy, które mają przetrwać zamknięcie przeglądarki i nie są tajne: motyw strony, język, zgoda na komunikat, znacznik pierwszej wizyty. Tworzy je setcookie (USTAW CIASTECZKO), z nazwą, wartością i czasem wygaśnięcia podanym jako liczba sekund od początku 1970 roku, czyli time() powiększone o żądany okres.
<?php
setcookie('motyw', 'ciemny', time() + 3600 * 24 * 30);
echo '<p>Motyw zapisany na 30 dni.</p>';
Motyw zapisany na 30 dni.
Rachunek w trzecim argumencie czyta się od lewej: 3600 sekund to godzina, razy 24 daje dobę, razy 30 daje trzydzieści dni. Pominięty trzeci argument daje ciasteczko sesyjne, które znika po zamknięciu przeglądarki. Dalsze argumenty to ścieżka, domena, wymuszenie HTTPS oraz flaga httponly, zabraniająca skryptom po stronie klienta czytać to ciasteczko.
Ciasteczko ustawione w tym żądaniu nie jest jeszcze widoczne w $_COOKIE, bo przeglądarka dostała je dopiero w odpowiedzi. Skrypt odczyta je od NASTĘPNEGO żądania, po odświeżeniu strony.
<?php
$motyw = 'jasny';
if (isset($_COOKIE['motyw'])) {
$motyw = $_COOKIE['motyw'];
}
echo '<body class="' . $motyw . '">';
Przy pierwszej wizycie warunek jest fałszywy i strona dostaje klasę jasny, po odświeżeniu klasę ciemny. Usunięcie ciasteczka polega na ustawieniu go jeszcze raz, z pustą wartością i datą z przeszłości: setcookie('motyw', '', time() - 3600). Przeglądarka widzi, że ciasteczko wygasło, i kasuje je u siebie. Jedno ciasteczko mieści około 4 kB, więc listy towarów ani wyniku zapytania się w nim nie zapisze. Nie trzymają się w nim także hasła ani znaczniki w rodzaju „zalogowany": plik z ciasteczkami leży na dysku użytkownika, kto ma do niego dostęp, ten może zmienić wartość i odesłać ją na serwer.
Pułapka: setcookie wysyła nagłówek HTTP, więc musi stać przed jakąkolwiek treścią. Nawet spacja przed <?php albo wcześniejszy echo dają Warning: Cannot modify header information - headers already sent, a ciasteczko się nie ustawia.
Sesje
Sesja (session, seans) to zestaw danych trzymany po stronie serwera i przypisany jednemu odwiedzającemu, do którego przeglądarka nosi wyłącznie identyfikator. Dane leżą w pliku na dysku serwera, w XAMPP domyślnie w katalogu C:\xampp\tmp, a nazwa pliku to sess_ plus identyfikator. Do przeglądarki jedzie samo ciasteczko PHPSESSID, więc użytkownik widzi losowy ciąg znaków, a nie zawartość koszyka. Dlatego wszystko, co ma być bezpieczne albo większe niż kilka bajtów, idzie do sesji, a nie do ciasteczka.
Ciasteczko trzyma dane na dysku klienta i dosyła je w każdym żądaniu, sesja trzyma dane w pliku na serwerze, a klient nosi tylko identyfikator PHPSESSID
Sesję otwiera albo wznawia session_start (ROZPOCZNIJ SESJĘ) i musi ona stać w KAŻDYM skrypcie, który sięga do sesji, zanim padnie pierwszy znak treści. Dopiero po niej istnieje tablica superglobalna $_SESSION, do której zapisuje się i z której czyta się dane jak ze zwykłej tablicy asocjacyjnej.
<?php
session_start();
$_SESSION['zalogowany'] = 'anna';
$_SESSION['koszyk'][] = 3;
$_SESSION['koszyk'][] = 1;
echo $_SESSION['zalogowany'] . ' ma ' . count($_SESSION['koszyk']) . ' pozycje';
Pojedynczy klucz kasuje unset($_SESSION['koszyk']), całą zawartość czyści przypisanie $_SESSION = [], a session_destroy (ZNISZCZ SESJĘ) usuwa z serwera plik sesji. Wylogowanie robi wszystkie trzy rzeczy naraz i przekierowuje na stronę główną.
<?php
session_start();
$_SESSION = [];
session_destroy();
header('Location: index.php');
exit;
Sesja wygasa też sama, po okresie bezczynności ustawionym w php.ini parametrem session.gc_maxlifetime, domyślnie po 1440 sekundach, czyli 24 minutach. PHPSESSID jest ciasteczkiem sesyjnym, więc znika przy zamknięciu przeglądarki i następna wizyta zaczyna się od pustej sesji.
Ciasteczko, sesja czy plik
Wybór sprowadza się do dwóch pytań: kto ma widzieć dane i jak długo mają żyć. Ciasteczko przeżywa zamknięcie przeglądarki, ale jest jawne i małe. Sesja jest ukryta i pojemna, tyle że kończy się razem z wizytą. Plik zostaje na serwerze na stałe i jest wspólny dla wszystkich odwiedzających.
Cecha
Ciasteczko
Sesja
Plik
gdzie leżą dane
na dysku klienta
w pliku na serwerze
na dysku serwera
kto może je zmienić
użytkownik
tylko serwer
tylko serwer
ile żyje
do daty z setcookie
do zamknięcia przeglądarki albo session_destroy
aż ktoś skasuje
rozmiar
do 4 kB
bez praktycznego limitu
bez praktycznego limitu
czyje dane
jednego odwiedzającego
jednego odwiedzającego
wspólne dla wszystkich
odczyt w PHP
$_COOKIE
$_SESSION
file_get_contents
typowe użycie
motyw, język, zapamiętanie wyboru
koszyk, zalogowany użytkownik
log, licznik, eksport danych
Trwałe dane jednego użytkownika, na przykład historia jego zamówień, nie mieszczą się w żadnej z tych trzech kolumn: idą do bazy danych, którą opisuje lekcja o PHP i bazie danych. Koszyk w sesji krok po kroku
Koszyk to lista identyfikatorów produktów trzymana w $_SESSION między kliknięciami. Plik koszyk.php robi trzy rzeczy: dokłada produkt wskazany w adresie parametrem id, czyści koszyk po kliknięciu odnośnika i wypisuje zawartość. Pierwsza część zmienia sesję, więc niczego jeszcze nie wypisuje.
<?php
session_start();
$cennik = [1 => 'Klawiatura', 2 => 'Mysz', 3 => 'Monitor 24'];
if (isset($_GET['czysc'])) {
unset($_SESSION['koszyk']);
}
if (isset($_GET['id']) && isset($cennik[$_GET['id']])) {
$_SESSION['koszyk'][] = (int) $_GET['id'];
}
Sprawdzenie isset($cennik[$_GET['id']]) odrzuca numery spoza cennika, bo parametr w adresie może wpisać każdy; skąd biorą się dane w $_GET, tłumaczy lekcja o formularzach w PHP. Druga część tego samego pliku wypisuje koszyk i dokłada odnośniki. $koszyk = $_SESSION['koszyk'] ?? [];
echo '<p>W koszyku: ' . count($koszyk) . '</p>';
echo '<ol>';
foreach ($koszyk as $id) {
echo '<li>' . $cennik[$id] . '</li>';
}
echo '</ol>';
echo '<a href="koszyk.php?id=1">Dodaj klawiaturę</a> ';
echo '<a href="koszyk.php?czysc=1">Wyczyść koszyk</a>';
W koszyku: 2
1. Mysz
2. Klawiatura
Dodaj klawiaturę Wyczyść koszyk
Wynik pokazuje stan po wejściu na koszyk.php?id=2, a potem na koszyk.php?id=1. Operator ?? podstawia pustą tablicę, gdy klucza koszyk jeszcze nie ma, więc pierwsza wizyta pokazuje zero pozycji zamiast ostrzeżenia o nieistniejącym kluczu. Kliknięcie odnośnika z czysc kasuje klucz i licznik wraca do zera, a sam koszyk przeżywa przejście na dowolną inną podstronę, o ile tamten skrypt też zaczyna się od session_start.
Na egzaminie
Pytania pisemne o ten materiał mają kilka stałych kształtów. Pierwszy to nazwa funkcji: ciasteczko tworzy setcookie, a nie createcookie ani addcookie, sesję otwiera session_start, zawartość pliku do zmiennej tekstowej czyta file_get_contents, a zapis do pliku prowadzi fputs albo fwrite. Drugi to tryb fopen: otwarcie istniejącego pliku tak, żeby wskaźnik stanął na końcu i dopisywanie nie kasowało treści, to fopen('lektury.txt', 'a'), a odczyt wiersza wymaga pary fopen z trybem 'r' oraz fgets. Trzeci to zawartość tablic globalnych: $_SESSION trzyma zmienne zarejestrowane w bieżącej sesji, dane logowania przechowuje się właśnie tam, a nie w $_COOKIE, i poprawny zapis zmiennej sesyjnej to $_SESSION['wyborID'] = 4;. Czwarty to rachunek czasu życia: setcookie('owoce', 'jablko', time() + 3600) daje godzinę, a time() + 60 * 60 * 24 dobę, czyli 24 godziny. Piąty to rozpoznanie kodu z ramki: skrypt z $_SESSION obsługuje sesję, skrypt z setcookie ciasteczka, a kod, który czyta liczbę z pliku, zwiększa ją i zapisuje z powrotem, zlicza odwiedziny strony. Szósty pyta o zastosowanie: komunikat pokazywany tylko przy pierwszej wizycie stawia setcookie, a witryna wielostronicowa, która ma pamiętać dane przy przechodzeniu między podstronami, potrzebuje session_start. Wraca też include('egzamin.php') jako jedyny sposób na włączenie kodu z innego pliku PHP: fopen ani fgets go nie uruchomią.
Pułapka: dane sesji leżą NA SERWERZE, a nie w przeglądarce. Przeglądarka dostaje sam identyfikator PHPSESSID i tylko on podróżuje w ciasteczku.
W zadaniu praktycznym polecenie brzmi zwykle „zapamiętaj liczbę odwiedzin w ciasteczku" albo „po poprawnym zalogowaniu zapisz nazwę użytkownika w sesji i wyświetlaj ją na każdej podstronie". Obie wersje robi się tak samo: pierwszy znak pliku to <?php, zaraz po nim session_start, a setcookie i header stoją przed jakimkolwiek echo i przed znacznikami HTML strony. Kolejność jest oceniana pośrednio, bo przy złej strona pokazuje ostrzeżenie zamiast zapisać dane, a rezultat „działanie witryny" tego nie przepuści. Plik z danymi powstaje w folderze zadania, tym samym, w którym leżą strony, a stronę otwiera się przez http://localhost/, nie z dysku: skrypt uruchomiony podwójnym kliknięciem nie wykona się w ogóle. Formularz, który dostarcza dane do zapisania, opisuje lekcja o formularzach HTML. Ściąga
fopen('dane.txt', 'r')
otwiera plik do odczytu, plik musi istnieć
fopen('dane.txt', 'w')
otwiera do zapisu i kasuje starą treść
fopen('dane.txt', 'a')
otwiera do zapisu i dopisuje na końcu
fwrite($plik, $tekst)
zapisuje tekst, druga nazwa fputs
fgets($plik)
czyta jeden wiersz
feof($plik)
czy wskaźnik doszedł do końca pliku
file_get_contents('a.txt')
czyta cały plik do zmiennej tekstowej
file_put_contents('a.txt', $t)
zapisuje zmienną do pliku
file('a.txt')
czyta plik do tablicy wierszy
file_exists('a.txt')
czy plik istnieje
unlink('a.txt')
kasuje plik
mkdir('dane')
tworzy katalog
setcookie('motyw', 'ciemny', time() + 3600)
ciasteczko ważne godzinę
setcookie('motyw', '', time() - 3600)
kasuje ciasteczko datą z przeszłości
$_COOKIE['motyw']
odczyt ciasteczka, od następnego żądania
session_start()
otwiera albo wznawia sesję, przed każdą treścią
$_SESSION['zalogowany'] = 'anna'
zapis danej do sesji
$_SESSION['koszyk'][] = 3
dokłada element do tablicy w sesji
unset($_SESSION['koszyk'])
kasuje jeden klucz sesji
session_destroy()
usuwa plik sesji z serwera
PHPSESSID
ciasteczko z identyfikatorem sesji
Pobierz ściągę PDF