phpFashion

Na navigaci | Klávesové zkratky

Tabulátory místo mezer jako projev ohleduplnosti

Určitě už jste někdy narazili debatu „tabulátory vs. mezery“ pro odsazování. Polemika probíhá od nepaměti a oba tábory vyzdvihují své argumenty:

Tabulátory:

  • odsazování je jejich účel
  • menší soubory, protože odsazení zabírá jeden znak
  • můžete si nastavit vlastní šířku odsazení (👈 k tomu se vrátíme)

Mezery:

  • kód bude vypadat všude stejně a konzistence je klíčová
  • vyhnete se možným problémům v prostředích citlivých na bílé znaky

Co když jde ale o víc než o osobní preference? ChaseMoskal nedávno zveřejnil na Redditu velmi podnětný příspěvek s názvem Nikdo nezmínil skutečný důvod, proč používat tabulátory místo mezer, který vám otevře oči.

Stěžejní důvod, proč používat tabulátory

Chase ve svém příspěvku popisuje zkušenost se zaváděním mezer na svém pracovišti a negativní dopady, které to mělo na spolupracovníky se zrakovým postižením.

Jeden z nich byl zvyklý používat šířku tabulátoru 1, aby se vyhnul velkým odsazením při použití obřího písma. Druhý používá šířku tabulátoru 8, protože mu nejlépe vyhovuje na ultraširokém monitoru. Pro oba však představuje kód s mezerami vážný problém, musí je převádět na tabulátory před čtením a zase zpátky na mezery před komitováním.

Pro nevidomé programátory, kteří používají braillské displeje, představuje každá mezera jednu braillskou buňkou. Pokud je tedy výchozí odsazení 4 mezery, odsazení 3. úrovně plýtvá 12 cennými braillskými buňkami ještě před začátkem kódu. Na 40buňkovém displeji, který se u notebooků používá nejčastěji, je to více než čtvrtina dostupných buněk, které jsou promrhány bez jakékoliv informace.

Nám se může přizpůsobení šířky odsazení zdát jako zbytečnost, jsou ale mezi námi programátoři, pro které je naprosto nezbytné. A to prostě nemůžeme ignorovat.

Tím, že budeme v našich projektech používat tabulátory, dáváme jim možnost tohoto přizpůsobení.

Nejdříve přístupnost, pak osobní preference

Jistě, nejde přesvědčit každého, aby se přiklonil na jednu či druhou stranu, jde-li o preference. Každý má své. A měli bychom být rádi za možnost volby.

Zároveň však musíme dbát na to, abychom zohledňovali všechny. Abychom respektovali odlišnosti a používali přístupné prostředky. Jakým je například znak tabulátor.

Myslím, že Chase to vystihl dokonale, když ve svém příspěvku uvedl, že „…neexistuje žádný protiargument, který by se jen blížil k tomu převážit potřeby přístupnosti našich spolupracovníků“.

Accessible first

Stejně jako při navrhování webů se vžila metodika „mobile first“, kdy se snažíme zajistit, aby každý, bez ohledu na zařízení, měl s vaším produktem skvělou user experience – měli bychom usilovat o „accessible first“ prostředí tím, že zajistíme, aby každý měl stejnou možnost pracovat s kódem, ať už v zaměstnání nebo na opensource projektu.

Pokud se tabulátory stanou výchozí volbou pro odsazování, odstraníme jednu bariéru. Spolupráce pak bude příjemná pro každého, bez ohledu na jeho schopnosti. Pokud budou mít všichni stejné možnosti, můžeme maximálně využít společný potenciál ❤️


Článek vychází z Default to tabs instead of spaces for an ‚accessible first‘ environment. Podobně přesvědčivý post jsem si přečetl v roce 2008 a ještě ten den změnil ve všech svých projektech mezery na tabulátory. Zůstala po tom stopa v Gitu, ale samotný článek už zmizel v propadlišti dějin.

před měsícem v rubrice PHP


Přidejte si značku {texy} do Latte

Knihovna Texy od verze 3.1.6 přidává podporu pro Latte 3 v podobě značky {texy}. Co umí a jak ji nasadit?

