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.
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
.
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.
::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á.
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ýrazyKonstrukce 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.
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.
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.
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.
intval
, strval
a podobným přibyla i funkce boolval
. Ta se může hodit třeba ve funkci array_map
nebo obecně všude, kam potřebujeme předat callable
. Jinde se dalo už dřív použít přetypování (bool)
.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.curl_share_init
, a třída pro práci s uploadovanými soubory CURLFile
. 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řídy CURLFile
.mysqli_begin_transaction
a funkce pro práci se savepointy. Funkce mysqli_commit
a mysqli_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")
.json_last_error
vracející kód chyby přibyla funkce json_last_error_msg
. Funkce json_encode
po vzoru json_decode
přijímá parametr $depth
.pg_escape_literal
(kterou je vhodné používat místo pg_escape_string
, protože se v dotazu neuvádí apostrofy) a pg_escape_identifier
. Já jsem se obdobu druhé jmenované pokusil přidat do PDO, ale nedotáhnul jsem to do konce.set_error_handler
přijímá null
a funkce set_exception_handler
vrací předchozí handler i při předání null
.cli_set_process_title
a cli_get_process_title
.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.
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.
php_logo_guid
, php_egg_logo_guid
, php_real_logo_guid
a zend_logo_guid
dříve používané pro vykreslení loga na stránce phpinfo
už nejsou potřeba, protože loga se vykreslují pomocí protokolu data:. Tyhle funkce ostatně nemusely být viditelné nikdy, protože stránka phpinfo
si je volala interně./e
funkce preg_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 funkce preg_replace_callback
. Detaily v RFC.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.int
místo osmibajtového.__get
a spol. to umožňují jen pro neexistující vlastnosti, takže v praxi se používají hlavně uživatelské metody get*
a set*
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ů.null
lze předat, pokud je parametr volitelný a jeho výchozí hodnota je null
. 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.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 je zrušena z důvodu spamu.