phpFashion

Rubrika PHP

Escapování – definitivní příručka

Největší programátorský evergreen jsou zmatky a nejasnosti kolem escapování. Neznalost způsobuje, že nejtriviálnější metody narušení webových stránek, jako třeba Cross Site Scripting (XSS) nebo SQL injection, patří bohužel mezi nejrozšířenější.

Escapování je náhrada znaků majících v daném kontextu speciální význam za jiné odpovídající sekvence.

Příklad: do řetězce ohraničeného uvozovkami chceme zapsat uvozovky. Jelikož uvozovky mají v kontextu řetězce speciální význam a jejich prosté zapsání by bylo chápáno jako ukončení řetězce, je potřeba je zapsat jinou odpovídající sekvencí. Jakou přesně určují pravidla kontextu.

Předpoklady

Každá escapovací funkce předpokládá, že vstupem je vždy surový řetězec v určitém kódování (znakové sadě).

Ukládat třeba do databáze řetězce již předem escapované pro HTML výstup a podobně je zcela kontraproduktivní.

S jakými kontexty se setkáváme?

Jak bylo řečeno, escapování převádí znaky mající v určitém kontextu speciální význam. Pro každý kontext se používají jiné escapovací funkce. Tato tabulka je pouze orientační, je nutné si přečíst poznámky níže.

kontext escapovací funkce reverzní funkce
HTML htmlspecialchars html_entity_decode
XML htmlspecialchars
regulární výraz preg_quote
PHP řetězce var_export
MySQL databáze mysql_real_escape_string
MySQL improved mysqli_real_escape_string
SQLite databáze sqlite_escape_string
PostgreSQL databáze pg_escape_string
PostgreSQL, typ bytea pg_escape_bytea pg_unescape_bytea
JavaScript, JSON json_encode json_decode
CSS addcslashes
URL rawurlencode urldecode

Vysvětlení k následujícím poznámkám:

  • řada kontextů má své podkontexty a v nich se escapování liší. Nebude-li řečeno jinak, je uvedená escapovací funkce použitelná plošně bez dalšího rozlišování podkontextů.
  • pod pojmem obvyklá znaková sada se rozumí znaková sada s 1bajtovým nebo UTF-8 kódováním

HTML

V HTML kontextech mají souhrnně speciální význam znaky < & " ' a odpovídající sekvence jsou &lt; &amp; &quot; &#039;. Výjimkou je ovšem HTML komentář, kde má speciální význam jen dvojice --.

K escapování se používá:

$s = htmlspecialchars($s, ENT_QUOTES);

Funguje s libovolnou obvyklou znakovou sadou. Ale nezohledňuje podkontext HTML komentářů (tj. neumí nahradit dvojici -- za něco jiného).

Reverzní funkce:

$s = html_entity_decode($s, ENT_QUOTES, 'UTF-8');

XML / XHTML

XML 1.0 se od HTML liší v tom, že zakazuje použití kontrolních znaků C0 (a to včetně zápisu v podobě entity) s výjimkou tabulátoru, odřádkování a mezery. XML 1.1 tyto zakázané znaky s výjimkou NUL v podobě entit naopak povoluje a dále přikazuje kontrolní znaky C1 s výjimkou NEL taktéž zapisovat jako entity. Dále v XML má speciální význam sekvence ]]>, proto je třeba jeden z těchto znaků také escapovat.

Pro XML 1.0 a libovolnou obvyklou znakovou sadu tak lze použít:

$s = preg_replace('#[\x00-\x08\x0B\x0C\x0E-\x1F]+#', '', $s);
$s = htmlspecialchars($s, ENT_QUOTES);

Regulární výraz