Značka {texy} představuje snadný způsob, jak v Latte šablonách psát přímo v syntaxi Texy:

{texy}
You Already Know the Syntax
----------

No kidding, you know Latte syntax already. **It is the same as PHP syntax.**
{/texy}

Stačí do Latte nainstalovat rozšíření a předat mu objekt Texy nakonfigurovaný podle potřeby:

$texy = new Texy\Texy;
$latte = new Latte\Engine;
$latte->addExtension(new Texy\Bridges\Latte\TexyExtension($texy));

Pokud je mezi značkami {texy}...{/texy} statický text, tak se přeloží pomocí Texy už během kompilace šablony a výsledek do ní uloží. Pokud je obsah dynamický (tj. jsou uvnitř Latte značky), zpracování pomocí Texy se provádí pokaždé při vykreslování šablony.

Pokud je žádoucí Latte značky uvnitř vypnout, dá se to udělat takto:

{texy syntax: off} ... {/texy}

Do rozšíření lze kromě objektu Texy předat také vlastní funkci a tak umožnit předávat ze šablony parametry. Kupříkladu chceme mít možnost předávat parametry locale a heading:

$processor = function (string $text, int $heading = 1, string $locale = 'cs'): string {
	$texy = new Texy\Texy;
	$texy->headingModule->top = $heading;
	$texy->typographyModule->locale = $locale;
	return $texy->process($text);
};

$latte = new Latte\Engine;
$latte->addExtension(new Texy\Bridges\Latte\TexyExtension($processor));

Parametry v šabloně předáme takto:

{texy locale: en, heading: 3}
...
{/texy}

Pokud chcete pomocí Texy formátovat text uložený v proměnné, můžete použít filtr:

{$description|texy}

Latte 3: největší skok v dějinách Nette

Prosím o fanfáry, na scénu přichází Latte 3. S kompletně přepsaným kompilátorem. Nová verze představuje největší vývojový skok, jaký kdy v Nette nastal.

Proč vlastně Latte

Latte má překvapivou historii. Původně totiž nebylo myšleno vážně. Mělo dokonce demonstrovat, že žádný šablonovací systém není v PHP potřeba. Bylo pevně spjato s presentery v Nette, kde však nebylo defaultně zapnuté a programátor jej musel aktivovat přes tehdejší ošklivý název CurlyBracketsFilter.

Zvrat přišel až s nápadem, že šablonovací systém by mohl HTML stránce rozumět. Vysvětlím. Pro ostatní šablonovací systémy je text v okolí značek jen šumem bez jakéhokoliv významu. Je jedno, jestli jde o HTML stránku, CSS styl nebo třeba text v Markdownu, šablonovací engine vidí jen shluk bajtů. Latte naopak dokument chápe. Což přináší spoustu zásadních výhod. Od komfortu v podobě vychytávek jako jsou třeba n:attributy, až po ultimátní bezpečnost.

Latte tak ví, jakou použít escapovací funkci (což většina programátorů neví, ale díky Latte to nevadí a nevytvoří bezpečnostní díru Cross-site scripting). Zabrání vypsání řetězce, který by v určitém místě byl nebezpečný. Dokonce dokáže předejít dezinterpretaci mustache závorek frontendovým frameworkem. A bezpečnostní experti nebudou mít co žrát :)

Nečekal bych, že tímto nápadem přeběhne Latte ostatní systémy o 10 let, protože dodneška vím pouze o dvou, co takto fungují. Krom Latte je to ještě Soy od Google. Latte a Soy jsou jediné opravdu bezpečné šablonovací systémy pro web. (Byť teda Soy ze zmíněných vychytávek má pouze to escapování.)

Druhou klíčovou vlastností Latte je, že pro výrazy uvnitř značek (někdy se říká maker) používá jazyk PHP. Tedy syntaxi programátorovi důvěrně známou. Vývojář se tak nemusí učit nový jazyk. Nemusí zkoumat, jak se to či ono v Latte píše. Prostě to napíše tak jak umí. Naopak třeba populární šablonovací systém Twig používá syntaxi Pythonu, kde se i zcela základní konstrukce píší odlišně. Například foreach ($people as $person) se v Pythonu (a tedy i Twigu) píše jako for person in people, což zcela zbytečně nutí mozek přepínat mezi dvěma opačnými konvencemi.

