PHP: pliki, ciasteczka i sesje

INF.03 · teoria
autor: redakcja · 08.09.2026
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.

[ INF.03: PYTANIA Z TEGO DZIAŁU ]

Bez­sta­no­wość (stateless, brak pa­mię­ci między żą­da­nia­mi) to cecha PHP, przez którą każdy skrypt za­czy­na pracę z pu­sty­mi zmien­ny­mi i po wy­sła­niu od­po­wie­dzi traci wszyst­ko, co po­li­czył. Żeby dane prze­trwa­ły do na­stęp­ne­go klik­nię­cia, trzeba je odło­żyć w jednym z trzech miejsc: do pliku na dysku ser­we­ra, do cia­stecz­ka w prze­glą­dar­ce albo do sesji. W części pi­sem­nej wra­ca­ją py­ta­nia o to, gdzie leżą dane sesji, czym cia­stecz­ko różni się od sesji, po co jest session_start, dla­cze­go setcookie musi stać przed tre­ścią strony i który tryb fopen do­pi­su­je treść na końcu pliku, a in­for­ma­tor CKE wy­mie­nia cia­stecz­ka i sesje wprost na liście innych zadań prak­tycz­nych.

Pliki: otwar­cie, zapis i tryby

Uchwyt pliku (file handle, wskaź­nik otwar­te­go pliku) to war­tość zwra­ca­na przez fopen (OTWÓRZ PLIK), którą podaje się potem każdej funk­cji czy­ta­ją­cej albo pi­szą­cej. Praca z pli­kiem ma trzy kroki: otwórz, zapisz albo prze­czy­taj, za­mknij. Drugi ar­gu­ment fopen to tryb i to on roz­strzy­ga, czy stara treść prze­trwa oraz czy plik po­wsta­nie, gdy go nie ma.
Tryb
Co robi
r
tylko odczyt, plik musi ist­nieć, wskaź­nik staje na po­cząt­ku
w
tylko zapis, kasuje całą starą treść, tworzy plik, gdy go nie ma
a
tylko zapis, do­pi­su­je na końcu, starej treści nie rusza
x
tworzy nowy plik do zapisu, kończy się błędem, gdy plik już ist­nie­je
r+
odczyt i zapis, plik musi ist­nieć, wskaź­nik staje na po­cząt­ku
w+
odczyt i zapis, kasuje całą starą treść
a+
odczyt i zapis, do­pi­su­je na końcu
Zapis pro­wa­dzi fwrite (ZAPISZ DO PLIKU), która bierze uchwyt i tekst; jej drugą nazwą jest fputs, w ban­kach pytań częst­sza. Na koniec fclose (ZAMKNIJ PLIK) zwal­nia plik. Re­jestr za­mó­wień ze sklepu kom­pu­te­ro­we­go do­pi­su­je się trybem a, bo nowe za­mó­wie­nie ma do­łą­czyć do po­przed­nich, a nie ska­so­wać 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';
Zapisano 2 pozycje
Po dwóch takich uru­cho­mie­niach plik zamowienia.txt ma cztery wier­sze. Ten sam kod z trybem w zo­sta­wił­by tylko dwa ostat­nie, bo w kasuje za­war­tość już przy otwar­ciu. Znak \n robi przej­ście do nowego wier­sza i działa wy­łącz­nie w cu­dzy­sło­wie po­dwój­nym. Ścież­ka względ­na liczy się od ka­ta­lo­gu skryp­tu, więc plik po­wsta­je obok pliku PHP.

Pliki: odczyt, funk­cje skró­to­we i po­rząd­ki

Odczyt ma dwa style. Pierw­szy czyta plik wiersz po wier­szu funk­cją fgets (POBIERZ WIERSZ), a o końcu pliku mówi feof (KONIEC PLIKU); w pa­mię­ci trzyma naraz jeden wiersz, więc radzi sobie z dużymi pli­ka­mi. Całą za­war­tość oddaje fread, której podaje się liczbę bajtów, zwykle wy­li­czo­ną 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
Wier­sze roz­dzie­la w oknie znacz­nik <br> do­kle­ja­ny w pętli, bo sam znak nowego wier­sza z pliku prze­glą­dar­ka zwija do spacji.
Pu­łap­ka: feof zgła­sza koniec pliku do­pie­ro po nie­uda­nym od­czy­cie, więc pętla wy­ko­nu­je się o jeden raz za dużo i do­kła­da pusty wiersz. Bez­piecz­ny wa­ru­nek to while (($wiersz = fgets($plik)) !== false).
Drugi styl pomija uchwyt i zamyka ope­ra­cję w jednym wy­wo­ła­niu. file_get_contents czyta plik do zmien­nej tek­sto­wej, file_put_contents za­pi­su­je zmien­ną do pliku, a file czyta plik od razu do ta­bli­cy, w której jeden ele­ment to jeden wiersz. Licz­nik od­wie­dzin mieści się dzięki nim w kilku wier­szach.
<?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;
Odwiedziny: 1
Przy pierw­szym uru­cho­mie­niu pliku jesz­cze nie ma, więc licz­nik star­tu­je od zera i strona po­ka­zu­je
  • 1.
    Każde od­świe­że­nie pod­no­si liczbę o jeden, bo war­tość wraca z dysku. Rzu­to­wa­nie (int) za­mie­nia
