PHP 8
8.2
8.1
PHP 8.1 — большое обновление языка PHP.
Оно содержит множество новых возможностей, включая перечисления, readonly-свойства, callback-функции как объекты первого класса, файберы, пересечение типов, улучшения производительности и многое другое.
Перечисления
// PHP < 8.1
class Status
{
const DRAFT = 'draft';
const PUBLISHED = 'published';
const ARCHIVED = 'archived';
}
function acceptStatus(string $status) {...}
// PHP 8.1
enum Status
{
case Draft;
case Published;
case Archived;
}
function acceptStatus(Status $status) {...}
Используйте перечисления вместо набора констант, чтобы валидировать их автоматически во время выполнения кода.
Readonly-свойства
// PHP < 8.1
class BlogData
{
private Status $status;
public function __construct(Status $status)
{
$this->status = $status;
}
public function getStatus(): Status
{
return $this->status;
}
}
// PHP 8.1
class BlogData
{
public readonly Status $status;
public function __construct(Status $status)
{
$this->status = $status;
}
}
Readonly-свойства нельзя изменить после инициализации (т.е. когда им было присвоено значение). Они будут крайне полезны при реализации объектов типа VO(Объект-значени) и DTO(Объект передачи данных).
Callback-функции как объекты первого класса
// PHP < 8.1
$foo = [$this, 'foo'];
$fn = Closure::fromCallable('strlen');
// PHP 8.1
$foo = $this->foo(...);
$fn = strlen(...);
С помощью нового синтаксиса любая функция может выступать в качестве объекта первого класса. Тем самым она будет рассматриваться как обычное значение, которое можно, например, сохранить в переменную.
Расширенная инициализация объектов
// PHP < 8.1
class Service
{
private Logger $logger;
public function __construct(
?Logger $logger = null,
) {
$this->logger = $logger ?? new NullLogger();
}
}
// PHP 8.1
class Service
{
private Logger $logger;
public function __construct(
Logger $logger = new NullLogger(),
) {
$this->logger = $logger;
}
}
Объекты теперь можно использовать в качестве значений параметров по умолчанию, статических переменных и глобальных констант, а также в аргументах атрибутов.
Таким образом появилась возможность использования вложенных атрибутов.
// PHP < 8.1
class User
{
/**
* @Assert\All({
* @Assert\NotNull,
* @Assert\Length(min=5)
* })
*/
public string $name = '';
}
// PHP 8.1
class User
{
#[\Assert\All(
new \Assert\NotNull,
new \Assert\Length(min: 5))
]
public string $name = '';
}
Пересечение типов
// PHP < 8.1
function count_and_iterate(Iterator $value) {
if (!($value instanceof Countable)) {
throw new TypeError('value must be Countable');
}
foreach ($value as $val) {
echo $val;
}
count($value);
}
// PHP 8.1
function count_and_iterate(Iterator&Countable $value) {
foreach ($value as $val) {
echo $val;
}
count($value);
}
Теперь в объявлении типов параметров можно указать, что значение должно относиться к нескольким типам одновременно.
В данный момент пересечения типов нельзя использовать вместе с объединёнными типами, например, A&B|C
.
Тип возвращаемого значения never
// PHP < 8.1
function redirect(string $uri) {
header('Location: ' . $uri);
exit();
}
function redirectToLoginPage() {
redirect('/login');
echo 'Hello'; // <- dead code
}
// PHP 8.1
function redirect(string $uri): never {
header('Location: ' . $uri);
exit();
}
function redirectToLoginPage(): never {
redirect('/login');
echo 'Hello'; // <- dead code detected by static analysis
}
Функция или метод, объявленные с типом never
, указывают на то, что они не вернут значение и либо выбросят исключение, либо завершат выполнение скрипта с помощью вызова функции die()
, exit()
, trigger_error()
или чем-то подобным.
Окончательные константы класса
//PHP < 8.1
class Foo
{
public const XX = "foo";
}
class Bar extends Foo
{
public const XX = "bar"; // No error
}
// PHP 8.1
class Foo
{
final public const XX = "foo";
}
class Bar extends Foo
{
public const XX = "bar"; // Fatal error
}
Теперь константы класса можно объявить как окончательные (final), чтобы их нельзя было переопределить в дочерних классах.
Явное восьмеричное числовое обозначение
// PHP < 8.1
016 === 16; // false because `016` is octal for `14` and it's confusing
016 === 14; // true
// PHP 8.1
0o16 === 16; // false — not confusing with explicit notation
0o16 === 14; // true
Теперь можно записывать восьмеричные числа с явным префиксом 0o
.
Файберы
// PHP < 8.1
$httpClient->request('https://example.com/')
->then(function (Response $response) {
return $response->getBody()->buffer();
})
->then(function (string $responseBody) {
print json_decode($responseBody)['code'];
});
// PHP 8.1
$response = $httpClient->request('https://example.com/');
print json_decode($response->getBody()->buffer())['code'];
Файберы — это примитивы для реализации облегчённой невытесняющей конкурентности. Они являются средством создания блоков кода, которые можно приостанавливать и возобновлять, как генераторы, но из любой точки стека. Файберы сами по себе не предоставляют возможностей асинхронного выполнения задач, всё равно должен быть цикл обработки событий. Однако они позволяют блокирующим и неблокирующим реализациям использовать один и тот же API.
Файберы позволяют избавиться от шаблонного кода, который ранее использовался с помощью Promise::then()
или корутин на основе генератора. Библиотеки обычно создают дополнительные абстракции вокруг файберов, поэтому нет необходимости взаимодействовать с ними напрямую.
Поддержка распаковки массивов со строковыми ключами
// PHP < 8.1
$arrayA = ['a' => 1];
$arrayB = ['b' => 2];
$result = array_merge(['a' => 0], $arrayA, $arrayB);
// ['a' => 1, 'b' => 2]
// PHP 8.1
$arrayA = ['a' => 1];
$arrayB = ['b' => 2];
$result = ['a' => 0, ...$arrayA, ...$arrayB];
// ['a' => 1, 'b' => 2]
PHP раньше поддерживал распаковку массивов с помощью оператора ...
, но только если массивы были с целочисленными ключами. Теперь можно также распаковывать массивы со строковыми ключами.
Улучшения производительности
Время запроса демо-приложения Symfony 25 последовательных запусков по 250 запросов (сек) (чем меньше тем лучше)
Результат (относительно PHP 8.0):
- Ускорение демо-приложения Symfony на 23,0%
- Ускорение WordPress на 3,5%
Функциональность с улучшенной производительностью в PHP 8.1:
- Бэкенд JIT для ARM64 (AArch64).
- Кеш наследования (не требуется связывать классы при каждом запросе).
- Ускорено разрешение имени класса (исключены преобразование регистра имени и поиск по хешу).
- Улучшения производительности
timelib
иext/date
. - Улучшения итераторов файловой системы SPL.
- Оптимизация функций
serialize()
/unserialize()
. - Оптимизация некоторых внутренних функций (
get_declared_classes()
,explode()
,strtr()
,strnatcmp()
,dechex()
). - Улучшения и исправления JIT.
Новые классы, интерфейсы и функции
- Добавлен новый атрибут
#[ReturnTypeWillChange]
. - Добавлены функции
fsync
иfdatasync
. - Добавлена новая функция
array_is_list
. - Новые функции Sodium XChaCha20.
Устаревшая функциональность и изменения в обратной совместимости
- Передача значения
NULL
параметрам встроенных функций, не допускающим значениеNULL
, объявлена устаревшей. - Предварительные типы возвращаемых значений во встроенных методах классов PHP
- Интерфейс
Serializable
объявлен устаревшим. - Функции по кодированию/декодированию HTML-сущностей по умолчанию преобразуют одинарные кавычки и заменяют недопустимые символы на символ замены Юникода.
- Ограничены способы использования переменной
$GLOBALS
. - Модуль MySQLi: режим ошибок по умолчанию установлен на выбрасывание исключения.
- Неявное преобразование числа с плавающей точкой к целому с потерей ненулевой дробной части объявлено устаревшим.
- Модуль finfo: ресурсы
file_info
заменены на объектыfinfo
. - Модуль IMAP: ресурсы
imap
заменены на объектыIMAP\Connection
. - Модуль FTP: ресурсы
Connection
заменены на объектыFTP\Connection
. - Модуль GD:
Font identifiers
заменены на объектыGdFont
. - Модуль LDAP: ресурсы заменены на объекты
LDAP\Connection
,LDAP\Result
иLDAP\ResultEntry
. - Модуль PostgreSQL: ресурсы заменены на объекты
PgSql\Connection
,PgSql\Result
иPgSql\Lob
. - Модуль Pspell: ресурсы
pspell
,pspell config
заменены на объектыPSpell\Dictionary
,PSpell\Config
.
8.0
PHP 8.0 — большое обновление языка PHP.
Оно содержит множество новых возможностей и оптимизаций, включая именованные аргументы, union type, атрибуты, упрощённое определение свойств в конструкторе, выражение match, оператор nullsafe, JIT и улучшения в системе типов, обработке ошибок и консистентности.
Именованные аргументы
// PHP7
htmlspecialchars($string, ENT_COMPAT | ENT_HTML401, 'UTF-8', false);
// PHP8
htmlspecialchars($string, double_encode: false);
- Указывайте только необходимые параметры, пропускайте необязательные.
- Порядок аргументов не важен, аргументы самодокументируемы.
Атрибуты
// PHP 7
class PostsController
{
/**
* @Route("/api/posts/{id}", methods={"GET"})
*/
public function get($id) { /* ... */ }
}
// PHP 8
class PostsController
{
#[Route("/api/posts/{id}", methods: ["GET"])]
public function get($id) { /* ... */ }
}
Вместо аннотаций PHPDoc вы можете использовать структурные метаданные с нативным синтаксисом PHP.
Объявление свойств в конструкторе
// PHP 7
class Point {
public float $x;
public float $y;
public float $z;
public function __construct(
float $x = 0.0,
float $y = 0.0,
float $z = 0.0
) {
$this->x = $x;
$this->y = $y;
$this->z = $z;
}
}
// PHP 8
class Point {
public function __construct(
public float $x = 0.0,
public float $y = 0.0,
public float $z = 0.0,
) {}
}
Меньше шаблонного кода для определения и инициализации свойств.
Тип Union
// PHP 7
class Number {
/** @var int|float */
private $number;
/**
* @param float|int $number
*/
public function __construct($number) {
$this->number = $number;
}
}
new Number('NaN'); // Нет ошибки
// PHP 8
class Number {
public function __construct(
private int|float $number
) {}
}
new Number('NaN'); // TypeError
Вместо аннотаций PHPDoc для объединённых типов вы можете использовать объявления типа union, которые проверяются во время выполнения.
Выражение Match
// PHP 7
switch (8.0) {
case '8.0':
$result = "О нет!";
break;
case 8.0:
$result = "То, что я и ожидал";
break;
}
echo $result;
//> О нет!
// PHP 8
echo match (8.0) {
'8.0' => "О нет!",
8.0 => "То, что я и ожидал",
};
//> То, что я и ожидал
Новое выражение match похоже на оператор switch со следующими особенностями:
- Match — это выражение, его результат может быть сохранён в переменной или возвращён.
- Условия match поддерживают только однострочные выражения, для которых не требуется управляющая конструкция break;.
- Выражение match использует строгое сравнение.
Оператор Nullsafe
// PHP 7
$country = null;
if ($session !== null) {
$user = $session->user;
if ($user !== null) {
$address = $user->getAddress();
if ($address !== null) {
$country = $address->country;
}
}
}
// PHP 8
$country = $session?->user?->getAddress()?->country;
Вместо проверки на null
вы можете использовать последовательность вызовов с новым оператором Nullsafe. Когда один из элементов в последовательности возвращает null
, выполнение прерывается и вся последовательность возвращает null
.
Улучшенное сравнение строк и чисел
// PHP 7
0 == 'foobar' // true
// PHP 8
0 == 'foobar' // false
При сравнении с числовой строкой PHP 8 использует сравнение чисел. В противном случае число преобразуется в строку и используется сравнение строк.
Ошибки согласованности типов для встроенных функций
// PHP 7
strlen([]); // Warning: strlen() expects parameter 1 to be string, array given
array_chunk([], -1); // Warning: array_chunk(): Size parameter expected to be greater than 0
// PHP 8
strlen([]); // TypeError: strlen(): Argument #1 ($str) must be of type string, array given
array_chunk([], -1); // ValueError: array_chunk(): Argument #2 ($length) must be greater than 0
Большинство внутренних функций теперь выбрасывают исключение Error, если при проверке параметра возникает ошибка.
Компиляция Just-In-Time
PHP 8 представляет два механизма JIT-компиляции. Трассировка JIT, наиболее перспективная из них, на синтетических бенчмарках показывает улучшение производительности примерно в 3 раза и в 1,5–2 раза на некоторых долго работающих приложениях. Стандартная производительность приложения находится на одном уровне с PHP 7.4.
Относительный вклад JIT в производительность PHP 8
Улучшения в системе типов и обработке ошибок
- Более строгие проверки типов для арифметических/побитовых операторов RFC
- Проверка методов абстрактных трейтов RFC
- Правильные сигнатуры магических методов RFC
- Реклассификация предупреждений движка RFC
- Фатальная ошибка при несовместимости сигнатур методов RFC
- Оператор
@
больше не подавляет фатальные ошибки. - Наследование с private методами RFC
- Новый тип mixed RFC
- Возвращаемый тип
static
RFC - Типы для стандартных функций E-mail Тема
- Непрозрачные объекты вместо ресурсов для Curl, Gd, Sockets, OpenSSL, XMLWriter, e XML расширения
Прочие улучшения синтаксиса
- Разрешена запятая в конце списка параметров RFC и в списке use замыканий RFC
- Блок catch без указания переменной RFC
- Изменения синтаксиса переменных RFC
- Имена в пространстве имен рассматриваются как единый токен RFC
- Выражение Throw RFC
- Добавление
::class
для объектов RFC
Новые классы, интерфейсы и функции
- Класс Weak Map
- Интерфейс Stringable
- str_contains(), str_starts_with(), str_ends_with()
- fdiv()
- get_debug_type()
- get_resource_id()
- Объектно-ориентированная функция token_get_all()
- Новые API для обходения и обработки DOM