Несмотря на то, что мы активно работаем с Python и Go, всё же существенная часть нашего серверного кода написана на PHP. Поэтому мы внимательно следим за всеми нововведениями языка. Прошло меньше года после релиза предыдущей минорной версии, и вот уже последний бета-релиз запланирован на 17 августа. Его ещё не рекомендуется использовать в production, но уже можно скачать docker-образ. Пора разбираться, что изменилось в новой версии языка.
Содержание
- Оптимизация
- Новая функциональность
- Добавлена возможность загружать расширения по имени
- Добавлена возможность перегружать абстрактные функции
- Запрещено number_format() возвращать -0
- Добавлена возможность конвертировать нумерованные ключи при приведении типов object/array
- Запрещено передавать null в качестве параметра для get_class()
- Вызов Count с параметром, который нельзя посчитать
- Возможность расширения типа параметра
- Добавлена возможность указывать запятую в конце группированных неймспейсов
- Реализовано семейство функций socket_getaddrinfo
- Улучшены TLS-константы
- Object typehint
- LDAP EXOP
- В ядро PHP добавлена Libsodium
- Добавлен алгоритм Argon2 в хешировании пароля
- HashContext as Object
- Добавлен отладчик PDO Prepared statements
- Добавлен отладчик PDO Prepared statements v2
- Расширенные типы строк для PDO
- Добавлены опции JSON_INVALID_UTF8_IGNORE и JSON_INVALID_UTF8_SUBSTITUTE
- Удалено из PHP 7.2
- Объявлено устаревшим в PHP 7.2
- Устарел __autoload
- Устарели png2wbmp() и jpeg2wbmp()
- Устарела $php_errormsg
- Устарела create_function()
- Устарел mbstring.func_overload
- Устарел (unset) cast
- Устарела функция parse_str() без второго аргумента
- Устарела функция gmp_random()
- Устарела функция each()
- Устарела функция assert() со строковым аргументом
- Устарел $errcontext аргумент в error handler
- Устарела константа INTL_IDNA_VARIANT_2003
- Объявлены устаревшими приведение типов (binary) и b"" литералы (RFC)
Оптимизация
В Opcache добавлена глобальная оптимизация на основе анализа потока данных с использованием SSA (Static single assignment form): Sparse Conditional Constant Propagation (SCCP), удаление мертвого кода (Dead Code Elimination — DCE) и удаление неиспользуемых локальных переменных.
Оптимизирована работа встроенной функции in_array()
с помощью поиска хеша в перевернутом массиве.
Новая функциональность
Добавлена возможность загружать расширения по имени (RFC)
Раньше extension=
и zend_extension=
в файле php.ini содержали пути до файла расширения.
Но, к сожалению, имя файла зависело от платформы. Например, в unix-подобных системах оно строилось как <extension-name>.<suffix>
, где suffix
это .so
на всех системах кроме HP-UX, где он sl
. В Windows имя файла формируется как php_<extension-name>.dll
. Всё это порождало много ошибок.
Теперь вы можете писать:
extension=bz2
zend_extension=xdebug
И нужные расширения будут подгружены в зависимости от ОС.
Этот механизм будет работать при установке extension и zend_extension в ini-файле, а также как аргумент для функции dl()
.
Но абсолютные пути по-прежнему необходимо будет указывать при флаге -z
в CLI-режиме, а также при указании абсолютного пути. Следующий пример работать не будет:
extension=/path/to/extensions/bz2
Добавлена возможность перегружать абстрактные функции (RFC)
Теперь вы сможете перегрузить абстрактные функции точно так же, как и обычные функции:
abstract class A { abstract function bar(stdClass $x); }
abstract class B extends A { abstract function bar($x): stdClass; }
class C extends B { function bar($x): stdClass{} }
До PHP 7.2 выдавалась ошибка вида:
Fatal error: Can't inherit abstract function A::bar() (previously declared abstract in B)
Запрещено number_format() возвращать -0 (RFC)
Вызов number_format(-0.00)
возвращал string(1) “0”
, однако number_format(-0.01)
возвращал string(2) “-0”
. Сейчас же будет возвращен 0 без знака.
Добавлена возможность конвертировать нумерованные ключи при приведении типов object/array (RFC)
Предыстория:
В PHP есть два типа данных, которые содержат ключ/значение. Первый — это массивы, которые могут содержать ключи в виде строк или чисел. При этом если строка удовлетворяет правилу /^(0|(-?[1-9][0-9]*))$/
и она достаточно маленькая PHP_INT_MIN ? n ? PHP_INT_MAX
, то она конвертируется в числовой ключ.
Второй тип — это объекты, в которых недопустимы числовые ключи, и ключи конвертируются в строки.
При этом в Zend Engine они представлены в виде одной структуры HashTable
.
Теперь это исправлено.
Давайте посмотрим на пару примеров:
$obj = new stdClass;
$obj->{'0'} = 1;
$obj->{'1'} = 2;
$obj->{'2'} = 3;
$arr = (array) $obj;
var_dump($arr); // Видим, что массив содержит ключи/значения
var_dump($arr[1]); // Не можем обратиться по ключу. В PHP 7.2 это исправлено.
$arr = [0 => 1, 1 => 2, 2 => 3];
$obj = (object)$arr;
var_dump($obj); // Видим, что объект содержит ключи/значения
var_dump($obj->{'0'}); // Не можем обратиться по ключу. В PHP 7.2 это исправлено.
Запрещено передавать null в качестве параметра для get_class() (RFC)
Когда null передается как параметр get_class()
внутри контекста класса, поведение функции может быть весьма неожиданным:
class Foo
{
function bar($repository)
{
$result = $repository->find(100);
return get_class($result);
}
}
Если $result
содержит действительный объект, возвращенный из репозитория, результатом функции будет имя класса этого объекта.
Если $result
содержит null
, выход будет иметь контекст класса, из которого вызывается get_class()
, в этом случае Foo
.
Эта особенность нарушает принцип наименьшего удивления: «если необходимая функция имеет высокий коэффициент удивления, может потребоваться перепроектирование этой функции».
Теперь будет выдаваться предупреждение:
Warning: get_class() expects parameter 1 to be object, null given in %s on line %d
Если вы хотите сохранить старое поведение, придётся переписать код:
// Было:
$x = get_class($some_value_that_may_be_null);
// Стало:
if ($some_value_that_may_be_null === null) {
$x = get_class();
} else {
$x = get_class($some_value_that_may_be_null);
}
Вызов Count с параметром, который нельзя посчитать (RFC)
Теперь вызов count()
с параметром, который является скалярным, null или объектом, который не реализовал интерфейс Countable, будет выдавать Warning
.
Возможность расширения типа параметра (RFC)
Одно из самых горячо обсуждаемых изменений — возможность не указывать тип параметра в наследнике. Таким образом наследник сможет принять параметр любого типа.
class ArrayClass {
public function foo(array $foo) { /* ... */ }
}
class EverythingClass extends ArrayClass {
public function foo($foo) { /* ... */ }
}
До PHP 7.2 возвращалась ошибка:
Warning: Declaration of EverythingClass::foo($foo) should be compatible with ArrayClass::foo(array $foo) in %s on line 18
В пулл реквесте мнения разделились.
Одни говорили:
"Конечно, давайте отправим SOLID в ад. Кто такая Барбара Лисков??? Какая-то безумная женщина? Конечно! Давайте разрешим ломать принципы и идеи".
Другие считают, что:
"Ограничения вроде принципа единой ответственности (single responsibility) должны быть реализованы не на уровне языка, а в коде приложения. И теперь и буква L в SOLID будет на усмотрение разработчика".
Добавлена возможность указывать запятую в конце группированных неймспейсов (RFC)
// Раньше это работало только для массивов
$array = [1, 2, 3,];
// Теперь и для группировки неймспейсов
use Foo\Bar\{ Foo, Bar, Baz, };
Планировалось добавить такую возможность и для других списков, но на стадии голосования они были отменены и будут по-прежнему возвращать Parse error
:
// Аргументы функций и методов
fooCall($arg1, $arg2, $arg3,);
// Перечисление реализуемых интерфейсов
class Foo implements
FooInterface,
BarInterface,
BazInterface,
{
// Перечисление трейтов
use
FooTrait,
BarTrait,
BazTrait,
;
// Перечисление свойств и констант
const
A = 1010,
B = 1021,
C = 1032,
D = 1043,
;
protected
$a = 'foo',
$b = 'bar',
$c = 'baz',
;
private
$blah,
;
// Декларация функций и методов
function something(FooBarBazInterface $in, FooBarBazInterface $out,) : bool
{
}
}
// Наследование переменных из родительской области в анонимных функциях
$foo = function ($bar) use (
$a,
$b,
$c,
) {
// . . .
};
Реализовано семейство функций socket_getaddrinfo (RFC)
Теперь из PHP будет доступна информация из getaddrinfo()
, реализованная на C. Это недостающая функция для текущей библиотеки сокетов. При работе с различными сетями было бы полезно разрешить libc
рассказать нам, какие методы подключения/прослушивания будут наиболее подходящими с учетом набора подсказок.
Были утверждены четыре функции:
socket_addrinfo_lookup(string node[, mixed service, array hints]) : array
socket_addrinfo_connect(resource $addrinfo) : resource
socket_addrinfo_bind(resource $addrinfo) : resource
socket_addrinfo_explain(resource $addrinfo) : array
Улучшены TLS-константы (RFC)
Теперь:
tls://
имеет дефолтное значениеTLSv1.0 + TLSv1.1 + TLSv1.2
ssl://
это алиас кtls://
- константа
STREAM_CRYPTO_METHOD_TLS_*
имеет дефолтное значениеTLSv1.0 + TLSv1.1 + TLSv1.2
вместоTLSv1.0
- константа
STREAM_CRYPTO_METHOD_SSLv23_CLIENT
считается устаревшей и позже будет удалена.
Object typehint (RFC)
Добавлен новый тип для хинта: object
function acceptsObject(object $obj) {
// ...
}
acceptsObject(json_decode('{}'));
acceptsObject(new \MyObject());
acceptsObject("Будет ошибка");
function correctFunction() : object {
$obj = json_decode('{}');
return $obj;
}
// Будет ошибка
function errorFunction() : object {
return [];
}
LDAP EXOP (RFC)
Добавлены функции для использования расширенных операций LDAP в php-ldap.
// Вызов EXOP whoami и сохранение результатов в $identity
if (ldap_exop($link, LDAP_EXOP_WHO_AM_I, NULL, $identity)) {
echo "Connected as $identity\n";
} else {
echo "Operation failed\n";
}
// Делаем то же самое, используя объект результата:
$r = ldap_exop($link, LDAP_EXOP_WHO_AM_I);
if (($r !== FALSE) && ldap_parse_exop($link, $r, $retdata)) {
echo "Connected as $retdata\n";
} else {
echo "Operation failed\n";
}
// То же самое с хелпером:
if (ldap_exop_whoami($link, $identity)) {
echo "Connected as $identity\n";
} else {
echo "Operation failed\n";
}
// Изменение пароля с хелпером:
if (ldap_exop_passwd($link, 'uid=johndoe,dc=example,dc=com', '', 'newpassword')) {
echo "Password changed\n";
} else {
echo "Operation failed\n";
}
В ядро PHP добавлена Libsodium (RFC)
Libsodium — современная криптографическая библиотека, которая предлагает аутентифицированное шифрование, высокоскоростную криптографию с эллиптическими кривыми и многое другое. В отличие от других криптографических стандартов (которые являются набором криптографических примитивов, например, WebCrypto), libsodium включает в себя тщательно подобранные алгоритмы, реализованные экспертами по безопасности. Это поможет избежать уязвимостей в сторонних каналах.
Добавлен алгоритм Argon2 в хешировании пароля (RFC)
Argon2 — это современный простой алгоритм, направленный на высокую скорость заполнения памяти и эффективное использование нескольких вычислительных блоков.
HashContext as Object (RFC)
Начиная с PHP5 предпочтительной структурой для хранения внутренних данных были объекты. По какой-то причине в расширении Hash для этого использовались ресурсы. Данный RFC пытается исправить недоразумение, переведя расширение Hash на хранение внутренних данных в виде объектов.
Примеры использования ресурса в качестве внутреннего представления:
resource hash_copy ( resource $context )
resource hash_init ( string $algo [, int $options = 0 [, string $key = NULL ]] )
Внутреннее представление преобразуется из ресурса в объект. Существующий код должен продолжить работать, если он не использует явных проверок is_resource(), эти проверки могут быть легко заменены на is_resource | is_object.
Добавлен отладчик PDO Prepared statements (RFC)
$db = new PDO(...);
$stmt = $db->query('SELECT 1');
var_dump($stmt->activeQueryString()); // => string(8) "SELECT 1"
$stmt = $db->prepare('SELECT :string');
$stmt->bindValue(':string', 'foo');
// возвращает необработанную строку до выполнения
var_dump($stmt->activeQueryString()); // => string(14) "SELECT :string"
// возвращает обработанную строку после выполнения
$stmt->execute();
var_dump($stmt->activeQueryString()); // => string(11) "SELECT 'foo'"
Добавлен отладчик PDO Prepared statements v2 (RFC)
$calories = 150;
$colour = 'red';
$sth = $dbh->prepare('SELECT name, colour, calories
FROM fruit
WHERE calories < ? AND colour = ?');
$sth->bindParam(1, $calories, PDO::PARAM_INT);
$sth->bindValue(2, $colour, PDO::PARAM_STR);
$sth->execute();
$sth->debugDumpParams();
/*
Вывод:
SQL: [82] SELECT name, colour, calories
FROM fruit
WHERE calories < ? AND colour = ?
Sent SQL: [88] SELECT name, colour, calories
FROM fruit
WHERE calories < 150 AND colour = 'red'
Params: 2
Key: Position #0:
paramno=0
name=[0] ""
is_param=1
param_type=1
Key: Position #1:
paramno=1
name=[0] ""
is_param=1
param_type=2
*/
Расширенные типы строк для PDO (RFC)
$db->quote('uber', PDO::PARAM_STR | PDO::PARAM_STR_NATL); // N'uber'
$db->quote('A'); // 'A'
$db->setAttribute(PDO::ATTR_DEFAULT_STR_PARAM, PDO::PARAM_STR_NATL);
$db->quote('uber'); // N'uber'
$db->quote('A', PDO::PARAM_STR | PDO::PARAM_STR_CHAR); // 'A'
Добавлены опции JSON_INVALID_UTF8_IGNORE и JSON_INVALID_UTF8_SUBSTITUTE (Request)
Для функций json_encode
/ json_decode
добавлены новые опции JSON_INVALID_UTF8_IGNORE
и JSON_INVALID_UTF8_SUBSTITUTE
для игнорирования или замены некорректных последовательностей байтов UTF-8.
Удалено из PHP 7.2
Следующий функционал объявлен устаревшим и был удален.
Удалены строки без кавычек (bare word) (RFC)
Строки без кавычек теперь вызывают E_WARNING
. В PHP2 такие строки вызывали Syntax error
, но в PHP3 бета-поведение было изменено.
К примеру:
$foo = flase; // Опечатка, но раньше вызывалась ошибка E_NOTICE, которую часто отключали.
// ...
if ( $foo ) {
var_dump($foo); // string(5) "flase"
}
Перенос mcrypt в PECL
Расширение mcrypt, объявленное устаревшим в PHP 7.1, было перемещено в PECL.
Объявлено устаревшим в PHP 7.2 (RFC)
Следующий функционал объявлен устаревшим и больше не рекомендуется к использованию.
Этот функционал будет удален в версии 8.0.
Устарел __autoload
Функция __autoload
была заменена на spl_autoload_register
ещё в версии 5.1.
Основным преимуществом spl_autoload_register
является возможность использовать несколько автозагрузчиков. Теперь будет выбрасываться Deprecation notice
на стадии компиляции.
Устарели png2wbmp() и jpeg2wbmp()
Png2wbmp()
и jpeg2wbmp()
— единственные функции, изменяющие формат изображений, которые можно вызвать напрямую, доступные в ext / gd
, что делает их довольно обособленными, поскольку libgd не предлагает таких функций. Кроме того, WBMP
был изобретен для поддержки WAP
, который в настоящее время устарел. Теперь будет выбрасываться Deprecation notice
.
Устарела $php_errormsg
Переменная $php_errormsg
создается в локальной области при возникновении нефатальной ошибки, если параметр track_errors
включен (отключен по умолчанию), и ошибка не перехватывалась никаким обработчиком ошибок.
Помимо того, что поведение зависело от настроек ini-файла, оно также было магическим. Функция error_get_last
обеспечивает более чистый способ получения последней ошибки. С PHP 7 доступна функция error_clear_last
, таким образом, охватываются все возможные варианты использования $php_errormsg
без манипуляции с областями видимости.
Устарела create_function()
create_function()
— это тонкая оболочка вокруг конструкции языка eval()
, позволяющая создавать функцию со сгенерированным именем, списком аргументов и телом в виде строковых аргументов. До введения замыканий в PHP 5.3 она обеспечивала способ создания чего-то похожего на лямбда-функции.
Из-за характера работы create_function()
, помимо потенциального источника проблем безопасности, имеет очень плохие характеристики производительности и использования памяти. Использование реальных замыканий во всех отношениях предпочтительнее.
Устарел mbstring.func_overload
Параметр mbstring.func_overload
в ini-файле позволяет заменить определенное подмножество строковых функций на аналоги с расширением mbstring. Например, strlen()
больше не будет возвращать длину строки в байтах, вместо этого она вернет длину в символах в соответствии с текущей выбранной внутренней кодировкой.
Это означает, что код с использованием mbstring.func_overload
не совместим кодом, написанным в предположении, что основные операции со строками работают нормально. Некоторые библиотеки прямо запрещают func_overload
(например, Symfony), другие библиотеки перестают работать. Код, который хочет поддерживать func_overload, должен условно переключаться между обычными строковыми функциями и функциями mbstring с 8-битным кодированием (обычно только библиотеки для криптографии пытаются это сделать).
Теперь будет выбрасываться Deprecation notice
если mbstring.func_overload
содержит ненулевое значение.
Устарел (unset) cast
Каст (unset)
превращает значение в null. Это означает, что (unset) expr
— просто выражение, которое всегда возвращает null и не имеет других побочных эффектов.
Помимо бесполезности, это поведение только путает, так как многие люди разумно предполагают, что (unset) $a
будет вести себя аналогично unset($a)
, а на самом деле этого не происходит.
Теперь будет выбрасываться Deprecation notice
на стадии компиляции.
Устарела функция parse_str() без второго аргумента
Функция parse_str()
разбирает строку URL и присваивает значения переменным в текущем контексте (или заносит в массив, если задан параметр result).
Использовать эту функцию без параметра result крайне не рекомендовалось, потому что динамическое создание переменных в области видимости функции ведет ровно к тем же проблемам, что и register_globals. Теперь выбрасывается Deprecation notice
, если параметр result не передается.
Устарела функция gmp_random()
Функция gmp_random()
генерирует случайное число. Число будет в диапазоне нуля до произведения числа limiter на количество бит в лимбе (limb). Если число limiter отрицательное, будет возвращен отрицательный результат.
Лимб — внутренний GMP-механизм. Технически это часть числа, помещающаяся в одно машинное слово. Количество бит в нем может различаться в разных системах. В основном это либо 16, либо 32, но это не гарантируется. Так происходит, потому что реализация GMP/MPIR не доступна пользователю. Таким образом, использование этой функции требует угадывания размера Лимба и может зависеть от платформы.
Чтобы исправить это, в PHP 5.6 добавили функции gmp_random_bits()
и gmp_random_range()
, которые позволяют точно контролировать используемый диапазон случайных чисел. Эти функции всегда должны быть предпочтительнее, чем gmp_random()
.
Теперь при вызове gmp_random()
выбрасывается Deprecation notice
.
Устарела функция each()
Функция each()
может использоваться для итерации по массиву, подобно foreach
. В каждом вызове он возвращает массив с текущим ключом и значением и продвигает указатель внутреннего массива на следующую позицию. Типичное использование, представленное в руководстве, выглядит следующим образом:
reset($array);
while (list($key, $val) = each($array)) {
echo "$key => $val\n";
}
Функция each
уступает foreach
практически во всём, среди прочего она в 10 раз медленнее.
Поддержка этой функции создает проблему для некоторых изменений языка. Например, в предупреждении для невалидного контейнера массива (RFC) пришлось исключить list()
, потому что типичное использование each
полагается на факт, что вы можете получить доступ к смещениям массива на false
без предупреждения.
Теперь выбрасывается Deprecation warning
при первом вызове each
, потому что чаще всего он используется в цикле.
Устарела функция assert() со строковым аргументом
Функция assert()
имеет два режима работы: если передано что-то, кроме строки, она будет проверять, что значение является истиной. Если была передана строка, она будет запущена через eval()
, и assert будет проверять истинность результата eval()
.
Причиной такого поведения является то, что до PHP 7 это было единственным способом предотвратить вычисление выражения. Начиная с PHP 7 опция zend.assertions
в ini-файле может использоваться, чтобы избежать вычисления выражений. Таким образом, больше нет необходимости поддерживать неявное вычисление строковых аргументов.
Использование assert($value)
для проверки истинности значения открывает уязвимость удаленного выполнения кода, если существует вероятность того, что $value
будет строкой.
Теперь выбрасывается Deprecation notice
, если assert()
используется со строковым аргументом.
Устарел $errcontext аргумент в error handler
Обработчикам ошибок, заданных с помощью set_error_handler()
, передается в качестве последнего аргумента $errcontext
. Этот аргумент представляет собой массив, содержащий все локальные переменные в месте, где была сгенерирована ошибка.
Эта функция трудна для оптимизации, поскольку $errcontext
может использоваться для изменения всех ссылок и объектов в текущей области видимости. Эта функциональность практически не используется. Если вы хотите проверить переменные состояния в месте ошибки, вы должны использовать debugger.
Обратите внимание, что контекст ошибки содержит только локальные переменные. Ошибки backtrace, включая $this
и аргументы функции, останутся доступны через debug_backtrace()
.
В этом случае невозможно выбрасывать Deprecation warning
, поэтому этот функционал будет просто отмечен в документации как устаревший.
Устарела константа INTL_IDNA_VARIANT_2003
В PHP 7.2 константа INTL_IDNA_VARIANT_2003 объявлена устаревшей.
В PHP 7.4 будут изменены idn_to_ascii()
и idn_to_utf8()
, в которых дефолтный $variant
параметр станет INTL_IDNA_VARIANT_UTS46.
В PHP 8.0 уберут поддержку INTL_IDNA_VARIANT_2003.
Объявлены устаревшими приведение типов (binary) и b"" литералы (RFC)
Приведение типа (binary)
и поддержка префикса b
были добавлены в PHP 5.2.1
для совместимости с PHP 6
, однако эта версия так и не появилась, и неизвестно, будут ли когда-нибудь ещё попытки реализовать двоичные строки. Тем не менее, они все еще принимаются языковым сканером, хотя и игнорируются.
Теперь выбрасывается Deprecation notice
при использовании этих кастов.
Специально к версии 7.2 я подготовил репозиторий — "Что нового в PHP", который описывает изменения в версиях PHP, начиная с 5.
Список на английском языке вы можете найти в источниках:
https://github.com/php/php-src/blob/PHP-7.2/UPGRADING
https://wiki.php.net/rfc#php_next_72
Попробовать новую версию онлайн:
https://3v4l.org/
Как мы видим, глобальных изменений достаточно мало.
А как вы относитесь к изменениям в PHP? Какая фича понравилась больше всего?