Latte tedy má oproti konkurenci natolik podstatnou přidanou hodnotu, že má smysl investovat úsilí do jeho údržby a vývoje.

Současný kompilátor

Latte a jeho syntax vznikla před 14 lety (rok 2008), současný kompilátor o tři roky později. Uměl už tehdy vše podstatné, co se dodnes používá, tedy i bloky, dědičnost, snippety atd.

Kompilátor fungoval jako jednoprůchodový, což znamená, že parsoval šablonu a rovnou ji přetvářel do PHP kódu, který sestavil do výsledného souboru. Jazyk PHP používaný ve značkách (tj. v makrech) se tokenizoval a poté procházel několika procesy, které tokeny upravovaly. Jeden proces doplňoval řetězcové uvozovky kolem identifikátorů, jiný přidával syntaktické vychytávky, které PHP tehdy neznalo (například zápis polí pomocí [] místo array(), nullsafe operátory ?->) nebo které nezná doposud (zkrácený ternární operátor, filtry ($var|upper|truncate), atd).

Tyto procesy ale nijak nekontrolovaly PHP syntax nebo používané konstrukce. Což se výrazně změnilo až před dvěma lety (rok 2020) s příchodem sandbox režimu. Sandbox hledá v tokenech možné volání funkcí a metod a upravuje je, což není vůbec jednoduché. Přičemž případné selhání je vlastně bezpečností chybou.

Nový kompilátor

Za jedenáct let vývoje Latte se našly situace, kdy jednoprůchodový kompilátor nestačil (třeba při inkludování bloku, který ještě nebyl definován). Všechny issue šlo sice vyřešit, ale ideální by bylo přejít na dvoukrokovou kompilaci, tedy nejprve šablonu naparsovat do mezipodoby, do AST stromu, a pak teprve z něj vygenerovat kód třídy.

Taktéž s postupným vylepšováním PHPlike jazyka používaného ve značkách přestávala dostačovat reprezentace v tokenech a ideální by bylo i jej naparsovat do AST stromu. Naprogramovat sandbox nad AST stromem je výrazně snadnější a dá se garantovat, že bude skutečně neprůstřelný.

Trvalo mi pět let se do přepsání kompilátoru pustit, protože jsem věděl, že to bude extrémně náročné. Už samotná tokenizace šablony představuje výzvu, neboť musí běžet paralelně s parsováním. Parser totiž musí mít možnost ovlivňovat tokenizaci, když například narazí na atribut n:syntax=off.

Podporu pro paralelní běh dvou kódů přináší až Fibers v PHP 8.1, nicméně Latte je zatím nevyužívá, aby mohlo fungovat na PHP 8.0. Místo toho používá obdobné coroutines (v dokumentaci PHP o nich nic nenajdete, tak alespoň odkaz na Generator RFC). Pod kapotou Latte se tedy odehrávají kouzla.

Nicméně jako ještě mnohem náročnější úkol mi připadalo napsat lexer a parser pro tak komplexní jazyk, jako je dialekt PHP používaný ve značkách. V podstatě to znamenalo vytvořit něco jako nikic/PHP-Parser pro Latte. A zároveň i nutnost formalizovat gramatiku tohoto jazyka.

Dnes můžu říct, že se mi povedlo všechno dokončit. Latte má kompilátor, jaký jsem si dlouhá léta přál. A z toho původního nezbyl ani jediný řádek kódu 🙂


Nejsi ve vleku cargo kultů?

Před mnoha lety jsem si uvědomil, že když v PHP ve funkci používám proměnnou obsahující předdefinovanou tabulku dat, tak při každém volání funkce musí být pole znovu „vytvořené“, což je překvapivě dost pomalé. Příklad:

function isSpecialName(string $name): bool
{
	$specialNames = ['foo' => 1, 'bar' => 1, 'baz' => 1, ...];
	return isset($specialNames[$name]);
}