Perlových regulárních výrazech mají souhrnně speciální význam znaky . \ + * ? [ ^ ] $ ( ) { } = ! < > | : - a tzv. delimiter, což je znak ohraničující regulární výraz (např. pro výraz '#[a-z]+#i' je to #). Escapuje se znakem \.

$s = preg_quote($s, $delimiter);

V řetězci, kterým se hledaný výraz nahrazuje (tedy například 2. parametr funkce preg_replace), má speciální význam zpětné lomítko a dolar:

$s = addcslashes($replacement, '$\\');

Kódování musí být buď 1bajtové nebo UTF-8, podle modifikátoru v regulárním výrazu. Viz také Escapování v regulárních výrazech.

PHP řetězce

PHP rozlišuje tyto typy řetězců:

  • v jednoduchých uvozovkách, kde speciální význam mohou mít znaky \ '
  • ve dvojitých uvozovkách, kde speciální význam mohou mít znaky \ " $
  • NOWDOC, kde speciální význam nemá žádný znak
  • HEREDOC, kde speciální význam mohou mít znaky \ $

Escapuje se znakem \. To obvykle provádí programátor při psaní kódu, pro generátory PHP kódu lze využít funkci var_export.

Poznámka: protože zmíněné regulární výrazy se obvykle zapisují uvnitř PHP řetězce, je potřeba zkombinovat obě escapování. Např. znak \ se pro regulární výraz zapíše jako \\ a v řetězci s uvozovkami je třeba psát \\\\.

SQL a databáze

Každá databáze má svou vlastní escapovací funkci, viz tabulka výše. Téměř vždy je ale dostupná jen funkce pro escapování řetězců a tu nelze použít k ničemu jinému, zejména chybí funkce escapující zástupné znaky používané v konstrukcích LIKE (v MySQL jde o znaky % _) nebo identifikátory, jako jsou názvy tabulek či sloupců. Databáze nevyžadují odstraňování escapování na výstupu! (S výjimkou např. typu bytea.)

Znakové sady s neobvyklým vícebajtovým kódováním je nutné v MySQL nastavit funkcí mysql_set_charset resp. mysqli_set_charset.

Doporučuji používat databázový layer (např. dibi, Nette Database, PDO) nebo parametrické dotazy, které escapování obstarají za vás.

JavaScript, JSON

Jakožto programovací jazyk má řadu velmi odlišných podkontextů. K escapování řetězců lze využít vedlejší efekt funkce

$s = json_encode((string) $s);

která navíc obalí řetězec do uvozovek. Striktně vyžaduje UTF-8.

JavaScript zapsaný uvnitř HTML atributů (např. onclick) je nutné ještě escapovat podle HTML pravidel, neplatí to však pro JavaScript uvnitř značek <script>, kde musí být ošetřen pouze případný výskyt koncové značky </script> uvnitř řetězce. To ovšem funkce json_encode zajistí, jelikož JSON escapuje lomítko /. Neošetří však konec HTML komentáře --> (což v HTML nevadí) nebo XML bloku CDATA ]]>, do kterého se skript obaluje. Pro XML/XHTML je řešením

$s = json_encode((string) $s);
$s = str_replace(']]>', ']]\x3E', $s);

Jelikož JSON využívá podmnožinu syntaxe JavaScriptu, je reverzní funkce json_decode plně použitelná jen pro JSON, pro JavaScript omezeně.

CSS

V CSS kontextech je rozsah platných znaků přesně vymezen, pro escapování identifikátorů lze použít například tuto funkci:

$s = addcslashes($s, "\x00..\x2C./:;<=>?@[\\]^`{|}~");

Pro CSS uvnitř HTML kódu platí totéž, co bylo řečeno o JavaScriptu a jeho escapování uvnitř HTML atributů a značek (zde se jedná o atributy style a značky <style>).

URL

V kontextu URL se escapuje vše kromě písmen anglické abecedy, číslic a znaků - _ . nahrazením za % + hexadecimálně vyjádřený bajt.

$s = rawurlencode($s);

Podle RFC 2718 (z roku 1999) nebo RFC 3986 (z roku 2005) je preferován zápis znaků v kódování UTF-8.

Reverzní funkcí je v tomto případě urldecode, která rozeznává i znak + s významem mezery.


Pokud se vám zdá celá problematika příliš složitá, nezoufejte. Brzy přijdete na to, že jde vlastně o jednoduché tranformace a celý trik spočívá v uvědomění, v jakém kontextu se nacházím a jakou musím pro něj zvolit funkci. Nebo ještě lépe, zkuste použít inteligentní šablonovací systém, který dokáže kontexty rozeznat sám a použít správné escapování: Latte

před 16 lety v rubrice PHP


Všechno, co jste kdy chtěli vědět o PHP…

…ale nebylo kde se zeptat.

Nejsem příliš nakloněn tomu, aby se v diskusních fórech Nette nebo Dibi řešily obecné otázky programování, PHP a vůbec. Diskutéry jsem vždy odkázal do patřičných míst. Například na Builder, diskusi.JakPsátWeb, WebTrh či Interfórum. Jednoho dne jsem se podíval, kam mé anchory vlastně míří, a pochopil jsem, že žádné z fór není hodno být PHP fórem, že nereflektuje to, čím PHP bezesporu je. Tedy populárním a nepřeberným semeništěm drobných, zásadních, zpětně kompatibilních a zcela nových chyb. No a také mi vadila absence obarvování PHP kódu.

