Nette Documentation Preview

syntax
Sessions
********

<div class=perex>

HTTP je bezestavový protokol, nicméně takřka každá aplikace potřebuje stav mezi požadavky uchovávat, například obsah nákupního košíku. Právě k tomu slouží session neboli relace. Ukážeme si,

- jak používat sessions
- jak předejít jmenným konfliktům
- jak nastavit expiraci

</div>

Při použití sessions každý uživatel obdrží jedinečný identifikátor nazývaný session ID, který se předává v cookie. Ten slouží jako klíč k session datům. Na rozdíl od cookies, které se uchovávají na straně prohlížeče, jsou data v session uchovávána na straně serveru.

Session nastavujeme v [konfiguraci |configuration#session], důležitá je zejména volba doby exipirace.

Správu session má na starosti objekt [api:Nette\Http\Session], ke kterému se dostanete tak, že si jej necháte předat pomocí [dependency injection |dependency-injection:passing-dependencies]. V presenterech stačí jen zavolat `$session = $this->getSession()`.

→ [Instalace a požadavky |@home#Instalace]


Start session
=============

Nette ve výchozím nastavení automaticky zahájí session automaticky ve chvíli, když z ní začneme číst nebo do ní zapisovat data. Ručně se session zahájí pomocí `$session->start()`.

PHP odešle při spuštění session HTTP hlavičky ovlivňující kešování, viz [php:session_cache_limiter], a případně i cookie se session ID. Proto je nutné vždy session nastartovat ještě před odesláním jakéhokoliv výstupu do prohlížeče, jinak dojde k vyhození výjimky. Pokud tedy víte, že v průběhu vykreslování stránky se bude používat session, nastartujte ji ručně předtím, třeba v presenteru.

Ve vývojářském režimu startuje session Tracy, protože ji používá pro zobrazování pruhů s přesměrováním a AJAXovými požadavky v Tracy Baru.


Sekce
=====

V čistém PHP je datové úložiště session realizováno jako pole dostupné přes globální proměnnou `$_SESSION`. Problém je v tom, že aplikace se běžně skládají z celé řady vzájemně nezávislých částí a pokud všechny mají k dispozici jen jedno pole, dříve nebo později dojde ke kolizi názvů.

Nette Framework problém řeší tak, že celý prostor rozděluje na sekce (objekty [api:Nette\Http\SessionSection]). Každá jednotka pak používá svou sekci s unikátním názvem a k žádné kolizi již dojít nemůže.

Sekci získáme ze session:

```php
$section = $session->getSection('unikatni nazev');
```

V presenteru stačí použít `getSession()` s parametrem:

```php
// $this je Presenter
$section = $this->getSession('unikatni nazev');
```

Ověřit existenci sekce lze metodou `$session->hasSection('unikatni nazev')`.

Se samotnou sekcí se pak pracuje velmi snadno pomocí metod `set()`, `get()` a `remove()`: (od "nette/http v3.1.5":[https://github.com/nette/http/releases/tag/v3.1.5])

```php
// zápis proměnné
$section->set('userName', 'franta');

// čtení proměnné, vrátí null pokud neexistuje
echo $section->get('userName');

// zrušení proměnné
$section->remove('userName');
```

Před verzí 3.1.5 se používaly magické proměnné nebo ArrayAccess, které budou ve verzi 4 označeny za zastaralé.

```php
$section->userName = 'franta'; // nebo $section['userName'] = 'franta';

echo $section->userName; // nebo echo $section['userName'];

unset($section->userName);  // unset($section['userName']);
```

Pro získání všech proměnných ze sekce je možné použít cyklus `foreach`:

```php
foreach ($section as $key => $val) {
	echo "$key = $val";
}
```


Nastavení expirace
------------------

Pro jednotlivé sekce nebo dokonce jednotlivé proměnné je možné nastavit expiraci. Můžeme tak nechat vypršet přihlášení uživatele za 20 minut, ale přitom si nadále pamatovat obsah košíku.

```php
// sekce vyexpiruje po 20 minutách
$section->setExpiration('20 minutes');
```

Pro nastavení expirace jednotlivých proměnných slouží třetí parametr metody `set()`: (od "nette/http v3.1.5":[https://github.com/nette/http/releases/tag/v3.1.5])

```php
// proměnná 'flash' vyexpiruje už po 30 sekundách
$section->set('flash', $message, '30 seconds');
```

Před verzí 3.1.5 se použivala metoda `setExpiration()`:

```php
$section->setExpiration('30 seconds', 'flash');
```

.[note]
Nezapomeňte, že doba expirace celé session (viz [konfigurace session|configuration#session]) musí být stejná nebo vyšší než doba nastavená u jednotlivých sekcí či proměnných.

Zrušení dříve nastavené expirace docílíme metodou `removeExpiration()`. Okamžité zrušení celé sekce zajistí metoda `remove()`.


Události $onStart, $onBeforeWrite .{data-version:v3.1.5}
--------------------------------------------------------

Objekt `Nette\Http\Session` má [události|nette:glossary#Události] `$onStart` a `$onBeforeWrite`, můžete tedy přidat callbacky, které se vyvolají po startu session nebo před jejím zápisem na disk a následným ukončením.

```php
$session->onBeforeWrite[] = function () {
	// zapíšeme data do session
	$this->section->set('basket', $this->basket);
};
```


Správa session
==============

Přehled metod třídy `Nette\Http\Session` pro správu session:

<div class=wiki-methods-brief>


start(): void .[method]
-----------------------
Zahájí session.


isStarted(): bool .[method]
---------------------------
Je session zahájená?


close(): void .[method]
-----------------------
Ukončí session. Session se automaticky ukončí na konci běhu skriptu.


destroy(): void .[method]
-------------------------
Ukončí a smaže session.


exists(): bool .[method]
------------------------
Obsahuje HTTP požadavek cookie se session ID?


regenerateId(): void .[method]
------------------------------
Vygeneruje nové náhodné session ID. Data zůstávají zachované.


getId(): string .[method]
-------------------------
Vrátí session ID.

</div>


Konfigurace
-----------

Session nastavujeme v [konfiguraci |configuration#session]. Pokud píšete aplikaci, která nepoužívá DI kontejner, slouží ke konfiguraci tyto metody. Musí být volány ještě před spuštěním session.

<div class=wiki-methods-brief>


setName(string $name): static .[method]
---------------------------------------
Nastaví název cookie, ve které se přenáší session ID. Standardní název je `PHPSESSID`. Hodí se v případě, kdy v rámci jednoho webu provozujete několik odlišných aplikací.


getName(): string .[method]
---------------------------
Vrací název cookie, ve které se přenáší session ID.


setOptions(array $options): static .[method]
--------------------------------------------
Konfiguruje session. Lze nastavovat všechny PHP [session direktivy |https://www.php.net/manual/en/session.configuration.php] (ve formátu camelCase, např. místo `session.save_path` zapíšeme `savePath`) a také [readAndClose |https://www.php.net/manual/en/function.session-start.php#refsect1-function.session-start-parameters].


setExpiration(?string $time): static .[method]
----------------------------------------------
Nastaví dobu neaktivity po které session vyexpiruje.


setCookieParameters(string $path, ?string $domain=null, ?bool $secure=null, ?string $samesite=null): static .[method]
---------------------------------------------------------------------------------------------------------------------
Nastavení parametrů pro cookie. Výchozí hodnoty parametrů můžete změnit v [konfiguraci|configuration#Session cookie].


setSavePath(string $path): static .[method]
-------------------------------------------
Nastaví adresář, kam se ukládají soubory se session.


setHandler(\SessionHandlerInterface $handler): static .[method]
---------------------------------------------------------------
Nastavení vlastního handleru, viz [dokumentace PHP|https://www.php.net/manual/en/class.sessionhandlerinterface.php].

</div>


Bezpečnost především
====================

Server předpokládá, že komunikuje stále s tímtéž uživatelem, dokud požadavky doprovází stejné session ID. Úkolem bezpečnostních mechanismů je zajistit, aby tomu tak doopravdy bylo a nebylo možné identifikátor odcizit nebo podstrčit.

Nette Framework proto správně nakonfiguruje PHP direktivy, aby session ID přenášel pouze v cookie, znepřístupnil jej JavaScriptu a případné identifikátory v URL ignoroval. Navíc v kritických chvílích, jako je třeba přihlášení uživatele, vygeneruje session ID nové.

Pro konfiguraci PHP se používá funkce ini_set, kterou bohužel některé hostingy zakazují. Pokud je to případ i vašeho hostéra, pokuste se s ním domluvit, aby vám funkci povolil nebo alespoň server nakonfiguroval. .[note]

Sessions

HTTP je bezestavový protokol, nicméně takřka každá aplikace potřebuje stav mezi požadavky uchovávat, například obsah nákupního košíku. Právě k tomu slouží session neboli relace. Ukážeme si,

  • jak používat sessions
  • jak předejít jmenným konfliktům
  • jak nastavit expiraci

Při použití sessions každý uživatel obdrží jedinečný identifikátor nazývaný session ID, který se předává v cookie. Ten slouží jako klíč k session datům. Na rozdíl od cookies, které se uchovávají na straně prohlížeče, jsou data v session uchovávána na straně serveru.

Session nastavujeme v konfiguraci, důležitá je zejména volba doby exipirace.

Správu session má na starosti objekt Nette\Http\Session, ke kterému se dostanete tak, že si jej necháte předat pomocí dependency injection. V presenterech stačí jen zavolat $session = $this->getSession().

Instalace a požadavky

Start session

Nette ve výchozím nastavení automaticky zahájí session automaticky ve chvíli, když z ní začneme číst nebo do ní zapisovat data. Ručně se session zahájí pomocí $session->start().

PHP odešle při spuštění session HTTP hlavičky ovlivňující kešování, viz session_cache_limiter, a případně i cookie se session ID. Proto je nutné vždy session nastartovat ještě před odesláním jakéhokoliv výstupu do prohlížeče, jinak dojde k vyhození výjimky. Pokud tedy víte, že v průběhu vykreslování stránky se bude používat session, nastartujte ji ručně předtím, třeba v presenteru.

Ve vývojářském režimu startuje session Tracy, protože ji používá pro zobrazování pruhů s přesměrováním a AJAXovými požadavky v Tracy Baru.

Sekce

V čistém PHP je datové úložiště session realizováno jako pole dostupné přes globální proměnnou $_SESSION. Problém je v tom, že aplikace se běžně skládají z celé řady vzájemně nezávislých částí a pokud všechny mají k dispozici jen jedno pole, dříve nebo později dojde ke kolizi názvů.

Nette Framework problém řeší tak, že celý prostor rozděluje na sekce (objekty Nette\Http\SessionSection). Každá jednotka pak používá svou sekci s unikátním názvem a k žádné kolizi již dojít nemůže.

Sekci získáme ze session:

$section = $session->getSection('unikatni nazev');

V presenteru stačí použít getSession() s parametrem:

// $this je Presenter
$section = $this->getSession('unikatni nazev');

Ověřit existenci sekce lze metodou $session->hasSection('unikatni nazev').

Se samotnou sekcí se pak pracuje velmi snadno pomocí metod set(), get() a remove(): (od nette/http v3.1.5)

// zápis proměnné
$section->set('userName', 'franta');

// čtení proměnné, vrátí null pokud neexistuje
echo $section->get('userName');

// zrušení proměnné
$section->remove('userName');

Před verzí 3.1.5 se používaly magické proměnné nebo ArrayAccess, které budou ve verzi 4 označeny za zastaralé.

$section->userName = 'franta'; // nebo $section['userName'] = 'franta';

echo $section->userName; // nebo echo $section['userName'];

unset($section->userName);  // unset($section['userName']);

Pro získání všech proměnných ze sekce je možné použít cyklus foreach:

foreach ($section as $key => $val) {
	echo "$key = $val";
}

Nastavení expirace

Pro jednotlivé sekce nebo dokonce jednotlivé proměnné je možné nastavit expiraci. Můžeme tak nechat vypršet přihlášení uživatele za 20 minut, ale přitom si nadále pamatovat obsah košíku.

// sekce vyexpiruje po 20 minutách
$section->setExpiration('20 minutes');

Pro nastavení expirace jednotlivých proměnných slouží třetí parametr metody set(): (od nette/http v3.1.5)

// proměnná 'flash' vyexpiruje už po 30 sekundách
$section->set('flash', $message, '30 seconds');

Před verzí 3.1.5 se použivala metoda setExpiration():

$section->setExpiration('30 seconds', 'flash');

Nezapomeňte, že doba expirace celé session (viz konfigurace session) musí být stejná nebo vyšší než doba nastavená u jednotlivých sekcí či proměnných.

Zrušení dříve nastavené expirace docílíme metodou removeExpiration(). Okamžité zrušení celé sekce zajistí metoda remove().

Události $onStart, $onBeforeWrite

Objekt Nette\Http\Sessionudálosti $onStart a $onBeforeWrite, můžete tedy přidat callbacky, které se vyvolají po startu session nebo před jejím zápisem na disk a následným ukončením.

$session->onBeforeWrite[] = function () {
	// zapíšeme data do session
	$this->section->set('basket', $this->basket);
};

Správa session

Přehled metod třídy Nette\Http\Session pro správu session:

start(): void

Zahájí session.

isStarted(): bool

Je session zahájená?

close(): void

Ukončí session. Session se automaticky ukončí na konci běhu skriptu.

destroy(): void

Ukončí a smaže session.

exists(): bool

Obsahuje HTTP požadavek cookie se session ID?

regenerateId(): void

Vygeneruje nové náhodné session ID. Data zůstávají zachované.

getId(): string

Vrátí session ID.

Konfigurace

Session nastavujeme v konfiguraci. Pokud píšete aplikaci, která nepoužívá DI kontejner, slouží ke konfiguraci tyto metody. Musí být volány ještě před spuštěním session.

setName(string $name): static

Nastaví název cookie, ve které se přenáší session ID. Standardní název je PHPSESSID. Hodí se v případě, kdy v rámci jednoho webu provozujete několik odlišných aplikací.

getName(): string

Vrací název cookie, ve které se přenáší session ID.

setOptions(array $options)static

Konfiguruje session. Lze nastavovat všechny PHP session direktivy (ve formátu camelCase, např. místo session.save_path zapíšeme savePath) a také readAndClose.

setExpiration(?string $time)static

Nastaví dobu neaktivity po které session vyexpiruje.

setCookieParameters(string $path, ?string $domain=null, ?bool $secure=null, ?string $samesite=null)static

Nastavení parametrů pro cookie. Výchozí hodnoty parametrů můžete změnit v konfiguraci.

setSavePath(string $path)static

Nastaví adresář, kam se ukládají soubory se session.

setHandler(\SessionHandlerInterface $handler)static

Nastavení vlastního handleru, viz dokumentace PHP.

Bezpečnost především

Server předpokládá, že komunikuje stále s tímtéž uživatelem, dokud požadavky doprovází stejné session ID. Úkolem bezpečnostních mechanismů je zajistit, aby tomu tak doopravdy bylo a nebylo možné identifikátor odcizit nebo podstrčit.

Nette Framework proto správně nakonfiguruje PHP direktivy, aby session ID přenášel pouze v cookie, znepřístupnil jej JavaScriptu a případné identifikátory v URL ignoroval. Navíc v kritických chvílích, jako je třeba přihlášení uživatele, vygeneruje session ID nové.

Pro konfiguraci PHP se používá funkce ini_set, kterou bohužel některé hostingy zakazují. Pokud je to případ i vašeho hostéra, pokuste se s ním domluvit, aby vám funkci povolil nebo alespoň server nakonfiguroval.