A přišel jsem na jednoduchý trik, který znovuvytváření zabránil. Stačilo proměnnou definovat jako statickou:

function isSpecialName(string $name): bool
{
	static $specialNames = ['foo' => 1, 'bar' => 1, 'baz' => 1, ...];
	return isset($specialNames[$name]);
}

Zrychlení, pokud pole bylo trošku větší, se pohybovalo v několika řádech (jako třeba klidně 500×).

Takže od té doby jsem u konstantních polí vždy používal static. Je možné, že tento zvyk někdo následoval, a třeba ani netušil, jaký má skutečný důvod. Ale to nevím.


Před pár týdny jsem psal třídu, která nesla v několika properties velké tabulky předdefinovaných dat. Uvědomil jsem si, že to bude zpomalovat vytváření instancí, tedy že operátor new bude pokaždé „vytvářet“ pole, což jak víme je pomalé. Tudíž musím properties změnit na statické, nebo možná ještě lépe použít konstanty.

A tehdy jsem si položil otázku: Hele a nejsi jen ve vleku cargo kultu? Opravdu pořád platí, že bez static je to pomalé?

Těžko říct, PHP prošlo revolučním vývojem a staré pravdy nemusí být platné. Připravil jsem proto testovací vzorek a udělal pár měření. Samozřejmě jsem si potvrdil, že v PHP 5 použití static uvnitř funkce nebo u properties přineslo zrychlení o několik řádů. Ale pozor, v PHP 7.0 už šlo jen o jeden řád. Výborně, projev optimalizací v novém jádře, ale stále je rozdíl podstatný. Nicméně u dalších verzí PHP rozdíl dál klesal a až postupně téměř vymizel.

Dokonce jsem zjistil, že použití static uvnitř funkce v PHP 7.1 a 7.2 běh zpomalovalo. Zhruba 1,5–2×, tedy z pohledu řádů, o kterých se tu celou dobu bavíme, zcela zanedbatelně, ale byl to zajímavý paradox. Od PHP 7.3 rozdíl zmizel zcela.

Zvyklosti jsou dobrá věc, ale je nutné jejich smysl stále validovat.


Zbytečný static v těle funkcí už používat nebudu. Nicméně u oné třídy, která držela velké tabulky předdefinovaných dat v properties, jsem si řekl, že je programátorsky správné konstanty použít. Za chvíli jsem měl refaktoring hotový, ale už jak vznikal jsem naříkal nad tím, jak se kód stává ošklivým. Místo $this->ruleToNonTerminal nebo $this->actionLength se v kódu objevovalo řvoucí $this::RULE_TO_NON_TERMINAL a $this::ACTION_LENGTH a vypadalo to fakt hnusně. Zatuchlý závan ze sedmdesátých let.

Až jsem zaváhal, jestli vůbec chci koukat na tak hnusný kód, a jestli raději nezůstanu u proměnných, případně statických proměnných.

A tehdy mi to došlo: Hele nejsi jen ve vleku cargo kultu?

No jasně že jsem. Proč by měla konstanta řvát? Proč by měla na sebe upozorňovat v kódu, být vyčnívajícím elementem v toku programu? Fakt, že struktura slouží jen ke čtení, není důvod PRO ZASEKNUTÝ CAPSLOCK, AGRESIVNÍ TÓN A HORŠÍ ČITELNOST.

TRADICE VELKÝCH PÍSMEN POCHÁZÍ Z JAZYKA C, KDE SE TAKTO OZNAČOVALY MAKROKONSTANTY PREPROCESORU. BYLO UŽITEČNÉ NEPŘEHLÉDNUTELNĚ ODLIŠIT KÓD PRO PARSER OD KÓDU PRO PREPROCESOR. V PHP SE ŽÁDNÉ PREPROCESORY NIKDY NEPOUŽÍVALY, TAKŽE NENÍ ANI DŮVOD psát konstanty velkými písmeny.

Ještě ten večer jsem je všude zrušil. A stále nemohl pochopil, proč mě to nenapadlo už před dvaceti lety. Čím větší blbost, tím tužší má kořínek.


