Sesje
HTTP to protokół bezstanowy, jednak prawie każda aplikacja potrzebuje utrzymywać stan między żądaniami, na przykład zawartość koszyka. Dokładnie do tego służą sesje. Pokażemy:
- jak używać sesji
- jak zapobiec konfliktom nazw
- jak ustawić wygaśnięcie
Przy używaniu sesji każdy użytkownik otrzymuje unikalny identyfikator zwany session ID, przekazywany w cookie. Służy on jako klucz do danych sesji. W przeciwieństwie do cookies, które przechowywane są po stronie przeglądarki, dane sesji przechowywane są po stronie serwera.
Sesję konfigurujemy w konfiguracji; szczególnie ważny jest wybór czasu wygaśnięcia.
Zarządzaniem sesją zajmuje się obiekt Nette\Http\Session, do którego dostaniesz się, pozwalając
sobie go przekazać przez wstrzykiwanie
zależności. W presenterach wystarczy wywołać $session = $this->getSession().
Uruchomienie sesji
Domyślnie Nette uruchamia sesję automatycznie w momencie, gdy zaczniemy z niej czytać albo do niej pisać dane. Ręcznie
sesję uruchomisz za pomocą $session->start().
PHP przy uruchamianiu sesji wysyła nagłówki HTTP wpływające na cache (patrz session_cache_limiter), a ewentualnie także cookie z session ID. Dlatego zawsze trzeba uruchomić sesję przed wysłaniem jakiegokolwiek wyjścia do przeglądarki, w przeciwnym razie zostanie rzucony wyjątek. Jeśli więc wiesz, że w trakcie renderowania strony będzie używana sesja, uruchom ją wcześniej ręcznie, na przykład w presenterze.
W trybie deweloperskim Tracy uruchamia sesję, bo używa jej do wyświetlania pasków dla przekierowań i żądań AJAX w Tracy Barze.
Sekcje
W czystym PHP magazyn danych sesji zrealizowany jest jako tablica dostępna przez zmienną globalną $_SESSION.
Problem w tym, że aplikacje zwykle składają się z wielu niezależnych części, a jeśli wszystkie mają do dyspozycji tylko
jedną tablicę, prędzej czy później dojdzie do kolizji nazw.
Nette Framework rozwiązuje ten problem, dzieląc całą przestrzeń na sekcje (obiekty Nette\Http\SessionSection). Każda jednostka używa wtedy własnej sekcji o unikalnej nazwie i do żadnej kolizji nie może dojść.
Sekcję uzyskujemy z sesji:
$section = $session->getSection('unique name');
W presenterze wystarczy użyć getSession() z parametrem:
// $this to Presenter
$section = $this->getSession('unique name');
Istnienie sekcji można sprawdzić metodą $session->hasSection('unique name'). Listę nazw wszystkich
istniejących sekcji zwraca $session->getSectionNames().
Praca z samą sekcją jest potem bardzo łatwa za pomocą metod set(), get() i
remove():
// zapis zmiennej
$section->set('userName', 'john');
// odczyt zmiennej, zwraca null, jeśli nie istnieje
echo $section->get('userName');
// usunięcie zmiennej
$section->remove('userName');
Żeby uzyskać wszystkie zmienne z sekcji, możesz użyć pętli foreach:
foreach ($section as $key => $val) {
echo "$key = $val";
}
Jak ustawić wygaśnięcie
Wygaśnięcie można ustawić dla poszczególnych sekcji, a nawet dla poszczególnych zmiennych. Możemy pozwolić, żeby logowanie użytkownika wygasło po 20 minutach, a jednocześnie dalej pamiętać zawartość koszyka.
// sekcja wygasa po 20 minutach
$section->setExpiration('20 minutes');
Do ustawienia wygaśnięcia poszczególnych zmiennych służy trzeci parametr metody set():
// zmienna 'flash' wygasa po 30 sekundach
$section->set('flash', $message, '30 seconds');
Pamiętaj, że czas wygaśnięcia całej sesji (patrz konfiguracja sesji) musi być równy czasowi ustawionemu dla poszczególnych sekcji albo zmiennych, albo od niego dłuższy.
Do anulowania wcześniej ustawionego wygaśnięcia służy metoda removeExpiration(); żeby wyczyścić
wygaśnięcie konkretnej zmiennej, przekaż jej nazwę: removeExpiration('flash'). Do natychmiastowego usunięcia
całej sekcji służy metoda remove().
Zdarzenia $onStart, $onBeforeWrite
Obiekt Nette\Http\Session ma zdarzenia
$onStart i $onBeforeWrite, więc możesz dodać callbacki wywoływane po uruchomieniu sesji albo przed
jej zapisem na dysk i późniejszym zakończeniem.
$session->onBeforeWrite[] = function () {
// zapisujemy dane do sesji
$this->section->set('basket', $this->basket);
};
Zarządzanie sesją
Przegląd metod klasy Nette\Http\Session służących do zarządzania sesją:
start(): void
Uruchamia sesję.
isStarted(): bool
Czy sesja jest uruchomiona?
close(): void
Kończy sesję. Sesja kończy się automatycznie na końcu wykonywania skryptu.
destroy(): void
Kończy i usuwa sesję.
exists(): bool
Czy żądanie HTTP zawiera cookie z session ID?
regenerateId(): void
Generuje nowe losowe session ID. Dane pozostają zachowane.
getId(): string
Zwraca session ID.
Konfiguracja
Sesję konfigurujemy w konfiguracji. Jeśli piszesz aplikację, która nie używa kontenera DI, do konfiguracji użyj tych metod. Muszą być wywołane przed uruchomieniem sesji.
setName (string $name): static
Ustawia nazwę cookie, w którym przesyłane jest session ID. Standardowa nazwa to PHPSESSID. Przydaje się to,
gdy na tej samej witrynie uruchamiasz kilka różnych aplikacji.
getName(): string
Zwraca nazwę cookie, w którym przesyłane jest session ID.
setOptions (array $options): static
Konfiguruje sesję. Można ustawić wszystkie dyrektywy
sesji PHP (w formacie camelCase, np. zamiast session.save_path pisz savePath), a także readAndClose.
setExpiration (?string $expire): static
Ustawia czas nieaktywności, po którym sesja wygasa.
setCookieParameters (string $path, ?string $domain=null, ?bool $secure=null, SameSite|string|null $samesite=null): static
Ustawia parametry cookies. Domyślne wartości parametrów możesz zmienić w konfiguracji.
setSavePath (string $path): static
Ustawia katalog, w którym przechowywane są pliki sesji.
setHandler (\SessionHandlerInterface $handler): static
Ustawia własny handler, patrz dokumentacja PHP.
Bezpieczeństwo przede wszystkim
Serwer zakłada, że komunikuje się z tym samym użytkownikiem, dopóki żądaniom towarzyszy to samo session ID. Zadaniem mechanizmów bezpieczeństwa jest zapewnić, żeby tak rzeczywiście było i żeby identyfikatora nie dało się ukraść ani podmienić.
Nette Framework dlatego poprawnie konfiguruje dyrektywy PHP tak, żeby session ID przesyłane było wyłącznie w cookies, było niedostępne dla JavaScriptu i żeby ewentualne identyfikatory w URL były ignorowane. Poza tym w krytycznych momentach, jak logowanie użytkownika, generuje nowe session ID.
Do konfiguracji PHP używana jest funkcja ini_set, którą niestety niektórzy hostingodawcy
zabraniają. Jeśli tak jest u Twojego hostingodawcy, spróbuj się z nim umówić, żeby tę funkcję Ci udostępnił albo
przynajmniej odpowiednio skonfigurował serwer.