Novinky v Nette 3.1 je venku!

David Grudl
Nette Core | 8136
+
+23
-

Nette 3.1 je venku! Novinky zanáším do dokumentace a chystám články na blog, v tomhle postu si je chci postupně sumarizovat.

Všechny informace o tom, že je něco přejmenované, chápejte prosím tak, že původní varianta stále funguje, nejde o žádné BC breaky.

Obecně

  • Minimální požadovaná verze je PHP 7.2
  • Pokračuje odstraňování prefixu I z názvů rozhraní (o důvodech chystám článek)

Nette Application

  • novinky jsem už dříve sepsal tady
  • dále catchExceptions is always true on production mode
  • router se může v konfigu uvádět bez toho ‚router:‘ (viz)
  • v konfigu zrušena volba application > routeClass
  • volba application > scanFilter je nyní maska s podporou wildchars a výchozí hodnota je *Presenter
  • do flashMessage($obj) můžete nyní předat vlastní objekt zprávy, musí být potomkem stdClass
  • značka {control name, x: 1, y: 2} podporuje syntax pojmenovaných parametrů
  • Form::disableSameSiteProtection() replaced with allowCrossOrigin()

Bootstrap

  • kvůli konzistenci se třída Nette\Configurator přesouvá do namespace, takže je Nette\Bootstrap\Configurator
  • metoda addParameters() má alias addStaticParameters(), který je srozumitelnější ve dvojici s addDynamicParameters()
  • a parametry které předáváme již nemohou obsahovat další parametry, tj. addParameters(['path' => '%appDir%/temp') nebude fungovat. Že to funguje asi nikdo netušil a vznikaly kvůli tomu občas problémy.
  • úplně s Bootstrapem to nesouvisí, ale v Sandboxu a WebProjectu jsem vyslyšel žádosti a přesunul složku config/ o úroveň výš

Database

  • po mnoha letech hledání názvu pro Nette Database Explorer (pamatujete NDBT, Selection, Table, ufff) to vypadá, že jsme na konci a Nette Database Explorer funguje dobře, takže i třída Nette\Database\Context je nyní Nette\Database\Explorer
  • elegantní způsob práce s transakcí nabízí metoda transaction(), které předáme callback, který se vykoná v transakci. Pokud během vykonávání dojde k vyhození výjimky, transakce se zahodí, pokud vše proběhne v pořádku, transakce se potvrdí. Metoda transaction() vrací návratovou hodnotu callbacku.
$id = $database->transaction(function () use ($database) {
	$database->query('DELETE FROM ...');
	$database->query('INSERT INTO ...');
	// ...
	return $database->getInsertId();
});

Forms

Hlavní změna se týká getValues() a parametru $values předávaného do handlerů onSuccess, onClick:

  • jen zvalidované prvky se předávají do values (aby nedocházelo k typovým chybám při mapování do tříd s properties s typy)
  • setValidationScope() omezuje prvky, které se validují, a nově tedy i prvky předané do $values
  • volání $form->getValues() u nevalidního formuláře vyvolá warning
  • pro získání všech hodnot, i nevalidních, je tu nová metoda $form->getUntrustedValues().

A dále:

  • při standalone použití formulář kontroluje sameSite cookie kvůli ochraně před CSRF. Odeslání cookie zajišťuje Form::initialize().
  • pravidlo Form::URL doplní example.org na https://example.org místo dřívějšího http
  • FormMacros: už nevytváří deprecated proměnné $_form
  • Guess first parameter for event by type hint (#219)
  • Validator::validateEqual returns false if control value is empty array (BC break) [Closes #257]
  • Container::getValues($obj) to hydrate object
  • Validator Form::URL autocompletes https:// instead of http:// (BC break)
  • Checkbox: added getContainerPrototype()
  • Container: addImage() renamed to addImageButton()

Http

  • všechny cookies se defaultně posílají se SameSite=Lax, včetně session id
  • všechny cookies se na HTTPS spojení defaultně posílají s příznakem Secured
  • cookie pro detekci útoku se z nette-samesite přejmenovala na _nss
  • v konfiguraci jsou nové volby http > cookiePath a http > cookieDomain, naopak deprecated je session > cookieSecure protože její hodnota se přebírá z http > cookieSecure
  • SessionSection: nyní je možné číst data číst i ve chvíli, kdy se session uzavřela
  • FileUpload::getName() je přejmenovaný na více odstrašující getUntrustedName()

Latte

Neon

  • běží už jen v UTF-8 režimu

PhpGenerator

  • viz článek na blogu o verzi 3.4
  • dále ve verzi 3.5 doplněna podpora pro PHP 8 (constructor property promotion, PHP 8 attributes, union types, …)

Schema

  • nový systém generování chybových hlášek. Hlášky jsou objekty doplněné všemi informacemi, lze je tak snadno třeba překládat.
  • lze generovat i varování, například že položka je deprecated
  • structure() může být označena jako volitelná, i když je v ní povinný prvek
  • pomocí mergeDefaults() je možné vypnout mergování polí, které bylo pro řadu uživatelů kontraintuitívní

Security

  • třída Identity je přejmenovaná na SimpleIdentity. Důvod je uvolnit si do dalekého budoucna název Identity pro rozhraní.
  • v konfiguraci je deprecated security > roles a security > resources. Používali jste to někdo? Dejte mi vědět.
  • IAuthenticator se jmenuje Authenticator, ale obě varianty se lehce liší. U té nové se jméno a heslo už nepředávají v poli
  • nové rozhraní Nette\Security\UserStorage nahrazuje dřívější IUserStorage a zároveň jsou v k dispozici dvě implementace. Jedna, která ukládá informace o přihlášeném uživateli do session (jako původní) a nová druhá, která si vystačí čistě s cookie. Tedy bezsessionové přihlašování. (Mimochodem, forum a web Nette tak běží odjakživa). Hledejte security > authentication > storage
  • tedy getUser()->getStorage() nyní vrací nový storage, ale můžete v konfiguraci vynutit i původní:
services:
	security.userStorage: false
  • jak nejsnáze aktualizovat identitu po načtení ze session? Třeba v ní aktualizovat role? Stačí aby autentikátor implementoval rozhraní Nette\Security\IdentityHandler.
class Authenticator implements Nette\Security\Authenticator, Nette\Security\IdentityHandler
{
	public function authenticate(string $username, string $password): SimpleIdentity
	{
		...
	}

	public function wakeupIdentity(IIdentity $identity): SimpleIdentity
	{
		// aktualizace $identity
	}

Kromě metody wakeupIdentity() je tam ještě sleepIdentity(IIdentity $identity): IIdentity s opačnou úlohou, tady připravit identitu ke serializaci.

Pokud použijete uložiště security > authentication > storage: cookie, tak právě tyto dvě metody jsou klíčové, protože identita se do session neukládá vůbec.

Tracy

Zcela zbrusu nový neskutečně vylepšený dump(). K tomu bude samostatný článek.

  • neskutečně rychlé
  • vizuálně propracované
  • generování pomocí JavaScriptu
  • dark mode
  • podpora nativních objektů pro DOM, ArrayObject, atd
  • rozklikávání do hloubky přes Alt-click
  • zobrazuje reference mezi proměnnými
  • rozlišení uninitialized proměnných
  • informace o délce řetězce v bytech / znacích
  • přehledné zobrazování víceřádkových řetězců
  • a mnoho dalšího

Utils

  • renamed Nette\Utils\IHtmlString → Nette\HtmlStringable
  • nová třída Nette\Utils\Floats
  • Image: added detectTypeFromFile() and detectTypeFromString()
  • Arrays: searchKey() renamed to getKeyOffset()
  • Reflection: added getReturnTypes(), getParameterTypes() and getPropertyTypes() pro union typy
ApliTax
Bronze Partner | 2
+
+2
-

Ahoj Davide,
Tohle mi trošku komplikuje život:

IAuthenticator se jmenuje Authenticator, ale obě varianty se lehce liší. U té nové se jméno a heslo už nepředávají v poli

Vyhovovalo mi pole, protože jsem u několika aplikací nepotřeboval předávat z přihlašovacího formuláře jen login a heslo, ale i další parametry např. typ uživatele (dodavatel / subdodavatel / dopravce).

Uživatelé byli z uloženi v různých tabulkách s rozdílnou strukturou a měli přístup do odlišných částí aplikace.

Karel

David Grudl
Nette Core | 8136
+
+1
-

Pomohlo by ti, kdyby tady bylo $authenticator->authenticate(...func_get_args()), tedy by se do authenticate() poslaly všechny argumenty?

Jan Tvrdík
Nette guru | 2595
+
+4
-

Možná jsem to úplně nepochopil, ale neměl by CookieStorage tu cookie kryptograficky podepisovat (HMAC typicky)?

jAkErCZ
Člen | 321
+
0
-

Ahoj,
Já jsem používal

security:
	roles:
		guest:
		member: [guest]
		technik: [guest]
		lead: [member]
		admin: [lead]

A teď mám problém jak opět zprovoznit viz Problém s rolemi v nette 3

Díky za případnou radu 😊

David Grudl
Nette Core | 8136
+
0
-

@JanTvrdík ano, to je ukol pro IdentityHandler::sleepIdentity() vrátit UID, které se pak uloží do cookie, a wakeupIdentity() z totoho UID zase vytvoří původní identitu.

@jAkErCZ aha, no já tam objevil problém, ze kterého jsem pochopil, že to nikdy nefungovalo (ale teď si zaboha nevzpomenu jakej…). Takže to fungovalo?

jAkErCZ
Člen | 321
+
0
-

David Grudl napsal(a):

@JanTvrdík ano, to je ukol pro IdentityHandler::sleepIdentity() vrátit UID, které se pak uloží do cookie, a wakeupIdentity() z totoho UID zase vytvoří původní identitu.

@jAkErCZ aha, no já tam objevil problém, ze kterého jsem pochopil, že to nikdy nefungovalo (ale teď si zaboha nevzpomenu jakej…). Takže to fungovalo?

Ano u mě to normálně fungovalo 😊

ApliTax
Bronze Partner | 2
+
0
-

David Grudl napsal(a):

Pomohlo by ti, kdyby tady bylo $authenticator->authenticate(...func_get_args()), tedy by se do authenticate() poslaly všechny argumenty?

Ano, super, díky.

David Grudl
Nette Core | 8136
+
+1
-

Příklad toho IdentityHandler pro cookies. V db vytvořím sloupec token, ve kterém bude mít každý uživatel náhodný alphanumerický řetězec o dostatečné délce (minimálně 13 znaků).

final class Authenticator implements Nette\Security\Authenticator, Nette\Security\IdentityHandler
{
	public function authenticate(string $username, string $password): SimpleIdentity
	{
		// po přihlášení klasicky vytvořím identitu z údajů z databáze
		$row = $this->db->fetch('SELECT * FROM user WHERE username = ?', $username);
		...
		return new SimpleIdentity($row->id, null, (array) $row);
	}

	public function sleepIdentity(IIdentity $identity): SimpleIdentity
	{
		// Příprava identity k uložení do storage po přihlášení.
		// Do cookie storage se ukládá jen $identity->getId(), takže vrátím
		// novou identitu, kde jako id předám token.
		return new SimpleIdentity($identity->token);
	}

	public function wakeupIdentity(IIdentity $identity): ?SimpleIdentity
	{
		// Obnovení identity ze storage při každém požadavku.
		// Cookie storage vrátí identitu s tokenem, podle něj dohledám uživatele.
		$row = $this->db->fetch('SELECT * FROM user WHERE token = ?', $identity->getId());
		return $row
			? new SimpleIdentity($row->id, null, (array) $row) // a vrátím identitu jako authenticate()
			: null;
	}
}
David Grudl
Nette Core | 8136
+
0
-

@jAkErCZ už jsem na to přišel! Ty si můžeš nadefinovat role a resources, ale úplně tam chybí možnost definovat pravidla allow() a deny(). Bez toho mi to nedávalo smysl.

Jestli to chápu dobře, ty sis následně pravidla nadefinoval v setup?

David Grudl
Nette Core | 8136
+
0
-

@dsar This will probably be for a longer discussion…