Zapisovat nullable types s otazníkem nebo bez?

Vždycky mi vadila jakákoliv nadbytečnost nebo duplicita v kódu. Už jsem o tom psal před mnoha lety. Při pohledu na tento kód prostě trpím:

interface ContainerAwareInterface
{
    /**
     * Sets the container.
     */
    public function setContainer(ContainerInterface $container = null);
}

Obsahovou zbytečnost komentáře u metody ponechme stranou. A protentokrát i projev nepochopení dependency injection, pokud knihovna potřebuje disponovat takovým rozhraním. O tom, že použití slova Interface v názvu rozhraní je pro změnu projevem nepochopení objektového programování, chystám samostatný článek. Koneckonců jsem si tím sám prošel.

Ale proč proboha uvádět viditelnost public? Vždyť je to pleonasmus. Kdyby to nebylo public, tak to pak není rozhraní, ne? No a ještě někoho napadlo z toho udělat „standard“ 🤦‍♂️

Uff, omlouvám se za dlouhý úvod, to, kam celou dobu směřuju, je zda psát volitelné nullable typy s otazníkem nebo bez. Tj:

// bez
function setContainer(ContainerInterface $container = null);
// s
function setContainer(?ContainerInterface $container = null);

Osobně jsem se vždycky klonil k první možnosti, protože informace daná otazníkem je redundantní (ano, oba zápisy znamenají z pohledu jazyka totéž). Zároveň se tak zapisoval veškerý kód do příchodu PHP 7.1, tedy verze, která otazník přidala, a musel by být dobrý důvod jej najednou měnit.

S příchodem PHP 8.0 jsem názor změnil a vysvětlím proč. Otazník totiž není volitelný v případě properties. Na tomhle PHP zařve:

class Foo
{
	private Bar $foo = null;
}
// Fatal error: Default value for property of type Bar may not be null.
// Use the nullable type ?Bar to allow null default value

A dále od PHP 8.0 lze používat promoted properties, což umožňuje psát takovýto kód:

class Foo
{
	public function __construct(
		private ?Bar $foo = null,
		string $name = null,
	) {
		// ...
	}
}

Zde je vidět nekonzistence. Pokud je v kódu použito ?Bar (což je nutnost), mělo by o řádek níže následovat ?string. A pokud v některých případech budu psát otazník, měl bych ho psát ve všech.

Zůstává otázka, zda není lepší používat místo otazníku přímo union typ string|null. Pokud bych třeba chtěl zapsat Stringable|string|null, verze s otazníkem možná vůbec není.


Readonly proměnné v PHP 8.1 vás zaskočí

PHP 8.1 přichází se zajímavou novinkou: readonly členské proměnné:

Začneme rovnou příkladem použití:

class Test
{
	public readonly string $prop;

	public function setProp(string $prop): void
	{
		$this->prop = $prop; // legal initialization
	}
}

$test = new Test;
$test->setProp('abc');
echo $test->prop; // legal read
$test->prop = 'foo'; // throws exception: Cannot modify readonly property Test::$prop

Tedy jednou inicializovaná proměnná už nemůže být přepsána jinou hodnotou.

Scope

Překvapivě ale přiřazení do $test->prop vyhodí výjimku i v případě, že proměnná inicializovaná není:

$test = new Test;
$test->prop = 'foo';
// throws exception too: Cannot initialize readonly property Test::$prop from global scope

Dokonce i tohle vyhodí výjimku:

class Child extends Test
{
	public function __construct()
	{
		$this->prop = 'hello';
		// throws exception: Cannot initialize readonly property Test::$prop from scope Child
	}
}

Do readonly proměnné prostě nelze zapsat odjinud než ze třídy, která ji definovala. Zvláštní.

Neměnnost

To, že nelze měnit obsah readonly proměnných, ještě neznamená, že data tam zapsané jsou neměnné. Pokud do takové proměnné zapíšeme objekt, můžeme nadále měnit jeho vnitřní proměnné. Objekt se nestane immutable.