prze­czy­ta­ny tekst na liczbę. Do­myśl­nie file_put_contents nad­pi­su­je plik, a do­pi­sy­wa­nie na końcu włącza trzeci ar­gu­ment FILE_APPEND.
Funk­cja
Co robi
fopen($n, $tryb)
otwie­ra plik i oddaje uchwyt
fwrite($p, $t)
za­pi­su­je tekst przez uchwyt, druga nazwa fputs
fgets($p)
czyta jeden wiersz
fread($p, $ile)
czyta podaną liczbę bajtów
feof($p)
czy wskaź­nik do­szedł do końca pliku
fclose($p)
zamyka plik
file_get_contents($n)
czyta cały plik do zmien­nej tek­sto­wej
file_put_contents($n, $t)
za­pi­su­je zmien­ną do pliku, nad­pi­su­jąc go
file($n)
czyta plik do ta­bli­cy, jeden wiersz to jeden ele­ment
file_exists($n)
czy plik albo ka­ta­log ist­nie­je
filesize($n)
roz­miar pliku w baj­tach
unlink($n)
kasuje plik
rename($s, $c)
zmie­nia nazwę pliku albo go prze­no­si
copy($s, $c)
ko­piu­je plik
mkdir($n)
tworzy ka­ta­log
is_writable($n)
czy da się do pliku za­pi­sać
Dane ta­be­la­rycz­ne wy­god­niej trzy­mać w for­ma­cie CSV, gdzie jeden wiersz to jeden rekord, a pola roz­dzie­la śred­nik albo prze­ci­nek. fputcsv za­pi­su­je ta­bli­cę jako jeden wiersz, wsta­wia­jąc se­pa­ra­to­ry tam, gdzie trzeba, a fgetcsv czyta wiersz i oddaje gotową ta­bli­cę pól, więc nie trzeba roz­bi­jać tekstu funk­cją explode z lekcji o ta­bli­cach. Do włą­cze­nia kodu z innego pliku PHP nie służy żadna funk­cja pli­ko­wa, tylko include albo require.

Cia­stecz­ka

Cia­stecz­ko (cookie, ciast­ko) to mała porcja danych, którą serwer każe prze­glą­dar­ce za­pi­sać u siebie, a prze­glą­dar­ka odsyła ją potem w na­głów­ku każ­de­go ko­lej­ne­go żą­da­nia do tej wi­try­ny. Nadaje się do rzeczy, które mają prze­trwać za­mknię­cie prze­glą­dar­ki i nie są tajne: motyw strony, język, zgoda na ko­mu­ni­kat, znacz­nik pierw­szej wizyty. Tworzy je setcookie (USTAW CIASTECZKO), z nazwą, war­to­ścią i czasem wy­ga­śnię­cia po­da­nym jako liczba sekund od po­cząt­ku 1970 roku, czyli time() po­więk­szo­ne o żądany okres.
<?php setcookie('motyw', 'ciemny', time() + 3600 * 24 * 30); echo '<p>Motyw zapisany na 30 dni.</p>';
Motyw zapisany na 30 dni.
Ra­chu­nek w trze­cim ar­gu­men­cie czyta się od lewej: 3600 sekund to go­dzi­na, razy 24 daje dobę, razy 30 daje trzy­dzie­ści dni. Po­mi­nię­ty trzeci ar­gu­ment daje cia­stecz­ko se­syj­ne, które znika po za­mknię­ciu prze­glą­dar­ki. Dalsze ar­gu­men­ty to ścież­ka, domena, wy­mu­sze­nie HTTPS oraz flaga httponly, za­bra­nia­ją­ca skryp­tom po stro­nie klien­ta czytać to cia­stecz­ko.
Cia­stecz­ko usta­wio­ne w tym żą­da­niu nie jest jesz­cze wi­docz­ne w $_COOKIE, bo prze­glą­dar­ka do­sta­ła je do­pie­ro w od­po­wie­dzi. Skrypt od­czy­ta je od NASTĘPNEGO żą­da­nia, po od­świe­że­niu strony.
<?php $motyw = 'jasny'; if (isset($_COOKIE['motyw'])) { $motyw = $_COOKIE['motyw']; } echo '<body class="' . $motyw . '">';
<body class="ciemny">
Przy pierw­szej wi­zy­cie wa­ru­nek jest fał­szy­wy i strona do­sta­je klasę jasny, po od­świe­że­niu klasę ciemny. Usu­nię­cie cia­stecz­ka polega na usta­wie­niu go jesz­cze raz, z pustą war­to­ścią i datą z prze­szło­ści: setcookie('motyw', '', time() - 3600). Prze­glą­dar­ka widzi, że cia­stecz­ko wy­ga­sło, i kasuje je u siebie. Jedno cia­stecz­ko mieści około 4 kB, więc listy to­wa­rów ani wyniku za­py­ta­nia się w nim nie za­pi­sze. Nie trzy­ma­ją się w nim także hasła ani znacz­ni­ki w ro­dza­ju „za­lo­go­wa­ny": plik z cia­stecz­ka­mi leży na dysku użyt­kow­ni­ka, kto ma do niego dostęp, ten może zmie­nić war­tość i ode­słać ją na serwer.
Pu­łap­ka: setcookie wysyła na­głó­wek HTTP, więc musi stać przed ja­ką­kol­wiek tre­ścią. Nawet spacja przed <?php albo wcze­śniej­szy echo dają Warning: Cannot modify header information - headers already sent, a cia­stecz­ko się nie usta­wia.

