Jaké novinky přinese PHP 7.1

PHP 7.0.0 vyšlo teprve před necelým rokem a už se pomalu můžeme těšit na PHP 7.1. Před pár dny byla vydána verze RC3, takže už je čas začít testovat kompatibilitu aplikací.
Nálepky:
Finální verzi můžeme očekávat v listopadu nebo začátkem prosince. Stejně jako u PHP 7.0 není stanovené přesné datum vydání, ale finální verze vyjde, až bude dostatečně stabilní.
S testováním můžete začít snadno – už jsou k dispozici i buildy pro Windows a podpora ve stabilní verzi PHPStormu (stačí přepnout verzi v Settings – Languages & Frameworks – PHP). Pokud chcete porovnat chování se staršími verzemi PHP, tak doporučuji 3v4l.org (neboli php.wtf).
Prošel jsem pro vás novinky implementované v 7.1, prostudoval jsem RFC, vybral ty nejzajímavější a shrnul je i s příklady. Spoustu změn se nese v duchu zvyšování konzistence jazyka a omezování možností střelit se do nohy, u některých zrušených věcí jsem naštěstí ani nevěděl, že to je možné. Z provedených změn je vidět snahu o silnou zpětnou kompatibilitu a případná nekompatibilita je opodstatněná. Upgrade z 7.0 by tedy měl být relativně snadný.
V manuálu už přibyla sekce o upgrade na 7.1, takže si o potřebných změnách můžete přečíst i tam.
Nullable types
V PHP 7.0 přibyla možnost definovat typy parametrů funkcí a jejich návratových hodnot. Nicméně nebylo možné místo parametru s určeným typem předat null
. Obdobně nešlo vrátit null
z funkce s definovaným návratovým typem:
<?php
function foo(string $foo): string
{
return $foo;
}
foo('a'); // OK
foo(null); // TypeError: Argument 1 passed to foo() must be of the type string, null given
Obejít to šlo odebráním definice typů, případně u parametrů pomocí definice: string $foo = null
– nicméně v tu chvíli nejde jen o nullable parametr, ale zároveň je $foo
také nepovinný.
V PHP 7.1 přibyla možnost pomocí ?
označit nullable parametry a návratové hodnoty, takže můžeme bezpečně odlišit nepovinný a nullable parametr:
<?php // https://3v4l.org/mO6vd
function bar(?string $bar): ?string
{
return $bar;
}
var_dump(bar("a")); // string(1) "a"
var_dump(bar(null)); // NULL
var_dump(bar()); // Uncaught ArgumentCountError: Too few arguments to function bar()
Void return type
Od PHP 7.0 bylo možné definovat návratový typ funkce, ale nebylo možné vyžadovat nevrácení žádné hodnoty (což může být užitečné pro prevenci chyb). Teď je to možné pomocí void
:
<?php
function shouldReturnNothing(): void
{
return 1; // Fatal error: A void function must not return a value
}
V takové funkci musíme return
vynechat úplně nebo ho zavolat bez hodnoty: return;
. Vrácení null
pomocí return null;
také není platné.
Chybějící parametr uživatelsky definované funkce vyhodí Error
Pokud jste dříve zavolali funkci s chybějícími parametry, tak se provedla bez nich a zahlásila warning. Nově chybějící parametr funkce vyhodí ArgumentCountError
a funkce se vůbec neprovede.
Zdůrazňují, že se to netýká přímo funkcí v PHP, ale jen těch uživatelsky definovaných – substr();
způsobí stále jen warning.
<?php // https://3v4l.org/F9thD
function test($param)
{
var_dump($param);
}
test(); // Uncaught ArgumentCountError: Too few arguments to function test(), 0 passed and exactly 1 expected
Aritmetické operace s neplatnými stringy vrací warning či notice
V PHP <7.1 bylo možné sčítat jablka s hruškami bez warningů nebo notice:
<?php
var_dump('1' + '1'); // int(2)
var_dump('10 apples' + '5 pears'); // int(15)
var_dump('two apples' + '5 pears'); // int(5)
Pokud v PHP 7.1 použijeme aritmetickou operaci na string, který nelze převést na číslo (např. 'two apples'
) dostaneme Warning: A non-numeric value encountered in %s on line %d
. Pro stringy, ze kterých jde získat číslo ('10 apples'
) je připravena Notice: A non well formed numeric value encountered in %s on line %d
. Řetězec obsahující číslo ('10'
) se bere jako platný a notice nezpůsobí.
Definice viditelnosti konstant
Nově je možné konstanty označit jako private
nebo protected
. To přijde vhod, pokud chcete konstantu použít jen uvnitř třídy a nechcete, aby na ní někdo závisel v kódu mimo třídu.
<?php
class Token
{
const PUBLIC_CONST = 0; // = public const
public const PUBLIC_CONST_TWO = 0; // = const
protected const PROTECTED_CONST = 0;
private const PRIVATE_CONST = 0;
}
Chytání více typů výjimek v jednom catch
Pokud jsme v PHP <7.1 chtěli dva typy výjimek ošetřit stejným způsobem, bylo potřeba kód zkopírovat nebo vytvořit společnou metodu:
<?php
try {
// Some code...
} catch (ExceptionType1 $e) {
// Code to handle the exception
} catch (ExceptionType2 $e) {
// Same code to handle the exception
} catch (Exception $e) {
// ...
}
Nově je možné chytit více typů najednou:
<?php
try {
// Some code...
} catch (ExceptionType1 | ExceptionType2 $e) {
// Code to handle the exception
} catch (\Exception $e) {
// ...
}
Nový typ iterable
Při definici typehintu pro kolekci jste se museli rozhodnout, jestli použijete array
a nebude možné tam předat Traversable
nebo naopak. Obejít to šlo vynecháním typehintu a kontrolou až v kódu.
Teď už to není potřeba a můžete použít typ iterable
:
<?php
function foo(iterable $iterable)
{
foreach ($iterable as $value) {
// ...
}
}
Zároveň přibyla funkce (bool) is_iterable()
.
Negativní offsety pro stringové funkce
Některé funkce pro práci se stringy (například substr()
) umožňovaly používat negativní offsety:
<?php
substr("abcdef", -1); // "f"
Od 7.1 je možné negativní offsety používat také u funkcí: strpos()
, stripos()
, substr_count()
, iconv_strpos()
, mb_strpos()
(a několik dalších, viz RFC)
$this
se vždy bude chovat očekávaným způsobem
Tipuji, že jste nevěděli, a snad díky tomu ani nepoužívali možnost přiřadit do $this
jinou hodnotu (netuším, proč by to někdo dělal). V rámci zvyšování konzistence a odebírání historických haluzí už to naštěstí v 7.1 nepůjde.
<?php // https://3v4l.org/ZcMVY
$a = 'this';
$$a = 42;
var_dump($this); // na < 7.1 vypíše 42, na 7.1+ Fatal Error
<?php // https://3v4l.org/cAJGi
class C
{
function foo()
{
$a =& $this;
$a = 42;
var_dump($this); // na < 7.1 vypíše 42, na 7.1+ očekávaný dump $this
}
}
$x = new C();
$x->foo();
Session ID už není hashované
Bezpečně vygenerované ID (pomocí random_bytes()
přidaného v 7.0) už není potřeba hashovat, takže byly odebrány související volby v php.ini. Naopak přibyla možnost nastavení délky session id pomocí session.sid_length
. V php.ini
distribuovaném s PHP je nastaveno na 26 kvůli zpětné kompatibilitě, ale výchozí hodnota přímo v PHP je 32.
Doporučuji tedy ověřit, že sloupec (resp. místo, kam ukládáte session ID) zvládne 32 znaků, případně do php.ini
přidat session.sid_length = 26
Zkrácená syntaxe pro array destructuring
Je možné použít kratší zápis pro přiřazení pole do jednotlivých proměnných:
<?php
list($a, $b, $c) = array(1, 2, 3); // původně
[$a, $b, $c] = [1, 2, 3]; // nově také takto
Trošku odbočím, troufám si tvrdit, že použití list()
signalizuje code-smell a ve většině případů by mělo jít nahradit pomocí result objectu (tzn. metoda nebude vracet asociativní pole, ale immutable objekt). Pokud list()
používáte, zkuste se zamyslet, jestli místo přechodu na novou syntaxi nebude vhodnější upravit kód a rovnou vracet objekt. Pokud máte příklad, kde použití list()
dává smysl a neznepřehledňuje kód, tak budu rád, pokud se o něj podělíte v komentářích.
Drobnosti, které nepotřebují vlastní kapitolu
- Nová metoda pro převod callable na Closure:
Closure::fromCallable()
- Použití
eval
vmb_ereg_replace
je deprecated (zpreg_replace
bylo odebráno už v PHP 7.0) - Mcrypt je deprecated (a v další verzi bude odebrán) – je to od roku 2007 nepodporovaná knihovna
- Změny kolem generování náhodných čísel:
mt_rand()
bude používat jiný algoritmus,rand()
je aliasmt_rand()
asrand()
je aliasmt_srand()
- ext/curl bude podporovat HTTP/2 Server Push
Závěr
Jak je vidět, tak oproti 7.0 jsou změny drobnější a zaměřené na vyladění stávající funkcionality. Upgrade by tedy neměl být složitý, ale stejně doporučuji počkat s produkčním nasazením alespoň na 7.1.1 ;-)
Pokud se vám ještě nepodařil upgrade na PHP 7.0, tak budu rád, pokud se v komentářích rozepíšete o tom, na čem jste se zasekli a co vás drží zpět.
Takovou věc nepoužívám často, ale pokud chci parsovat tabulku třeba v CSV, je to na to jak dělané. Většinou to nedělám v PHP, ale to už je detail, i jiné jazyky mají podobné (a někdy i mocnější) konstrukce.
Další věc, kde se to může hodit, je parkování pomocí regulárních výrazů.
Pokud jde o jednorázový import CSV, tak je to asi v pohodě. Ale v případě, že to je nějaký univerzálnější import, tak je podle mě lepší mít v prvním řádku pojmenované názvy sloupců a nespoléhat na pořadí (pokud se to bude parsovat podle klíčů, tak je přidání dalšího sloupce méně práce a méně náchylné na rozbití).
Když jsem posledně parsoval CSV, tak jsem použil CSV knihovnu od PHP league a bylo to úplně bez starostí :-)
list() pouziju malokdy, ale u nejakych jednodussich specifickych skriptu se mi obcas hodi u vysledku internich php funkci. Napr:
list($full, $id, $date, $time) = $matches;
Mozna existuji lepsi cesty, necham si poradit
To první je náchylné na chyby kdy line bude ‚one-two‘. Tipuju, že to neošetřuješ, narozdíl od if (count($explodedLine) !== 3) { //exception }
U těch regulárních výrazů ten
list()
přidává zbytečnou křehkost do budoucna. V situaci, kdy bude potřeba matchnout a vrátit ještě nějakou hodnotu mezi těmi stávajícími, tak se to bude muset celé posunout (a tam hrozí zbytečná chyba – klidně to může upravovat jiný vývojář). Dobře to řeší ty pojmenované segmenty (viz komentář od Jakuba Vrány níže).list
používám na dvě věci:(?P<name>)
.getopt
.Odstranění
session.hash_function
škoda není. Nikdy jsem ale nepochopil, proč PHP na serveru neukládá jen hash session ID. Pokud někdo získá read-only přístup k úložišti session ze serveru (může to být třeba jen mirror), tak se rázem může vydávat za jakéhokoliv uživatele. Když by se na serveru ukládal jen hash session ID, tak by se to stát nemohlo. Dohledání dat podle daného session ID by ale pořád bylo možné. Viz též tip 883 v mé knize 1001 tipů a triků pro PHP, který to řeší.Já to používám u návratových hodnot funkcí:
Samozřejmě by to šlo i tím, že vrátím strukturu, ale ve výsledku je to zbytečně ukecané a rozdíl nulovej.
Samozřejmě musím mět zaručeno, že mi ta funkce bude vracet to co slibuje. Což je imho opět žádoucí, ať laskavě dělá svou práci pořádně, nebudu to za ní kontrolovat.
No a myšlenka je, že protože to nechceš kontrolovat, tak to uvedeš v kontraktu – co chceš, aby vrátila. A protože v PHP není jednoúčelový struct, tak musíš udělat celou třídu, případně interface+anonymní třídu.
Ano, to chápu. Jen se rozcházíme v užitku toho. Mě se zdá, že ve výsledku snaha o něco takového povede k mnohem většímu code-smell, než kterému se chceš vyvarovat.
Obecně se snažím zabránit zbytečným chybám (ať bych ji udělal já nebo jiný vývojář), takže jakákoliv další automatická kontrola je super. A vracení konkrétního typu místo pole zajistí, že v tom chybu udělat nejde. A kromě toho vracení definovaných typů také pomůže statické analýze lepšímu napovídání v IDE.
(Dříve mi to také přišlo jako „zbytečná práce“ a „budeme mít zbytečně moc tříd“. Abych to bral jako nezbytnou věc, stačilo, když to několikrát zabránilo chybě nebo překlepu)
Já jsem nepsal, že problém je zbytečná práce navíc.
Je zajímavé, že třeba v Haskellu se to používá hodně, páč pattern-matching, a je to hodně pohodlené. Chyby si tam hlídá typový systém.
V Lue, tam zase případ, když bych udělal něco jako list($a, $b, $c) = [1, 2]; tak v $c bude prostě Null. U ní jde zase o to, že Lua má velice čistá pravidla jak se pracuje s Null (respektive dle její terminologie Nil) hodnotama.
Já bych to za code-smell určitě nepovažoval.
Co jen si pocneme bez $$a ??? :D
Nene,
$$a
zůstává (naštěstí?):Zrušili jen možnost to použít pro přepsání
$this
.