To stejné platí pro pole. Byť tam je chování trošku odlišné. Změna prvků v poli se považuje za změnu celého pole a tedy jako taková je v readonly proměnné nepřípustná. Ale pokud pole obsahuje prvek, který je referencí, změna jeho obsahu se za změnu celého pole nepovažuje a tedy k ní může v readonly prvku docházet. Což je nicméně standardní chování PHP odjakživa.

Jinými slovy tohle lze:

class Test
{
	public readonly array $prop;

	public function test(): void
	{
		$item = 'foo';
		$this->prop = [1, &$item, 2];
		dump($this->prop); // [1, 'foo', 2]
		$item = 'bar'; // legal
		dump($this->prop); // [1, 'bar', 2]
	}
}

Ale tohle nelze:

class Test
{
	public readonly array $prop;

	public function test(): void
	{
		$this->prop = ['a', 'b'];
		$this->prop[1] = 'c'; // throws exception!
	}
}

Typ

Protože readonly proměnné využívají stavu ‚uninitialized‘, který existuje u proměnných s definovaným typem, je možné readonly uvádět jen společně s datovým typem.


Který framework má nejlepší dokumentaci?

Zajímalo mě, který PHP framework má nejlepší dokumentaci. A jak si v žebříčku stojí Nette. Jenže jak to zjistit?

Všichni víme, že nejhorší je žádná dokumentace. Pak následuje nedostatečná dokumentace. Opakem je obsáhlá dokumentace. Tedy zdá se, že důležitým vodítkem je samotný objem dokumentace. Pochopitelně obrovskou roli hraje i její srozumitelnost a aktuálnost, dojem dělá čtivost a bezchybnost. Tyto faktory se velmi těžko měří. Nicméně sám vím, kolik částí dokumentace Nette jsem mnohokrát přepsal, aby byly jasnější, kolik oprav jsem mergoval, a předpokládám, že se tak děje u každého letitého frameworku. Že tedy postupně všechny dokumentace konvergují k podobné vysoké kvalitě. Tudíž si jako vodítko dovolím brát čistě objem dat, byť jde o zjednodušení.

Pochopitelně se objem dokumentace musí dát do poměru s velikostí té které knihovny. Některé jsou i řádově větší než jiné a pak by měly mít i řádově větší dokumentaci. Pro jednoduchost budu velikost knihovny stanovovat podle objemu PHP kódu. S normalizovaným bílým místem, bez komentářů.

Vytvořil jsem graf poměru anglické dokumentace ku kódu u známých frameworků CakePHP (4.2), CodeIgniter (3.1), Laravel (8.62), Nette (3.1), Symfony (5.4), YII (2.0) a Zend Framework (2.x, již nevyvíjený):

Jak z grafu vidíte, obsáhlost dokumentace vůči kódu je u všech frameworků víceméně podobná.

Vyčnívá CodeIgniter. Smekám před CakePHP a YII, které se snaží udržovat dokumentaci v celé řadě dalších jazyků. Obsáhlost dokumentace Nette je nad průměrem. Zároveň Nette je jediný framework, který má 1:1 překlad i v naší mateřštině.

Smyslem grafu NENÍ ukázat, že ten či onen framework má o tolik procent obsáhlejší dokumentaci než jiný. Na to je metrika příliš primitivní. Smyslem je naopak ukázat, že obsáhlost dokumentace u jednotlivých frameworků z velké míry srovnatelná. Vytvořil jsem jej hlavně pro sebe, abych získal představu, jak je na tom dokumentace Nette ve srovnání s konkurencí.

Původně vyšlo v srpnu 2019, údaje jsou aktualizované pro říjen 2021.

před rokem v rubrice PHP


Jak probíhá shutdown v PHP a volání destruktorů?

Ukončení požadavku v PHP se skládá z těchto kroků prováděných v uvedeném pořadí:

  1. Volání všech funkcí registrovaných pomocí register_shutdown_function()
  2. Volání všech metod __destruct()
  3. Vyprázdnění všech output bufferů
  4. Ukončení všech rozšíření PHP (např. sessions)
  5. Vypnutí výstupní vrstvy (odeslání HTTP hlaviček, vyčištění output handlerů atd.)