Sesje

Sesja (session, seans) to zestaw danych trzy­ma­ny po stro­nie ser­we­ra i przy­pi­sa­ny jed­ne­mu od­wie­dza­ją­ce­mu, do któ­re­go prze­glą­dar­ka nosi wy­łącz­nie iden­ty­fi­ka­tor. Dane leżą w pliku na dysku ser­we­ra, w XAMPP do­myśl­nie w ka­ta­lo­gu C:\xampp\tmp, a nazwa pliku to sess_ plus iden­ty­fi­ka­tor. Do prze­glą­dar­ki jedzie samo cia­stecz­ko PHPSESSID, więc użyt­kow­nik widzi losowy ciąg znaków, a nie za­war­tość ko­szy­ka. Dla­te­go wszyst­ko, co ma być bez­piecz­ne albo więk­sze niż kilka bajtów, idzie do sesji, a nie do cia­stecz­ka.
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ę otwie­ra albo wzna­wia session_start (ROZPOCZNIJ SESJĘ) i musi ona stać w KAŻDYM skryp­cie, który sięga do sesji, zanim padnie pierw­szy znak treści. Do­pie­ro po niej ist­nie­je ta­bli­ca su­per­glo­bal­na $_SESSION, do której za­pi­su­je się i z której czyta się dane jak ze zwy­kłej ta­bli­cy aso­cja­cyj­nej.
<?php session_start(); $_SESSION['zalogowany'] = 'anna'; $_SESSION['koszyk'][] = 3; $_SESSION['koszyk'][] = 1; echo $_SESSION['zalogowany'] . ' ma ' . count($_SESSION['koszyk']) . ' pozycje';
anna ma 2 pozycje
Po­je­dyn­czy klucz kasuje unset($_SESSION['koszyk']), całą za­war­tość czyści przy­pi­sa­nie $_SESSION = [], a session_destroy (ZNISZCZ SESJĘ) usuwa z ser­we­ra plik sesji. Wy­lo­go­wa­nie robi wszyst­kie trzy rzeczy naraz i prze­kie­ro­wu­je na stronę główną.
<?php session_start(); $_SESSION = []; session_destroy(); header('Location: index.php'); exit;
Sesja wygasa też sama, po okre­sie bez­czyn­no­ści usta­wio­nym w php.ini pa­ra­me­trem session.gc_maxlifetime, do­myśl­nie po 1440 se­kun­dach, czyli 24 mi­nu­tach. PHPSESSID jest cia­stecz­kiem se­syj­nym, więc znika przy za­mknię­ciu prze­glą­dar­ki i na­stęp­na wizyta za­czy­na się od pustej sesji.

Cia­stecz­ko, sesja czy plik