Vzhledem k tomu, že disponuji jak softwarovým vybavením, tak volnou doménou, rozhodl jsem se oželet jeden díl Simpsonových a věnovat 21 minut zprovoznění nového českého PHP fóra na adrese

http://forum.php7.org

Fórum je rozděleno na sekce:

  • Programování věnované obecným problémům s jazykem a interpretem PHP, jakož i spřízněným oblastem jako je mod_rewrite, .htaccess, JavaScript nebo AJAX.
  • Design, frameworky, knihovny pro pokročilé a zkušené matadory, kteří i přesto setrvávají u PHP.
  • SQL, databáze
  • Různé, kde je malá burza práce nebo informace, jak správně zapisovat kód na fóru.

Třeba se fórum ujme a bude někomu užitečné. Uvidíme. Tož endžoj.

před 16 lety v rubrice PHP


PHAR - PHP v kompaktním balení

Je tomu právě tři a půl roku, kdy jsem distribuci Texy doplnil o tzv. kompaktní verzi. Zjednodušeně se dá říci, že jde o skript vzniklý spojením všech souborů tvořících knihovnu do jednoho jediného a vynecháním komentářů a nadbytečných mezer. Výsledkem je funkčně identický kód, jen s krapet hustší konzistencí. (Prostě je hustéééj. Jak Salko!)

Záměrem bylo zjednodušit nahrávání knihovny na server – uploadovat desítku malých souborů přes FTP bývá značně pomalejší, než nahrát soubor jeden. Jako vedlejší efekt se ukázalo, že jednosouborová knihovna znatelně zvyšuje výkon aplikace – klidně o 30 %. Používání kompaktních verzí se ujalo a od té doby většina bugreportů hlásila chybu na řádce 1 🙂

S příchodem jmenných prostoru v PHP 5.3 se generování kompaktních verzí poněkud zkomplikovalo – zatím totiž platí, že jeden soubor může obsahovat jen jeden jmenný prostor už to neplatí. To znamená, že „jednosouborová“ verze bude mít přinejmenším tolik souborů, kolik je v knihovně jmenných prostorů, plus případné další soubory řešící problémy se vzájemnou závislostí mezi prostory.

Zároveň však PHP 5.3 přichází s novinkou, která si klade za cíl vnést do kompaktních verzí nový svěží vítr: PHAR – PHP archive. Ostatně myslím, že promluvím za nás všechny, když řeknu: nemusí pršet, hlavně když kape!

Jak takový PHAR balíček vytvořit? Nejprve je nutné v PHP.INI zapnout extension=php_phar.dll a nastavit direktivu phar.readonly = Off. Následující kód zkonvertuje celý Nette Framework do souboru nette.phar

$phar = new Phar('nette.phar');

$phar->buildFromDirectory('libs/Nette'); // enter here path to Nette