Zaměříme se podrobněji na krok č. 2, tedy volání destruktorů. Samozřejmě už v prvním kroku, tedy při volání registrovaných shutdown funkcí, může dojít k destrukci objektů, např. pokud některá z funkcí držela poslední referenci na nějaký objekt nebo pokud byla samotná shutdown funkce objektem.

Volání destruktorů probíhá takto:

  1. PHP se nejprve pokusí zrušit objekty v globální tabulce symbolů.
  2. Poté volá destruktory všech zbývajících objektů.
  3. Pokud je provádění zastaveno např. kvůli exit(), zbývající destruktory se nevolají.

ad 1) PHP projde globální tabulku symbolů pozpátku, tj. začne od proměnné, která byla vytvořena jako poslední, a postupuje k proměnné, která byla vytvořena jako první. Při procházení zruší všechny objekty s refcount=1. Tato iterace se provádí, dokud takové objekty existují.

V podstatě se tedy dělá to, že a) odstraní všechny nepoužívané objekty v globální tabulce symbolů b) pokud se objeví nové nepoužívané objekty, odstraní je také c) a tak dále. Tento způsob destrukce se používá proto, aby objekty mohly být závislé na jiných objektech v destruktoru. Obvykle to funguje dobře, pokud objekty v globálním oboru nemají komplikované (např. kruhové) vzájemné vazby.

Destrukce globální tabulky symbolů se výrazně liší od destrukce ostatních tabulek symbolů, viz dále. Pro globální tabulku symbolů tedy PHP používá chytřejší algoritmus, který se snaží respektovat závislosti objektů.

ad 2) Ostatní objekty se prochází v pořadí podle jejich vytvoření a zavolá se jejich destruktor. Ano, PHP pouze zavolá __destruct, ale ve skutečnosti objekt nezruší (a dokonce ani nezmění jeho refcount). Pokud se tedy na objekt budou odkazovat jiné objekty, bude stále k dispozici (i když destruktor již byl zavolán). V jistém smyslu budou používat jakýsi „napůl zničený“ objekt.

ad 3) V případě, že je provádění zastaveno během volání destruktorů např. kvůli exit(), zbývající destruktory se nevolají. Místo toho PHP označí objekty za již destruované. Důležitý důsledek je, že volání destruktorů není jisté. Případy, kdy se tak stane, jsou spíše vzácné, ale stát se to může.

Zdroj https://stackoverflow.com/…ucted-in-php


How to write error handler in PHP?

If you are writing your own error handler in PHP, it is absolutely necessary to follow a few rules. Otherwise, it may break the behavior of other libraries and applications that do not expect betrayal in the error handler.

Parameters

The signature of the handler looks like this:

function errorHandler(
	int $severity,
	string $message,
	string $file,
	int $line,
	array $context = null // only in PHP < 8
): ?bool {
	...
}

Parameter $severity contains the error level (E_NOTICE, E_WARNING, …). Fatal errors, such as E_ERROR, cannot be caught with the handler, so the parameter will never have these values. Fortunately, fatal errors have essentially disappeared from PHP and have been replaced with exceptions.

Parameter $message is an error message. If the html_errors directive is enabled, special characters such as < etc., are written as HTML entities, so you must decode them into plain text. However, beware, some characters are not encoded as entities, which is a bug. The displaying of errors in pure PHP is so prone to XSS.

Parameters $file and $line represent the file name and the line where the error occurred. If the error occurred within eval(), the $file will be supplemented with this information.

Finally, parameter $context contains an array of local variables, which is useful for debugging, but has been dropped since PHP 8. To be compatible with PHP 8, omit the parameter or give it a default value.

Return value

The return value of the handler can be null or false.

The value false says that a standard PHP handler will be called. Depending on the configuration, it can print error, log it, etc. It will also make it available to the error_get_last() function.

Suppressed Errors

In PHP, errors can be suppressed (or muted) either by using the shut-up operator @ or using error_reporting():