Wybór spro­wa­dza się do dwóch pytań: kto ma wi­dzieć dane i jak długo mają żyć. Cia­stecz­ko prze­ży­wa za­mknię­cie prze­glą­dar­ki, ale jest jawne i małe. Sesja jest ukryta i po­jem­na, tyle że kończy się razem z wizytą. Plik zo­sta­je na ser­we­rze na stałe i jest wspól­ny dla wszyst­kich od­wie­dza­ją­cych.
Cecha
Cia­stecz­ko
Sesja
Plik
gdzie leżą dane
na dysku klien­ta
w pliku na ser­we­rze
na dysku ser­we­ra
kto może je zmie­nić
użyt­kow­nik
tylko serwer
tylko serwer
ile żyje
do daty z setcookie
do za­mknię­cia prze­glą­dar­ki albo session_destroy
aż ktoś ska­su­je
roz­miar
do 4 kB
bez prak­tycz­ne­go limitu
bez prak­tycz­ne­go limitu
czyje dane
jed­ne­go od­wie­dza­ją­ce­go
jed­ne­go od­wie­dza­ją­ce­go
wspól­ne dla wszyst­kich
odczyt w PHP
$_COOKIE
$_SESSION
file_get_contents
typowe użycie
motyw, język, za­pa­mię­ta­nie wyboru
koszyk, za­lo­go­wa­ny użyt­kow­nik
log, licz­nik, eks­port danych
Trwałe dane jed­ne­go użyt­kow­ni­ka, na przy­kład hi­sto­ria jego za­mó­wień, nie miesz­czą się w żadnej z tych trzech kolumn: idą do bazy danych, którą opi­su­je lekcja o PHP i bazie danych.

Koszyk w sesji krok po kroku

Koszyk to lista iden­ty­fi­ka­to­rów pro­duk­tów trzy­ma­na w $_SESSION między klik­nię­cia­mi. Plik koszyk.php robi trzy rzeczy: do­kła­da pro­dukt wska­za­ny w ad­re­sie pa­ra­me­trem id, czyści koszyk po klik­nię­ciu od­no­śni­ka i wy­pi­su­je za­war­tość. Pierw­sza część zmie­nia sesję, więc ni­cze­go jesz­cze nie wy­pi­su­je.
<?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']; }
Spraw­dze­nie isset($cennik[$_GET['id']]) od­rzu­ca numery spoza cen­ni­ka, bo pa­ra­metr w ad­re­sie może wpisać każdy; skąd biorą się dane w $_GET, tłu­ma­czy lekcja o for­mu­la­rzach w PHP. Druga część tego samego pliku wy­pi­su­je koszyk i do­kła­da od­no­śni­ki.
$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 po­ka­zu­je stan po wej­ściu na koszyk.php?id=2, a potem na koszyk.php?id=1. Ope­ra­tor ?? pod­sta­wia pustą ta­bli­cę, gdy klucza koszyk jesz­cze nie ma, więc pierw­sza wizyta po­ka­zu­je zero po­zy­cji za­miast ostrze­że­nia o nie­ist­nie­ją­cym kluczu. Klik­nię­cie od­no­śni­ka z czysc kasuje klucz i licz­nik wraca do zera, a sam koszyk prze­ży­wa przej­ście na do­wol­ną inną pod­stro­nę, o ile tamten skrypt też za­czy­na się od session_start.

Na eg­za­mi­nie

