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().

Instalacja i wymagania

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.

wersja: 4.x