$phar->setStub("<?php
Phar::mapPhar('nette.phar');
require 'phar://nette.phar/loader.php';
__HALT_COMPILER();");

$phar->compressFiles(Phar::GZ); // or Phar::BZ2

Nejprve se vytvoří nekomprimovaný archiv ze všech souborů v určeném adresáři a jeho podadresářích – jde o obdobu TAR archivu. Následně se definuje tzv. stub neboli zaváděcí program. Jeho úkolem bude načíst soubor loader.php, který je součástí Nette Frameworku a nalézá se vlastně uvnitř PHAR archivu. Stub musí být ukončen zvoláním __HALT_COMPILER(). Poté je možno archiv zkomprimovat.

Příklad použití:

require 'nette.phar';

echo Html::el('strong')->setText('It works!');

Vypadá to báječně, že? Ovšem podívejme se na věc podrobněji. PHP skripty nejsou v archivu nijak minimalizované, nejsou z nich odstraněny zbytečné komentáře ani mezery. To je potřeba udělat ručně. Takže zatímco komprimovaný nette.phar má nějakých 180 kB, stejný archiv vygenerovaný z kompaktní verze Nette (tj. místo adresáře libs/Nette použijeme libs/Nette.compact) váží pouhých 50 kB. Nicméně pokud si uvědomíme, že

  • minimalizace kódu vede k rychlejšímu parsování (závislost bude dost možná lineární)
  • dekomprimace naopak nutně zpomaluje

dojdeme k závěru, že ideální bude generovat PHAR rovnou z kompaktní verze a ukládat jej nekomprimovaný. Nojo – ale proč teda rovnou nezůstat u kompaktní verze? Bez PHAR režie navíc?

Důvod existuje. Kompaktní verze se vždy načte celá, zatímco PHAR lze parsovat a kompilovat po částech. Takže Zend Framework bude vhodnější zabalit do PHARu, u dibi nebo Texy si můžeme dovolit plný výkon kompaktní verze.


Singleton Sofie S.

Slíbil jsem přijít se silným lidským příběhem. Příběhem programátorky, která stojí před těžkou životní volbou. Její jméno je Sofie.

Jak už jste pochopili z druhé věty, Sofie nebyla právě pohledná žena. Zpoza prořídlých neupravených kadeří na vás přes velké umaštěné brýle hleděly dioptriemi zvětšená drobná očka, kostěné obroučky marně skrývaly uhří farmu táhnoucí se podél líček až ke knírku. V redakci, kde pracovala, platila za velmi zdatnou programátorku. Na svůj redakční systém byla náležitě pyšná, snad ještě víc, než na titul Miss Second Life 2007. Pýcha byla oprávněná, kód systému byl tak křišťálově čistý, že ani křišťál křišťálově čistější být nemůže.

Ačkoliv měla Sofie z návrhových vzorů velmi ráda právě singleton, jednalo se spíš o spřízněnost duchovní – v kódu jej používala umírněně, jen tam, kde se skutečně hodil. Například pro zapouzdření HTTP požadavku:

/**
 * @copyright Sofie S., 2006
 */
class HttpRequest
{
	/********************* singleton *******************/

	private static $instance;

	private function __construct()
	{}

	public static function getInstance()
	{
		if (self::$instance === null) {
			self::$instance = new self;
		}
		return self::$instance;
	}

	private function __clone()
	{}

	private function __wakeup()
	{}

	/********************* HTTP request encapsulation *******************/

	public function getRemoteAddress()
	{
		return isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : null;
	}

	...
}

// užití
if (HttpRequest::getInstance()->isPost()) {
	echo 'Hey mister POSTman!';
}

To pro představu snad stačí. Víc kódu ani ukázat nemůžu, protože nemám Sofiin souhlas a mazat kopyrajty si mi nechce. Ale určitě se shodneme na tom, že zapouzdření HTTP požadavku do formy singletonu je perfektní volba:

  • program v PHP vždy obsluhuje jen jeden HTTP požadavek (a právě jeden)
  • je praktická jeho globální dostupnost
  • objekt se hezky líně inicializuje až při prvním použití

Klíčový je samozřejmě první bod. Mohlo by se stát, že by v budoucnu bylo potřeba operovat nad dvěma HTTP požadavky? To bezpochyby ne, odpověděla si Sofie už v době návrhu systému a volba singletonu byla jasná věc.

Nemá smysl zastírat, že se Sofii líbil jeden redaktor z vedlejší kanceláře. Věděli to všichni. Ona z něj byla doslova hotová! Stačilo, aby prošel kolem jejího počítače a rvala si rodidla. Proto když jednoho pondělního rána našla na stole vzkaz, že s ní chce o něčem mluvit, málem štěstím omdlela.

Řeč se týkala redakčního systému. Eman nešetřil chválou a dokonce prohlásil, že Sofie je jejich poklad a co by si bez ní počal. Pak se jí, snad omylem, letmo dotkl ramene. Sofie pocítila v podbřišku slastné zachvění. „Je tu něco, co jsem ti chtěl už dávno říct,“ pohlédl jí Eman do očí, „když mám dlouho otevřený článek a pak kliknu na uložit, tak se mi objeví: byl jste automaticky odhlášen, bla bla bla, a celý článek je v řiti. Můžeš s tím něco udělat?“

Nebylo na světě nic, co by Sofie pro Emana neudělala. Ihned se pustila do práce.

V podstatě šlo o banální úkol. Dokonce tuhle slyšela, že stejný problém dokázali po dvou letech vyřešit borci na jednom blogovém serveru, to by bylo, aby si s tím ona během půl hodinky neporadila! Princip je jednoduchý: v okamžiku, kdy se detekuje vypršení časového limitu a uživatel se automaticky odhlásí, tak se objekt s aktuálním HTTP požadavkem, který představuje třeba právě odeslání formuláře, uloží do session. A pak stačí jen upravit přihlašovací rutinu, aby po úspěšném přihlášení, je-li v session uložený tento objekt, tak se nastavil jako aktuální a vykonal. Je to otázka pár řádek kódu a za dvacet minut už bude sedět u Emana a předvádět mu, jak to báječně funguje. Dají si společně šálek kávy, plánovala si. A možná … možná ji bude chtít pozvat na skleničku, ačkoliv ona sklo nerada. Ale Emanovi neumí říci ne.

Na kávu ten den nedošlo. Zrada přišla z míst, odkud je Sofie nečekala. Zradil ji singleton. Sofie musela povolit serializaci objektu HttpRequest, musela vyřešit prohození těchto objektů v metodě getInstance, ale především se musela smířit s faktem, že na ten krátký okamžik, kdy se uložený požadavek obnovil ze session, tak na tu chvíli existovaly požadavky dva. I singleton tvořil pár. Jen ona zůstala sama.


Je singleton zlo?

Singleton je jedním z nejpopulárnějších návrhových vzorů. Jeho úkolem je zajistit existenci pouze jediné instance určité třídy a zároveň poskytnout globální přístup k ní. Pro úplnost malý příklad:

class Database
{
	private static $instance;

	private function __construct()
	{}

	public static function getInstance()
	{
		if (self::$instance === null) {
			self::$instance = new self;
		}
		return self::$instance;
	}

	...
}

// singleton je globálně dostupný
$result = Database::getInstance()->query('...');

Typickými rysy jsou:

  • privátní konstruktor, znemožňující vytvoření instance mimo třídu
  • statická vlastnost $instance, kde je unikátní instance uložená
  • statická metoda getInstance(), která zpřístupní instanci a při prvním volání ji vytvoří (lazy loading)

Jednoduchý a snadno pochopitelný kód, který řeší dva problémy objektového programování. Přesto v dibi nebo Nette Framework žádné singletony nenajdete. Proč?

Unikátnost jen zdánlivá

Podívejme se na kód pozorněji – skutečně ručí za existenci pouze jedné instance? Obávám se, že nikoliv:

$dolly = clone Database::getInstance();

// nebo
$dolly = unserialize(serialize(Database::getInstance()));

// nebo
class Dolly extends Database {}
$dolly = Dolly::getInstance();

Proti tomu existuje obrana:

	final public static function getInstance()
	{
		// finální getInstance
	}

	final public function __clone()
	{
		throw new Exception('Clone is not allowed');
	}

	final public function __wakeup()
	{
		throw new Exception('Unserialization is not allowed');
	}

Jednoduchost implementace singletonu je ta tam. Ba co hůř – s každým dalším singletonem se nám bude opakovat kusanec stejného kódu. Také třída najednou plní dva zcela odlišné úkoly: kromě svého původního úkolu se stará o to být dost single. Obojí je varovný signál, že něco není v pořádku a kód by si zasloužil refaktoring. Vydržte, za chvíli se k tomu vrátím.

Globální = ošklivé?

Singletony poskytují globální přístupový bod k objektům. Není nutné si odkaz na ně neustále předávat. Zlí jazykové však tvrdí, že taková technika se nijak neliší od používání globálních proměnných a ty jsou přece čiré zlo.

(Pokud metoda pracuje s objektem, který jí byl nějak explicitně předán, parametrem nebo jde o proměnnou objektu, říkám tomu „drátové spojení“. Pokud pracuje s objektem, který si získá přes globální bod (např. skrz singleton), říkám tomu „bezdrátové spojení“. Docela fajn analogie, ne?)

Zlí jazykové se v jedné věci mýlí – na „globálním“ není a priori nic špatného. Stačí si uvědomit, že název každé třídy a metody není nic jiného, než globální identifikátor. Mezi bezproblémovou konstrukcí $obj = new MyClass a kritizovanou $obj = MyClass::getInstance() není ve skutečnosti zásadní rozdíl. Tím méně u dynamických jazyků jako je PHP, kde lze psát $obj = $class::getInstance().

Co ale může způsobit bolení hlavy, to jsou

  1. skryté závislosti na globálních proměnných
  2. nečekané používání „bezdrátových spojení“, které z API tříd nevyčtete (viz Singletons are Pathological Liars)

První bod se dá eliminovat, pokud se nebudou singletony chovat jako globální proměnné, ale spíš jako globální funkce či služby. Jak tomu rozumět? Vezměte si třeba google.com – hezký příklad singletonu jakožto globální služby. Existuje jedna instance (fyzická serverová farma kdesi v USA) globálně dostupná přes identifikátor www.google.com. (Dokonce ani clone www.google.com nefunguje, jak zjistil Microsoft, maj to holt kluci vychytaný.) Důležité je, že tato služba nemá skryté závislosti typické pro globální proměnné – vrací odpovědi bez neočekávatelné souvislosti s tím, co před chvíli hledal někdo úplně jiný. Naopak nenápadná funkce strtok trpí vážnou závislostí na globální proměnné a její používání může vést k velmi těžko odhalitelným chybám. Jinými slovy – problém není v „globálnosti“, ale v návrhu.

Také druhý bod je čistě záležitostí návrhu kódu. Není chybou použít „bezdrátové spojení“ a přistoupit ke globální službě, chybou je to dělat proti očekávání čili skrytě. Programátor by měl přesně vědět, jaký objekt využívá které třídy. Poměrně čistým řešením je mít v objektu proměnnou odkazující na objekt služby, která se inicializuje na globální službu, pokud programátor nerozhodne jinak (technika convention over configuration).

Unikátnost může být na škodu

K singletonům se váže problém, na který narazíme nejpozději při testování kódu. A tím je potřeba podstrčit jiný, testovací objekt. Vraťme se ke Google jakožto ukázkovému singletonu. Chceme otestovat aplikaci, která jej využívá, jenže po pár stech testech začne Google protestovat We're sorry… a jsme kde? A jsme někde. Řešením je pod identifikátor www.google.com podstrčit fiktivní (mock) službu. Potřebujeme upravit soubor hosts – jenže (zpět z analogie do světa OOP) jak toho dosáhnout u singletonů?

Jednou z možností je implementovat statickou metodu setInstance($mockObj). Ale ouha! Copak asi chcete slečně metodě předat, když žádná jiná instance, než ona jedna jediná, neexistuje?

Jakýkoliv pokus odpovědět na tuto otázku povede nevyhnutelně k rozpadu všeho, co singleton dělá singletonem.

Pokud odstraníme restrikce na existenci jen jedné instance, přestane být singleton single a my řešíme pouze potřebu globálního úložiště. Pak je nabíledni otázka, proč v kódu stále opakovat stejnou metodu getInstance() a nepřesunout ji do extra třídy, do nějakého globálního registru?

Anebo ponecháme restrikce, pouze identifikátor v podobě třídy nahradíme za interface (DatabaseIDatabase), čímž se vynoří problém nemožnosti implementovat IDatabase::getInstance() a řešením je opět globální registr.

O pár odstavců výše jsem sliboval vrátit se k otázce opakujícího se kódu ve všech singletonech a možnému refaktoringu. Jak vidíte, problém se mezitím vyřešil sám. Singleton zemřel.


Řetězení výjimek v PHP

Dobře utajenou novinkou PHP 5.3 je řetězení výjimek (exception chaining). Tuto vlastnost můžete znát třeba z Javy (caused exception) nebo ASP.NET (inner exception). O co jde? Každá vyhozená výjimka se může odkazovat na jinou výjimku, která její vznik zapříčinila. Jinými slovy, je možné zachycenou výjimku zabalit do obecnější výjimky.

function loadFile($file)
{
	if (!is_file($file)) {
		throw new FileNotFoundException("File '$file' not found.");
	}
	...
}


function getConfig()
{
	try {
		$s = loadFile('config.ini');
		...
	} catch (FileNotFoundException $e) {
		// obalíme výjimku
		throw new ConfigException("Missing configuration.", 0, $e);
	}

}


try {
	$config = getConfig();
} catch (ConfigException $e) {
	echo $e;
}

Jak vidíte, konstruktor třídy Exception se dočkal rozšíření o třetí nepovinný parametr, kterým lze zřetězenou výjimku nastavit. Získat ji můžeme metodou getPrevious(). Zřetězená výjimka se zahrne i do textového výpisu:

exception 'FileNotFoundException' with message 'File 'config.ini' not found.' in demo.php:8
Stack trace:
#0 demo.php(17): loadFile('config.ini')
#1 demo.php(28): getConfig()
#2 {main}

Next exception 'ConfigException' with message 'Missing configuration.' in demo.php:21
Stack trace:
#0 demo.php(28): getConfig()
#1 {main}

Všimněte si, že výjimky se poněkud nešťastně vypisují v pořadí, v jakém vznikly. Očekával bych pořadí opačné.

před 17 lety v rubrice PHP


PHP, lambda funkce a closures

Zdá se, že PHP 5.3 bude ještě větší bomba, než jsme si mysleli. Do dlouho očekávané a neustále oddalované verze se na poslední chvíli dostaly lambda funkce a tzv. closures. Český ekvivalent termínu neznám, ale dalo by se použít anonymní nebo lambda funkce. Pokud máte zkušenost s JavaScriptem nebo jazykem Ruby, budou vám připadat velmi povědomé:

$lambda = function () {
	echo 'Hello World!';
};

$lambda(); // vypíše Hello World!

Lambda funkce najdou uplatnění především tam, kde potřebujeme vytvořit callback na jedno použití. Příkladem jsou třeba volání preg_replace_callback nebo array_map, které bylo doposud spojeno s nutností deklarovat funkci, která však pro zbytek programu byla zcela zbytečná. Od PHP 5.3 ji bude možné nahradit lambda funkcí:

// convert to camelCaps
$s = 'hello-world';
$s = preg_replace_callback(
	'#-([a-z])#',
	function ($matches) { return strtoupper($matches[1]); },
	$s
);
echo $s; // vypíše helloWorld

Closures rozšiřují schopnosti lambda funkcí tak, že jim zpřístupní (vybrané) proměnné z kontextu, ve kterém jsou deklarované:

function getMultiplier($product)
{
	return function ($value) use ($product) {
		return $value * $product;
	};
}

// $multi23 je "násobička 23"
$multi23 = getMultiplier(23);
echo $multi23(3); // vypíše 69

Jiným příkladem je přesměrování výstupu do souboru:

function redirect($file)
{
	$handle = fopen($file, 'w');
	ob_start(function($buffer) use ($handle) {
		fwrite($handle, $buffer);
	});
}

redirect('output.html');
echo 'Hello World!'; // místo na obrazovku se vypíše do souboru

Jak to funguje interně? Lambda funkce a closures jsou representovány objektem finální třídy Closure s jedinou magickou funkcí __invoke(). Ta sama o sobě zajišťuje, že s objekty spolupracují interní funkce a lze je použít všude tam, kde se očekává pseudotyp callback. Také is_callable($lambda) vrací true. Zapouzdření callbacku tak dostává nový rozměr:

class Salute
{
	public function __invoke($name)
	{
		echo "Hello $name!";
	}
}

$salute = new Salute;
$salute('World'); // vypíše Hello World!

Jsou lambda funkce v PHP skutečně tak převratnou novinkou? V PHP je totiž lze do jisté míry nahradit skrze eval nebo create_function. Jenže za jakou cenu – tělo funkce je nutné zapsat jako řetězec, což je nepřehledné a nefunguje zvýrazňování kódu v IDE, takové funkce nelze debugovat, je znemožněno použití opcode cache atd. Dá se říci, že lambda funkce legalizují osm let starou funkci create_function pro použití ve zdravém kódu.

Podrobný popis najdete v propozicích a můžete si je rovnou i vyzkoušet – stačí stáhnout vývojovou verzi PHP 5.3.


Pište na Twitter z adresního řádku prohlížeče

Timy přišel s geniálním nápadem, jak psát na Twitter přímo z adresního řádku prohlížeče.
Tedy podobně, jako když googlíte pomocí „g keyword“, můžete štěbetat způsobem „tw Ach jo, dostala jsem zase krámy.“ Kromě toho, že je to pohodlné, to navíc řeší problém – pokud se zprávu nepodaří odeslat, máte šanci to zkusit znovu. V případě ajaxového odesílání přímo ze stránek Twitteru se totiž nezřídkakdy stane, že zpráva odejde do věčných lovišť. Celý svět pak zůstane ochuzen o váš 140 znakový elaborát.

Jak na to? Jednak můžete využít přímo Timyho formulář, ale protože mi nevyhoval, a také protože nechci prozrazovat své přihlašovací údaje, udělal jsem si vlastní. Stáhněte si knihovničku Twitter for PHP a vytvořte skript twitter-send.php:

<?php
header('Content-type: text/html; charset=utf-8');

if (isset($_POST['message'])) {
	require_once 'twitter.class.php';

	// SEM VLOŽTE SVÉ PŘIHLAŠOVACÍ ÚDAJE
	$twitter = new Twitter('DavidGrudl', '******');
	$status = $twitter->send($_POST['message']);

	if ($status) {
		header('Location: http://twitter.com/home');
		exit;

	} else {
		echo '<body style="background:red"><h1>Chyba</h1>';
	}
}

?>
<h1>Send message to Twitter</h1>

<form action="#" method="post">
<textarea name="message" cols="100"><?php
echo @htmlSpecialChars($_POST['message']) ?></textarea>
<br><input type="submit">
</form>

Oba soubory nahrajte na svůj server (klidně i lokální) a otevřete twitter-send.php v prohlížeči. Nyní s ním asociujte vyhledávací klíč (tedy jako pro Google je „g“, tak pro tento skript bude např. „tw“). Popis definice asociace ve Firefoxu nebo IE nechám na komentátorech, protože to sám neumím. V Opeře je to třeskutě jednoduché: klikněte pravým tlačítkem myši na textové pole, zvolte Vytvořit vyhledávač… a do okénka Klíč zadejte „tw“ a potvrďte.

To je vše! Příjemné štěbetání.


Jepičí život objektů

Programátoři, kteří se k PHP dostanou od jiných jazyků, se snaží předchozí zkušenosti adaptovat na PHP a tak někdy vznikají zlozvyky. Typickým příkladem je chápání referencí jakožto ukazatelů. Programátor se snaží optimalizovat výkon aplikace tím, že proměnné předává funkcím přes reference. Jenže, PHP nemá ukazatele a ve skutečnosti tím může aplikaci naopak zpomalit. Tyhle chyby jsem samozřejmě dělal také. Zkušenosti z jiných jazyků se mohou v novém prostředí vymstít.

Další věc, na kterou se hledí s nedůvěrou, je vytváření „jepičích objektů“, tedy objektů, které jen splní drobnou funkci nebo předají data a hned zase zaniknou. Programátoři uvažují, zda-li není možné objekt nahradit polem. Mají totiž zažitou představu, že vytvoření objektu je zatíženou velkou režií.

Ve skriptovacích jazycích tomu tak nebývá. Kdyby už PHP 4 bylo koncipované jako objektové, výkonnostní rozdíl mezi objektovým a neobjektovým přístupem by neexistoval. Leč z historických důvodů tu rozdíl je, objektovost je o něco pomalejší. S každou další verzí mám pocit, že je zanedbatelnější. Za zanedbatelný se dá považovat i rozdíl mezi vytvořením objektu a pole:

$obj = new Object(10);
$obj->val = 10;

// versus ekvivalent

$arr = array('__class' => 'Object');
__construct($arr, 10);
$arr['val'] = 10;

Zkusil jsem změřit obě operace a výsledek je 2.0E-6 sec : 1.8E-6 sec. Nemá pak smysl pozdvihnout obočí nad tím, že nějaká operace „zbytečně“ vytvoří objekt, jen aby předala data. Je to totiž totéž, jako když se vytváří jepičí pole:

$s = preg_replace_callback($pattern, array($obj, 'method'), $s);

Můžete namítnout, že výše uvedené konstrukce nejsou pro pole vůbec typické. Jistě, použil jsem je jen proto, abychom srovnávali funkčně podobný kód. V praxi je na rozhodnutí programátora zvolit, kdy použít pole a kdy objekt. Já se jen snažím ukázat, že do volby by neměly zasahovat mylné představy o (ne)výkonnosti.


foreach pikoška

Na stránce www.phpbench.com, která je bohužel plná zavádějících měření, jsem narazil na zajímavou konstrukci:

foreach ($arr as $key[] => $val[]);

Funguje to od PHP 4 a jde vlastně o obdobu

$key = array_keys($arr);
$val = array_values($arr);

Vůbec jsem netušil, že za as může následovat podobný výraz. Podíval jsem se do souboru zend_language_parser.output (najdete ve zdrojácích PHP) a ono je možné dokonce vytvářet konstrukce jako foreach ($arr as $key[fce($obj->x)] => $val[$y]). Praktické využití mě nenapadá, dívky tím neohromíte, ale blognout jsem to musel 🙂

Ať je alespoň něčím tento spot užitečný: víte, že funkce array_keys má tři parametry?


Ještě ke stránce The PHP Benchmark: snaha autora i vizuální provedení je rozhodně chválihodné, bohužel některé výsledky bez patřičného komentáře vyvolávají dojem, že ta či ona konstrukce je zbytečně pomalá. Pro pochopení je ale potřeba důkladná znalost vnitřností PHP (viz Půvab optimalizace rychlosti, Černá magie optimalizace, Derick Rethans: Understanding the PHP Engine).


phpFashion © 2004, 2025 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í.