Py­ta­nia pi­sem­ne o ten ma­te­riał mają kilka sta­łych kształ­tów. Pierw­szy to nazwa funk­cji: cia­stecz­ko tworzy setcookie, a nie createcookie ani addcookie, sesję otwie­ra session_start, za­war­tość pliku do zmien­nej tek­sto­wej czyta file_get_contents, a zapis do pliku pro­wa­dzi fputs albo fwrite. Drugi to tryb fopen: otwar­cie ist­nie­ją­ce­go pliku tak, żeby wskaź­nik stanął na końcu i do­pi­sy­wa­nie nie ka­so­wa­ło treści, to fopen('lektury.txt', 'a'), a odczyt wier­sza wymaga pary fopen z trybem 'r' oraz fgets. Trzeci to za­war­tość tablic glo­bal­nych: $_SESSION trzyma zmien­ne za­re­je­stro­wa­ne w bie­żą­cej sesji, dane lo­go­wa­nia prze­cho­wu­je się wła­śnie tam, a nie w $_COOKIE, i po­praw­ny zapis zmien­nej se­syj­nej to $_SESSION['wyborID'] = 4;. Czwar­ty to ra­chu­nek czasu życia: setcookie('owoce', 'jablko', time() + 3600) daje go­dzi­nę, a time() + 60 * 60 * 24 dobę, czyli 24 go­dzi­ny. Piąty to roz­po­zna­nie kodu z ramki: skrypt z $_SESSION ob­słu­gu­je sesję, skrypt z setcookie cia­stecz­ka, a kod, który czyta liczbę z pliku, zwięk­sza ją i za­pi­su­je z po­wro­tem, zlicza od­wie­dzi­ny strony. Szósty pyta o za­sto­so­wa­nie: ko­mu­ni­kat po­ka­zy­wa­ny tylko przy pierw­szej wi­zy­cie stawia setcookie, a wi­try­na wie­lo­stro­ni­co­wa, która ma pa­mię­tać dane przy prze­cho­dze­niu między pod­stro­na­mi, po­trze­bu­je session_start. Wraca też include('egzamin.php') jako jedyny sposób na włą­cze­nie kodu z innego pliku PHP: fopen ani fgets go nie uru­cho­mią.
Pu­łap­ka: dane sesji leżą NA SERWERZE, a nie w prze­glą­dar­ce. Prze­glą­dar­ka do­sta­je sam iden­ty­fi­ka­tor PHPSESSID i tylko on po­dró­żu­je w cia­stecz­ku.
W za­da­niu prak­tycz­nym po­le­ce­nie brzmi zwykle „za­pa­mię­taj liczbę od­wie­dzin w cia­stecz­ku" albo „po po­praw­nym za­lo­go­wa­niu zapisz nazwę użyt­kow­ni­ka w sesji i wy­świe­tlaj ją na każdej pod­stro­nie". Obie wersje robi się tak samo: pierw­szy znak pliku to <?php, zaraz po nim session_start, a setcookie i header stoją przed ja­kim­kol­wiek echo i przed znacz­ni­ka­mi HTML strony. Ko­lej­ność jest oce­nia­na po­śred­nio, bo przy złej strona po­ka­zu­je ostrze­że­nie za­miast za­pi­sać dane, a re­zul­tat „dzia­ła­nie wi­try­ny" tego nie prze­pu­ści. Plik z danymi po­wsta­je w fol­de­rze za­da­nia, tym samym, w którym leżą strony, a stronę otwie­ra się przez http://localhost/, nie z dysku: skrypt uru­cho­mio­ny po­dwój­nym klik­nię­ciem nie wykona się w ogóle. For­mu­larz, który do­star­cza dane do za­pi­sa­nia, opi­su­je lekcja o for­mu­la­rzach HTML.

Ściąga

Zapis
Znaczy
fopen('dane.txt', 'r')
otwie­ra plik do od­czy­tu, plik musi ist­nieć
fopen('dane.txt', 'w')
otwie­ra do zapisu i kasuje starą treść
fopen('dane.txt', 'a')
otwie­ra do zapisu i do­pi­su­je na końcu
fwrite($plik, $tekst)
za­pi­su­je tekst, druga nazwa fputs
fgets($plik)
czyta jeden wiersz
feof($plik)
czy wskaź­nik do­szedł do końca pliku
fclose($plik)
zamyka plik
file_get_contents('a.txt')
czyta cały plik do zmien­nej tek­sto­wej
file_put_contents('a.txt', $t)
za­pi­su­je zmien­ną do pliku
file('a.txt')
czyta plik do ta­bli­cy wier­szy
file_exists('a.txt')
czy plik ist­nie­je
unlink('a.txt')
kasuje plik
mkdir('dane')
tworzy ka­ta­log
setcookie('motyw', 'ciemny', time() + 3600)
cia­stecz­ko ważne go­dzi­nę
setcookie('motyw', '', time() - 3600)
kasuje cia­stecz­ko datą z prze­szło­ści
$_COOKIE['motyw']
odczyt cia­stecz­ka, od na­stęp­ne­go żą­da­nia
session_start()
otwie­ra albo wzna­wia sesję, przed każdą tre­ścią
$_SESSION['zalogowany'] = 'anna'
zapis danej do sesji
$_SESSION['koszyk'][] = 3
do­kła­da ele­ment do ta­bli­cy w sesji
unset($_SESSION['koszyk'])
kasuje jeden klucz sesji
session_destroy()
usuwa plik sesji z ser­we­ra
PHPSESSID
cia­stecz­ko z iden­ty­fi­ka­to­rem sesji
Pobierz ściągę PDF
ZAWODNIK
egzamin zawodowy INF.02 · INF.03 · INF.04
Serwer Discord
Egzaminy zawodowe IT/Matury/Studia
@yastor
© 2026 Zawodnik · by Yastor
Arkusze i zasady oceniania pochodzą z CKE. Serwis nie jest powiązany z Centralną Komisją Egzaminacyjną.