Jaké novinky přinese PHP 7.2
Publikováno: 19.9.2017
Článek jsem původně vydal v angličtině na svém blogu.
PHP 7.2 vyjde 30. listopadu 2017 (viz plán). Přinese dvě velké bezpečnostní funkcionality, několik menších vylepšení a trochu pročistí historický nepořádek v jazyce. Přečetl jsem RFCčka, diskuze v internals a PullRequesty na Githubu, takže vy už nemusíte.
Testovací verzi je možné zkoušet už teď, stačí si ji stáhnout pomocí odkazů v PHP 7.2.0 Release Candidate 2 Released. Používám ji pro vývoj lokálně už od verze 7.2.0-beta1 a zatím jsem nenarazil na žádný problém.
RFC: Argon2 Password Hash
Funkce password_hash
byla k dispozici už od PHP 5.5. Od začátku byla navržena pro použití s různými algoritmy pro hashování hesel, ale zatím byl k dispozici jen jeden, PASSWORD_BCRYPT
.
DŮLEŽITÉ: Pokud na hashování hesel používáte
sha1()
nebomd5()
, přestaňte prosím číst článek a běžte opravit svoji aplikaci tak, aby používalapassword_hash()
. Doporučuji článek Michala Špačka Změna hashování existujících hesel.
PHP 7.2 umožňuje použít algoritmus Argon2i pomocí konstanty PASSWORD_ARGON2I
:
password_hash('password', PASSWORD_ARGON2I)
Použití PASSWORD_BCRYPT
je stále úplně bezpečné. Argon2i je jen další možnost, která možná někdy v budoucnu nahradí bcrypt jako výchozí volbu.
Náročnost výpočtu hashe Argon2i lze ovlivnit nastavením následujících parametrů (podobně jako pro bcrypt
můžeme nastavit vlastní cost
):
$options = [
'memory_cost' => PASSWORD_ARGON2_DEFAULT_MEMORY_COST,
'time_cost' => PASSWORD_ARGON2_DEFAULT_TIME_COST,
'threads' => PASSWORD_ARGON2_DEFAULT_THREADS,
];
password_hash('password', PASSWORD_ARGON2I, $options);
Vhodné nastavení náročnosti pro Argon2i zjistíte experimentováním na cílovém serveru, kde pak aplikace poběží (což je to stejné, co byste měli dělat při nastavení cost
v bcryptu).
Teď je ta vhodná chvíle pro kontrolu délky sloupce pro ukládání hashe hesla. PASSWORD_BCRYPT
totiž generuje hashe dlouhé 60 znaků, ale hash PASSWORD_ARGON2I
je dlouhý 96 znaků. Dokumentace password_hash
doporučuje zvolit jako délku sloupce 255 znaků, aby se do něj vešel jakýkoliv další hash v budoucnu (což je důležité zejména, pokud máte jako algoritmus nastavený PASSWORD_DEFAULT
):
It is recommended to store the result in a database column that can expand beyond 60 characters (255 characters would be a good choice).
Na blogu Zend Framework vyšel o Argon2i detailnější článek: Protecting passwords with Argon2 in PHP 7.2.
Poznámka: Argon2i je k dispozici pouze pokud bylo PHP zkompilováno s přepínačem --with-password-argon2
. (V binárkách pro Windows na php.net už zkompilovaný).
RFC: Make Libsodium a Core Extension
V PHP 7.2 do jádra PHP přibyla kryptografická knihovna libsodium. Dříve byla dostupná prostřednictvím PECL. Je k dispozici i polyfill, takže s použitím nemusíte čekat až na vydání nové verze PHP (polyfill podporuje dokonce i PHP 5.2.4!).
Přiznám se, že toho o kryptografii moc nevím, ale ta nejdůležitější věc, kterou vím, je: Nevymýšlejte si to po svém! (V této odpovědi na StackExchange je to pěkně vyargumentované.).
Takže prostě používejte funkce sodium_
místo toho, abyste si kryptografické algoritmy implementovali sami (nebo je kopírovali ze StackOverflow).
Doporučuji k přečtení tyto dva články o změnách v kryptografii v PHP (oba napsal Scott Arciszewski, autor Libsodium RFC):
- PHP 7.2: The First Programming Language to Add Modern Cryptography to its Standard Library
When version 7.2 releases at the end of the year, PHP will be the first programming language to adopt modern cryptography in its standard library.
- It Turns Out, 2017 is the Year of Simply Secure PHP Cryptography
Další zajímavé změny
RFC: Object typehint
Nově bude možné použít object
jako typ parametru a jako návratový typ.
<?php declare(strict_types = 1);
function foo(object $definitelyAnObject): object
{
return 'another string';
}
foo('what about some string?');
// TypeError: Argument 1 passed to foo() must be an object, string given, called in...
foo(new \stdClass());
// TypeError: Return value of foo() must be an object, string returned
Od 7.2 bude object
klíčové slovo, takže si zkontrolujte, že ho nepoužíváte jako název třídy, rozhraní nebo traitu. Ostatně bylo rezervované už od PHP 7.0.
Je to užitečné hlavně pro knihovny (ORMka, DI kontejnery, serializery a další). Ale je možné, že to vám to umožní vyčistit i nějaký vlastní kód. U nás nám to pomůže vylepšit pomocnou metodu v testech:
class TestUtils
{
/**
* @param object $object
* @param string $propertyName
* @param mixed $value
*/
public static function setPrivateProperty($object, string $property, $value): void
{
if (!is_object($object)) {
throw new \Exception('An object must be passed');
}
Zjednoduší se na tohle:
class TestUtils
{
public static function setPrivateProperty(object $object, string $property, $value): void
{
RFC: Parameter Type Widening
Díky této změně je možné vynechat definici typu parametru v metodě zděděné třídy. Vím, že to zní složitě, takže se raději podívejme na příklad:
<?php declare(strict_types = 1);
class LibraryClass
{
public function doWork(string $input)
{
}
}
class UserClass extends LibraryClass
{
public function doWork($input)
{
}
}
// PHP <7.2.0
// Warning: Declaration of UserClass::doWork($input) should be compatible with LibraryClass::doWork(string $input) in …
// PHP 7.2.0+
// no errors
Kromě jiného by to mělo umožnit knihovnám přidat skalární type-hinty bez toho, aby jednotliví uživatelé museli upravit své zděděné třídy. Ale je to spíš teoretická možnost, protože není možné udělat to stejné pro definici návratových typů. A pokud už knihovna přidá typy k parametrům, tak dává smysl rovnou přidat i návratové typy.
Vynechání definice typů je v souladu s Liskovové substitučním principem – LSP protože to zvětšuje rozsah přijímaných hodnot. Ale zděděná třída nemůže vynechat návratový typ, protože to by rozšířilo možné návratové hodnoty (a to už LSP porušuje).
Stejné chování bylo upraveno pro abstraktní třídy v samostatném RFC: RFC: Allow abstract function override.
RFC: Counting of non-countable objects
Věděli jste, že je možné volat count
i na skalárních hodnotách? Ono to stejně ve skutečnosti moc nepočítá, jen to vrátí 1
.
<?php
var_dump(count(null)); // int(0)
var_dump(count(0)); // int(1)
var_dump(count(4)); // int(1)
var_dump(count('4')); // int(1)
Od PHP 7.2 to naštěstí začne vyhazovat warning:
Warning: count(): Parameter must be an array or an object that implements Countable in /in/4aIl2 on line 3
Z podobných úprav mám radost, protože takový kód nikdo nepíše naschvál. Mnohem pravděpodobněji jde o chybu, kterou je potřeba opravit.
Hádám, že na ten warning narazíte i někde ve své aplikaci. Podívejme se na příklad, kde chyba není vidět na první pohled:
<?php declare(strict_types = 1);
class Data
{
/** @var string[] */
private $data;
public function addOne(string $item)
{
if (count($this->data) >= 5) {
throw new \Exception('too much data');
}
$this->data[] = $item;
}
}
$data = new Data();
$data->addOne('item1');
$data->addOne('item2');
$data->addOne('item3');
// Warning: count(): Parameter must be an array or an object that implements Countable in ...
Zahlásí to warning, protože v if
voláte count
na hodnotě null
.
RFC: Deprecations for PHP 7.2
Následující funkce budou od PHP 7.2 vyhazovat deprecation warning. Odebrány budou pravděpodobně v PHP 8.0. V RFC naleznete detailní vysvětlení důvodů pro jednotlivé deprecations:
__autoload()
– používejtespl_autoload_register()
$php_errormsg
– používejteerror_get_last()
create_function()
– používejte raději anonymní funkcembstring.func_overload
(v ini souboru) – používejte přímo funkcemb_*
(unset)
cast – není to deprecationunset($var)
ale$foo = (unset) $bar
což to stejné jako volat$foo = null
(ano, je to divné)parse_str()
bez druhého parametru – přímo vytvářet proměnné, když parsujete query string není něco, co byste měli dělat (nebo snad stále používáteregister_globals
?)gmp_random()
– používejtegmp_random_bits()
nebogmp_random_range()
each()
– používejte radějiforeach
(je více než 10× rychlejší)assert()
se stringovým parametrem – interně totiž používáeval()
!$errcontext
jako parametr error handleru – používejtedebug_backtrace()
.
Drobnější změny
RFC: get_class() disallow null parameter
Věděli jste, že get_class()
bez parametrů vrací stejnou hodnotu jako __CLASS__
? Já ne. Ale to se v tomhle RFC nemění.
<?php
class MyClass
{
public function myInfo()
{
var_dump(get_class()); // string(7) "MyClass"
var_dump(__CLASS__); // string(7) "MyClass"
}
}
$a = new MyClass();
$a->myInfo();
var_dump(get_class($a)); // string(7) "MyClass"
RFC ruší možnost použít null
jako parametr. Mrkněte se na následující příklad z RFC. Pokud z repository načteme objekt a tedy $result
není null, vypíše to název třídy načteného objektu. Ale pokud bude vrácená hodnota null
, vypíše to Foo
(což je název třídy, odkud jsme to zavolali, ne název třídy objektu načteného z repository).
class Foo
{
function bar($repository)
{
$result = $repository->find(100);
echo get_class($result);
}
}
// In 7.2: Warning: get_class() expects parameter 1 to be object, null given in ...
RFC: Allow loading extensions by name
Pokud jste konfigurovali extensions před 7.2, museli jste v php.ini
použít celý název souboru s extension:
Na Windows:
extension=php_mbstring.dll
Na Linuxu:
extension=mbstring.so
To je v pohodě, pokud používáte pouze jeden systém. Ale je to složitější, pokud chcete vytvářet automatizované skripty podporující více platforem. V 7.2 to je vylepšené a teď můžete na Linuxu i Windows použít:
extension=mbstring
Původní syntaxe je samozřejmě stále podporovaná, ale je doporučené používat tu novou.
RFC: Deprecate and Remove Bareword (Unquoted) Strings
Tohle RFC je další krůček k tomu, aby bylo těžší PHP používat omylem špatně. Pokud se překlepnete v konstantě v 7.2, tak to vyhodí warning (v PHP 7.1 je to jen notice). A v PHP 8.0 to dokonce vyhodí error.
Nejjednodušší příklad je tohle:
<?php
var_dump(NONEXISTENT_CONSTANT);
// PHP 7.1
// Notice: Use of undefined constant NONEXISTENT_CONSTANT - assumed 'NONEXISTENT_CONSTANT' in …
// PHP 7.2
// Warning: Use of undefined constant NONEXISTENT_CONSTANT - assumed 'NONEXISTENT_CONSTANT' (this will throw an Error in a future version of PHP) in …
Hodně se mi líbí příklady chyb, kterým se RFC snaží předejít:
$foo = flase; // typo!
// ...
if ( $foo ) {
var_dump($foo); // string(5) "flase"
}
A tento:
$found = false;
foreach ( $list as $item ) {
if ( is_null($item) ) {
contniue; // this statement issues a notice and does nothing
}
// lines assuming $item is not null
}
RFC: Trailing Commas in List Syntax
Vždycky bylo možné psát čárku i za posledním prvkem pole:
$foo = [
'foo',
'bar',
];
Je to užitečné pro hezky čitelné diffy ve VCS a také se snadněji přidávají nové prvky na konec pole.
Tohle RFC navrhovalo přidat volitelnou čárku na konec všech výčtů:
- Skupinové namespace use
- Parametry funkcí/metod (definice i volání)
- Implementace rozhraní ve třídě
- Implementace traitu
- Předávání proměnných do scope anonymní funkce
Nejvíc jsem doufal, že projdou parametry metod a funkcí (jak definice, tak volání). Ale hlasováním to neprošlo (potřebovalo 2/3 většinu, protože to je změna jazyka).
Překvapivě prošly pouze „Skupinové namespace use“:
use Foo\Bar\{
Foo,
Bar,
Baz,
};
// všimněte si čárky po "Baz"
Závěrem
PHP 7.2 bude obsahovat nové bezpečnostní možnosti (sodium, Argon2i), několik vylepšení jazyka (typ object
, rozšiřování typů ve zděděných třídách) a spoustu drobnějších změn čistících nepořádek v jazyce.
RC2 byla vydána 14. září, takže teď je dobrý čas ho vyzkoušet. Pokud ho zatím nemůžete nebo nechcete používat lokálně pro vývoj, měli byste na něm alespoň spustit testy, abyste zjistili, jestli bude něco potřeba opravovat. Ale myslím, že v dobře udržované aplikaci na moc problémů nenarazíte.
Pokud spravujete nějaký open-source projekt, tak do TravisCI 7.2 přidejte určitě už teď, aby vaši uživatelé nenarazili na problémy s kompatibilitou. Nebo můžete v další verzi vyžadovat 7.2 jako minimální verzi stejně jako Doctrine ORM.
Na co se v PHP 7.2 těšíte nejvíce? A jestli ještě používáte něco staršího než PHP 7.1, tak budu rád, pokud se v komentářích podělíte o důvody.