Yield a další novinky PHP 5.5
Největší novinkou právě vydané verze PHP je operátor yield
, který dovoluje pozastavit provádění funkce a od stejného místa pokračovat později. Kromě toho přibylo několik příjemných syntaktických novinek a řada nových funkcí. Některé zátěže minulosti byly také označeny jako zastaralé nebo byly přímo odstraněny.
Článek vyšel na serveru Zdroják.
Operátor yield
Operátor yield
dovoluje vytvářet generátory – funkce generující hodnoty, které můžeme používat v kódu, ze kterého generátor voláme. Klasickou ukázkou použití generátorů je funkce xrange()
. Ta stejně jako vestavěná funkce range()
vrací hodnoty z nějakého rozsahu, ale na rozdíl od této funkce to dělá průběžně – nepotřebuje výsledek uložit do paměti:
<?php
function xrange($start, $end, $step = 1) {
for ($i = $start; $i <= $end; $i += $step) {
yield $i;
}
}
foreach (xrange(1, 5) as $i) {
echo "$i\n";
}
// Po dobu 0.1 sekundy vypisuje čísla.
$start = microtime(true);
foreach (xrange(1, INF) as $i) {
if (microtime(true) - $start > .1) {
break;
}
echo "$i\n";
}
?>
Možnosti využití tohoto operátoru jsou mnohem bohatší. Řekněme, že chceme spustit sadu dlouhotrvajících testů a jejich výsledky vypisovat. Logiku pro provedení testu a vypsání jeho výsledku máme samozřejmě oddělenou. Nejjednodušší řešení je spustit všechny testy, výsledky si uložit do pole a to na konec projít a výsledky vypsat. Nevýhody jsou zřejmé – na první výsledek čekáme, až dokud nedoběhnou všechny testy, a potřebujeme paměť na uložení všech výsledků. S operátorem yield
můžeme výsledky vracet a vypisovat průběžně. Samozřejmě to není jediné možné řešení – funkci pro výpis výsledků můžeme např. předat do spouštěče testů a volat ji z něj. Řešení s operátorem yield
je ale elegantnější.
Operátor yield
je také jedním z důležitých prvků pro výkon Facebooku. V zásadě všechny funkce, které potřebují nějaká data, před jejich získáním „yieldnou“. Všechny požadavky na data se sdruží, vyřídí najednou a rozdají zpátky funkcím. To dovoluje dramaticky snížit počet komunikací s úložištěm, který díky tomu závisí jen na počtu úrovní na sobě závislých požadavků, nikoliv na celkovém počtu míst, kde nějaká data potřebujeme. Facebook tento operátor používá už dlouhou dobu díky implementaci v kompilátoru HipHop for PHP.
Syntaxe je bohatší, z generátorů lze vracet i klíče a do generátoru můžeme zvenku poslat data, detaily naleznete v PHP manuálu nebo v RFC. Generátor lze detekovat metodou ReflectionFunctionAbstract::isGenerator
.
Blok finally
Blok finally
se používá při ošetřování výjimek a spustí se, ať už k výjimce dojde nebo ne. PHP se bez něj dlouho obešlo, protože se v tomto bloku nejčastěji uvolňuje zabraná paměť a provádí další úklid, který PHP obvykle dělá automaticky. Ale ne o všechen úklid se PHP stará automaticky, takže nyní můžeme tento blok konečně použít.
<?php
try {
$errorMode = $pdo->getAttribute(PDO::ATTR_ERRMODE);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
return $pdo->query($sql);
} catch (RecoverableException $ex) {
// Tady můžeme ošetřit výjimky.
error_log($ex->getMessage());
} finally {
$pdo->setAttribute(PDO::ATTR_ERRMODE, $errorMode);
}
?>
Blok se dal v minulosti emulovat pomocí callbacků, nebo bez nich poněkud krkolomně:
<?php
$caught = null;
try {
$errorMode = $pdo->getAttribute(PDO::ATTR_ERRMODE);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$return = $pdo->query($sql);
} catch (Exception $caught) {
// Tady můžeme ošetřit výjimky.
if ($caught instanceof RecoverableException) {
error_log($caught->getMessage());
$caught = null;
}
}
// Toto je blok finally.
$pdo->setAttribute(PDO::ATTR_ERRMODE, $errorMode);
if ($caught) {
throw $caught;
}
return $return;
?>
Detaily lze opět nalézt v PHP manuálu nebo v RFC.
Název třídy v ::class
Příjemnou novinkou, která se hodí při předávání názvů tříd funkcím je konstanta class
. Užitečná je hlavně u kódu používajícího jmenné prostory.
<?php
namespace N {
class C {
}
f(C::class); // Předá 'N\C'
}
?>
Konstanta nekontroluje, jestli daná třída skutečně existuje, takže ani nespouští autoloading. Detaily v RFC.
list
ve foreach
Cyklus foreach
nyní podporuje konstrukci list
. Když jsem s PHP začínal, tak mě tuhle kombinaci napadlo použít a byl jsem trochu překvapen, že ji PHP nepodporuje. Hodí se tehdy, když se strukturovaná data předávají v číselně indexovaných polích. To po pravdě řečeno není moc často, ale pokud se tak stane, tak si můžeme ušetřit jednu dočasnou proměnnou.
<?php
$fields = array(
array('article', 'id'),
);
foreach ($fields as list($table, $column)) {
echo "$table.$column\n";
}
?>
Detaily opět v manuálu nebo v RFC. Jsem zvědav, jestli PHP někdy bude podporovat i preg_match('/^([^=]+)=(.*)/', $line, list(, $name, $value))
, i když tato konstrukce už je poněkud divoká.
Neskalární klíče ve foreach
U normálních polí PHP dovoluje pro klíče použít pouze celá čísla nebo řetězce. Konstrukce foreach
proto také podporovala pouze přiřazování čísel nebo řetězců do klíče. U iterovatelných objektů ale omezení na klíče neexistuje a mohou v nich být libovolné hodnoty. Na co se něco takového dá použít? Např. NotORM dovoluje k řádkům v tabulce přistupovat pomocí primárního klíče: $db->article[$id]
. Co když je ale primární klíč vícesloupcový? NotORM pro tento případ nabízí syntaxi $db->article_tag[array("article_id" => $article_id, "tag_id" => $tag_id)]
. Někomu to může připadat moc magické, někdo pro to najde smysluplné využití.
Jak novinka vypadá v praxi?
<?php
// AnyKeyArray - https://gist.github.com/vrana/5581788
$article_tags = new AnyKeyArray();
// Tohle šlo už dřív.
$article_tags[array("article_id" => $article_id, "tag_id" => $tag_id)] = $article_tag;
echo $article_tags[array("article_id" => $article_id, "tag_id" => $tag_id)];
// Tohle funguje až v PHP 5.5.
foreach ($article_tags as $key => $val) {
print_r($key);
}
?>
Detaily v RFC. Osobně bych se přimlouval k tomu, aby pole mohlo být klíčem i v normálním poli – neexistuje žádný technický důvod, proč by to tak nemohlo být.
empty()
s výrazy
Konstrukce empty()
testuje, zda je proměnná nenastavená nebo se vyhodnotí jako false
. To je užitečné hlavně u proměnných, které mohou být nenastavené a kde by přístup mimo tuto konstrukci (a mimo isset()
) způsobil chybu. U obecných výrazů používání empty()
není potřeba, protože ty jsou vždycky nastavené a pro test pravdivosti stačí použít obyčejný operátor !
(negace). To je i důvod, proč tuto konstrukci v minulosti bylo možné používat jen s proměnnými.
PHP 5.5 nicméně dovoluje použít empty()
i s obecným výrazem, protože programátoři neznalí těchto interních detailů nechápou, proč empty($var)
funguje a empty(f())
ne. Někomu se kód používající empty()
místo !
může zdát i čitelnější, to je ale subjektivní. Konstrukci isset()
zůstalo původní chování a lze ji i nadále používat jen s proměnnými. Dokumentace v manuálu nebo v RFC.
Já osobně se konstrukci empty()
budu až na výjimky i nadále vyhýbat. Důvodem je právě potlačení chyby v případě neexistence proměnné:
<?php
$array = array(null);
$false = empty($aray[0]); // Na tento překlep nás PHP neupozorní.
$false = !$aray[0]; // O tomto překlepu se dozvíme.
$null = isset($aray[0]); // O tomto překlepu se nedozvíme.
$null = ($aray[0] === null); // O tomto překlepu se dozvíme.
$empty = empty($aray[-1]); // Nedozvíme se.
$empty = !array_key_exists(-1, $aray) || !$aray[-1]; // Dozvíme se.
?>
Poslední příkaz je nicméně poněkud krkolomný a empty()
bych pro něj použil raději. Kontrolu neexistujících proměnných je ostatně lepší nechat nezávislé statické analýze, protože PHP to stejně dělá v mnoha směrech nedokonale.
Přístup k prvkům konstantních polí a bajtům řetězce
Droboučkou změnou bez valného smyslu a využití je povolení operátoru []
pro přístup k prvkům pole a bajtům řetězce i u konstantních hodnot.
<?php
$a = array(1, 2)[0];
?>
Nenapadá mě, proč by někdo chtěl něco takového dělat, na druhou stranu ale ani není žádný vážný důvod, proč by to mělo být zakázané. Detaily v RFC.
Práce s hesly
Pro ukládání hesel se obvykle používá hašování. Z několika důvodů to ale není tak jednoduché, jak to vypadá. V první řadě je potřeba k heslu přidat náhodnou sůl, aby nebylo poznat, že dva uživatelé mají stejné heslo. Další problém je v tom, že běžné hašovací funkce jako md5
a sha1
jsou moc rychlé, takže dovolují hrubou silou prozkoumat velké množství hašů, ať už jsou osolené nebo ne. Aplikace to obvykle řeší tak, že své uživatele týrají požadavky na velmi dlouhá a složitá hesla. Uživatelé si potom heslo nepamatují, napíšou si ho na papírek a ten přilepí na monitor. Protože jsou počítače pořád rychlejší a přístup k obrovskému výpočetnímu výkonu je čím dál jednodušší, tak by aplikace správně měly požadovat čím dál složitější hesla.
Lepší řešení je použít pomalou hašovací funkci jako např. Blowfish, která navíc dovoluje náročnost výpočtu stanovit parametrem. Generování náhodné soli také není úplně triviální. PHP 5.5 proto přináší dvě jednoduché funkce password_hash
a password_verify
, které heslo zahašují resp. haš zkontrolují. Detaily jsou v manuálu nebo v RFC.
<?php
$password = 'Passw0rd';
$start = microtime(true);
$hash = password_hash($password, PASSWORD_DEFAULT);
echo (microtime(true) - $start) . "\n";
$start = microtime(true);
var_dump(password_verify($password, $hash));
echo (microtime(true) - $start) . "\n";
?>
K dispozici je i uživatelská knihovna pro PHP >= 5.3.7, takže lze tyto funkce začít ihned používat.
V oblasti hašování je ještě jedna novinka – funkce hash_pbkdf2
a openssl_pbkdf2
používající algoritmus PBKDF2 schválený úřadem NIST.
Funkce array_column
Funkce array_column
ze seznamu polí vytáhne jen určité sloupce a vrátí je v novém poli. Používá se typicky u dat vrácených z databáze, ze kterých chceme vytvořit seznam hodnot nebo číselník.
<?php
$articles = $pdo->query("SELECT * FROM article")->fetchAll();
// Vytvoří pole [ $id, ... ].
$ids = array_column($articles, 'id');
// Vytvoří pole [ $id => $title, ... ].
$titles = array_column($articles, 'title', 'id');
// Vytvoří pole [ $id => $article, ... ].
$articles = array_column($articles, null, 'id');
?>
Důležité je podotknout, že pokud nám stačí jen jeden nebo dva sloupce, tak si můžeme vytáhnout jenom je příkazem $statement->fetchAll(PDO::FETCH_COLUMN)
resp. $statement->fetchAll(PDO::FETCH_KEY_PAIR)
. Pokud nicméně potřebujeme pracovat s kompletními záznamy a k tomu i jen s určitým sloupcem, tak je tato funkce velmi užitečná. Nebo samozřejmě v případě, kdy API pro získání dat není tak bohaté jako PDO.
Další přidané funkce a třídy
- K funkcím
intval
,strval
a podobným přibyla i funkceboolval
. Ta se může hodit třeba ve funkciarray_map
nebo obecně všude, kam potřebujeme předatcallable
. Jinde se dalo už dřív použít přetypování(bool)
. - Třída
DateTimeImmutable
je velmi podobná tříděDateTime
. Jediným rozdílem je, že nová třída nikdy nemění svou hodnotu a místo toho vrací naklonovaný objekt. - Do extenze cURL přibyly nové funkce, především
curl_share_init
, a třída pro práci s uploadovanými souboryCURLFile
. API pro uploadování souborů byla dříve katastrofa – pokud jste volběCURLOPT_POSTFIELDS
předali data začínající zavináčem, tak se část za zavináčem interpretovala jako název souboru. Kromě toho, že se nedala poslat data začínající zavináčem, to vedlo i k bezpečnostním problémům, proto PHP 5.2 vyžaduje předání tohoto řetězce v poli a verze 5.5 to konečně označuje jako zastaralé právě ve prospěch třídyCURLFile
. - Do extenze MySQLi přibyla funkce pro zajájení transakce
mysqli_begin_transaction
a funkce pro práci se savepointy. Funkcemysqli_commit
amysqli_rollback
přijímají nové parametry. Smysl mají tyto funkce při použití extenze mysqlnd_ms pro rozkládání zátěže, jinak se dá zůstat u normálního$mysqli->query("COMMIT")
. - K funkci
json_last_error
vracející kód chyby přibyla funkcejson_last_error_msg
. Funkcejson_encode
po vzorujson_decode
přijímá parametr$depth
. - V extenzi PostgreSQL přibyly funkce
pg_escape_literal
(kterou je vhodné používat místopg_escape_string
, protože se v dotazu neuvádí apostrofy) apg_escape_identifier
. Já jsem se obdobu druhé jmenované pokusil přidat do PDO, ale nedotáhnul jsem to do konce. - Do extenze pro práci s obrázky přibyly nové funkce, především pro oříznutí a změnu velikosti obrázku.
- Funkce
set_error_handler
přijímánull
a funkceset_exception_handler
vrací předchozí handler i při předánínull
. - V CLI lze změnit a získat název procesu funkcemi
cli_set_process_title
acli_get_process_title
.
Zend OPcache
PHP 5.5 obsahuje extenzi Zend OPcache, která kešuje a optimalizuje zkompilovaný mezikód. Dělá tedy část toho, co extenze APC (ta navíc dovoluje pracovat se sdílenou pamětí), podle benchmarků to ale dělá o něco lépe.
Kromě toho přibylo ještě několik drobnějších optimalizací, jak už je ve vývoji PHP zvykem.
Zastarání extenze MySQL
Extenze MySQL je označena jako zastaralá ve prospěch extenzí MySQLi a PDO. O jejím zastarání se mluví už od uvedení PHP 5 (před téměř 9 lety), proto někoho možná překvapí, že velké projekty jako WordPress nebo MediaWiki ji stále používají jako výchozí a často i jediný oficiální způsob připojení k MySQL. I Facebook vesele běží nad touto extenzí.
Extenze MySQLi zkrátka nepřinesla žádnou killer feature – vázání proměnných je v této extenzi zpackané, perzistentní připojení ve srovnání s MySQL naopak dlouho chybělo, vícenásobné dotazy moc programátorů nepotřebuje a v PHP 5.3 uvedené asynchronní dotazy potřebují na každý dotaz jedno otevřené připojení, takže ve většině případů žádnou úsporu nepřinesou. Všechny tyto novinky navíc mohly být snadno doplněny i do extenze MySQL. Plán „přejmenujeme všechny funkce a zpřeházíme jim parametry“ moc uživatelů zkrátka neoslovil.
PDO je na tom o něco lépe, podpora více databázových systémů a rozumnější objektové rozhraní už je přeci jen smysluplná hodnota. I když třeba vázání proměnných má opět vážné nedostatky, takže pro bezproblémové využití je potřeba si stejně postavit nějakou nadstavbu. A když už takovou nadstavbu máme, tak je celkem jedno, co je pod ní.
Detaily a hlasování (mimochodem ne tak jednoznačné jako v ostatních případech) v RFC.
Další zastaralé a odstraněné obraty
- Funkce
php_logo_guid
,php_egg_logo_guid
,php_real_logo_guid
azend_logo_guid
dříve používané pro vykreslení loga na stráncephpinfo
už nejsou potřeba, protože loga se vykreslují pomocí protokolu data:. Tyhle funkce ostatně nemusely být viditelné nikdy, protože stránkaphpinfo
si je volala interně. - Modifikátor
/e
funkcepreg_replace
je nově označen jako zastaralý. S tímto modifikátorem je spousta legrace, protože při jeho použití se druhý parametr interpretuje jako PHP kód. Pokud tedy někoho napadne pro argumenty této funkce použít uživatelský vstup, je o bezpečnost aplikace vystaráno. Problém dokonce až donedávna nastal i při použití uživatelského vstupu jen pro část mezi oddělovači, protože všechno za nulový bajtem se ořízlo. Komplikované je chování i v případě, kdy „matchneme“ uvozovky nebo apostrofy. Přítomnost tohoto modifikátoru považuji za „Perlismus“, navíc nikdy nebyl potřeba, protože jeho činnost lépe zastane funkcepreg_replace_callback
. Detaily v RFC. - Z extenze cURL bylo odstraněno volitelné převzetí obsluhy URL v běžných PHP funkcích. Pokud zavoláte např.
readfile("http://example.com")
, tak PHP naváže komunikaci se vzdáleným serverem. Volbou--with-curlwrappers
šlo nastavit, aby to dělalo cURL. Pokud tuto vlastnost někdo používal, tak většinou omylem. - Windows XP a 2003 už nejsou podporované. Naopak přibyly oficiální buildy pro 64bitová Windows, ty ale bohužel nesmyslně používají čtyřbajtový
int
místo osmibajtového.
Co se nevešlo
- Accessors – byl předložen návrh, který by dovolil přetížit přístup k vlastnostem objektu. Metody
__get
a spol. to umožňují jen pro neexistující vlastnosti, takže v praxi se používají hlavně uživatelské metodyget*
aset*
nadefinované pro každou vlastnost, které kód zanáší spoustem balastu. Syntaxe této novinky byla nicméně trochu podivná. Hlasovalo pro ni 60% vývojářů, ale pro změnu jazyka je potřeba 2/3 hlasů. - Scalar type hints – PHP dovoluje v deklaraci funkce specifikovat požadovaný typ parametrů, jde to ale jen pro objekty, pole a callbacky. Hodnotu
null
lze předat, pokud je parametr volitelný a jeho výchozí hodnota jenull
. Skalární typy (jako řetězce nebo čísla) ani nadále vynutit nejde. HipHop for PHP touto vlastností oplývá a celkem jsem si na ni zvykl. - Return type hints – ani návratový typ funkcí nadále nejde určit. Při návrhu API by se to docela hodilo – interface by mohl určit, jaké očekává návratové hodnoty, a implementace by to musely dodržet. Facebook tohle opět podporuje, i když v jiné syntaxi (návratový typ se uvádí za parametry a dvojtečkou).
Závěr
Novou verzi považuji za nekontroverzní a logické pokračování vývoje PHP. Syntaktické novinky jsou příjemné, nové funkce jsou užitečné, yield
může zcela změnit architekturu vysoce zatížených PHP aplikací, zastaralé a odstraněné obraty vesměs nebudou nikomu chybět.
Smutnou skutečností zůstává, že řada projektů bude moci tyto novinky využít až tak za tři až pět let. Většina hostingů nabízí „časem prověřené“ verze PHP a i na vlastním serveru řada uživatelů volí verzi z distribuce, jejichž správci také bývají velmi konzervativní. Aplikace určené pro nasazení v mnoha instalacích (typicky open source) jsou na tom ještě hůř, protože musí podporovat všechny verze svých uživatelů. Některé projekty teprve teď opatrně odstraňují podporu pro PHP 5.2 (ve prospěch PHP 5.3 uvedeného před čtyřmi lety). Snad se PHP 5.5 uchytí rychleji.
Diskuse
Michal:
Článek ještě nevyšel na serveru Zdroják :-) :-)
Jakub Vrána
:
Už je tam.


Michal:
Super. Rád si totiž čtu články zčerstva hned po vydání :-)trestná smradlavice:
Mám volně související otázku. Je podle vás dobré po vydání nové verze jako je tato hned v tom začít psát - jak kód specifický pro ten který projekt, tak obecné, znovupoužitelné funkce? díky
Jakub Vrána
:
Ano, já jeden projekt pro PHP 5.5 chystám.


O:
Rek bych, ze kdo dokaze vyuzit yield, uz davno v PHP nedela. :-)Jeste par verzi a uz to bude alfa verze normalniho jazyka ^^
Diskuse je zrušena z důvodu spamu.