// suppress errors E_USER_DEPRECATED
error_reporting(~E_USER_DEPRECATED);

// suppress all errors when calling fopen()
$file = @fopen($name, 'r');

Even when errors are suppressed, handler is called. Therefore, it is first necessary to check whether the error is suppressed, and if so, we must end the handler:

if (($severity & error_reporting()) !== $severity) {
	return false;
}

Handler must be terminated in this case with return false to run a standard error handler. It does not print anything and does not log (because the error is suppressed), but it ensures that the error can be returned by error_get_last().

Other Errors

If the handler handles the error (for example, prints its own message, etc.), there is no need to call a standard handler. Although it will not be possible to get the error using error_get_last(), this does not matter in practice, because this function is used mainly in combination with the shut-up operator.

If, on the other hand, the handler does not handle the error, it should return false to not to hide it.


Jak napsat error handler v PHP?

Pokud píšete vlastní error handler pro PHP, je bezpodmínečně nutné dodržet několik pravidel. Jinak může nabourat chování dalších knihoven a aplikací, které nečekají v error handleru zradu.

Parametry

Signatura handleru vypadá takto:

function errorHandler(
	int $severity,
	string $message,
	string $file,
	int $line,
	array $context = null // pouze v PHP < 8
): ?bool {
	...
}

Parametr $severity obsahuje úroveň chyby (E_NOTICE, E_WARNING, …). Pomocí handleru nelze zachytávat fatální chyby, jako třeba E_ERROR, takže těchto hodnot nikdy nebude parametr nabývat. Naštěstí fatální chyby v podstatě z PHP zmizely a byly nahrazeny za výjimky.

Parametr $message je chybová hláška. Pokud je zapnutá direktiva html_errors, jsou speciální znaky jako < apod. zapsány jako HTML entity, takže do podoby plain textu je musíte dekódovat. Ovšem pozor, některé znaky jako entity zapsány nejsou, což je bug. Samotné zobrazování chyb v čistém PHP je tak náchylné na XSS.

Parametry $file a $line představují název souboru a řádek, kde k chybě došlo. Pokud chyba nastala uvnitř eval(), bude $file doplněný o tuto informaci.

A nakonec parametr $context obsahuje pole lokálních proměnných, což představuje pro debugování užitečnou informaci, ale od PHP 8 je zrušený. Pokud má handler fungovat v PHP 8, parametr vynechte nebo mu dejte výchozí hodnotu.

Návratová hodnota

Návratová hodnota handleru může být null nebo false.

Hodnota false říká, že se zavolá ještě standardní PHP handler. Ten podle konfigurace PHP může chybu vypsat, zalogovat atd. Zpřístupní ji také funkci error_get_last().

Potlačené chyby

V PHP lze potlačit zobrazování chyb buď pomocí shut-up operátoru @ nebo pomocí error_reporting():

// potlač chyby úrovně E_USER_DEPRECATED
error_reporting(~E_USER_DEPRECATED);

// potlač všechny chyby při volání fopen()
$file = @fopen($name, 'r');

I při potlačení chyb dojde k volání handleru. Proto je nejprve nutné ověřit, zda chyba je potlačená, a pokud ano, tak musíme zpracování ukončit:

if (($severity & error_reporting()) !== $severity) {
	return false;
}

Zpracování musíme v tomto případě ukončit pomocí return false, aby se spustil ještě standardní error handler. Ten nic nevypíše a nezaloguje (protože je chyba potlačená), ale zajistí, že chybu půjde zjistit pomocí error_get_last().

Ostatní chyby

Pokud handler chybu zpracuje (například vypíše vlastní hlášku atd.), už není potřeba volat standardní handler. Sice pak nebude možné chybu zjistit pomocí error_get_last(), ale to v praxi nevadí, protože tato funkce se používá především v kombinaci s shut-up operátorem.

Pokud naopak handler chybu nezpracuje, měl by vrátit false, aby ji nezatajil.


phpFashion © 2004, 2022 David Grudl | o blogu

Ukázky zdrojových kódů smíte používat s uvedením autora a URL tohoto webu bez dalších omezení.