что обещает нам новый выпуск
PHP 8.1 был выпущен 25 ноября 2021 года
В этом посте мы последовательно рассмотрим все функции, улучшения производительности, изменения и прекращения поддержки.
Как и другие выпуски PHP, PHP 8.1 добавит несколько приятных новых возможностей. Также я озвучу некоторые функции, которые еще не были реализованы, но у которых есть хорошие шансы появиться в конечном релизе PHP. Я обязательно буду это отмечать. Итак приступим!
Распаковка массива уже была разрешена в PHP 7.4, но работала только с числовыми ключами. Причина, по которой строковые ключи не поддерживались раньше, заключается в том, что не было единого мнения о том, как объединять дубликаты массивов. RFC четко решает эту проблему, следуя семантике array_merge:
$array = ["a" => 1];
$array2 = ["b" => 2];
$arrayMerge = ["a" => 0, ...$array, ...$array2];
var_dump($arrayMerge); // ["a" => 1, "b" => 2]
Тип never может быть использован для того, чтобы указать, что функция фактически остановит поток приложения. Это можно сделать, выбросив исключение, вызывая exit/die или используя другие подобные функции.
function redirect(string $url): never {
header('Location: ' . $url);
exit();
}
redirect('Test'); // код гарантирует не продолжение.
do_something_else();
Тип возвращаемого значения never аналогичен существующему типу возвращаемого значения void, но тип never гарантирует, что программа завершится или выдаст исключение. Другими словами, объявленная функция/метод never типом вообще не должна вызывать return.
function foo(): never {
return;
}
foo();
TypeError: dispatch(): Nothing was expected to be returned
Как видите, если функция/метод с типом never никак не сгенерирует исключение, или прекратит работу, то PHP выдаст исключение TypeError.
А если при never типе вызвать return, то PHP выдаст Fatal Error. Узнать об этом можно будет только во время вызова, а не во время синтаксического анализа.
function foo(): never {
return;
}
foo();
Fatal error: A never function must not return in ... on line 2
Исходный RFC предлагал использовать noreturn для этой цели. Впрочем последующее голосование в RFC прошло уже в пользу never и он был избран.
Возможно, время от времени, вам приходится иметь с этим дело: определять, находятся ли ключи массива в числовом порядке, начиная с индекса 0. Точно так же, как json_encode решает, должен ли массив быть закодирован как массив или объект.
PHP 8.1 добавляет встроенную функцию, чтобы определить, является ли массив списком с этой семантикой или нет:
$list = ["a", "b", "c"];
array_is_list($list); // true
$notAList = [1 => "a", 2 => "b", 3 => "c"];
array_is_list($notAList); // false
$alsoNotAList = ["a" => "a", "b" => "b", "c" => "c"];
array_is_list($alsoNotAList); // false
Любой массив с ключами, не начинающимися с нуля, или любой массив, в котором не все ключи являются целыми числами в последовательном порядке, результат будет false:
// ключи начинаются не с 0
array_is_list([1 => 'apple', 'orange']); // false
// Ключи не отсортированы
array_is_list([1 => 'apple', 0 => 'orange']); // false
// Не все числовые ключи
array_is_list([0 => 'apple', 'foo' => 'bar']); false
// Непоследовательные ключи
array_is_list([0 => 'apple', 2 => 'bar']); false
Полифил функции будет выглядеть, как:
function array_is_list(array $array): bool {
if (empty($array)) {
return true;
}
$current_key = 0;
foreach ($array as $key => $noop) {
if ($key !== $current_key) {
return false;
}
++$current_key;
}
return true;
}
PHP 8.1 добавляет fsync и fdatasync - функции принудительной синхронизации изменений файлов на диск и и обеспечивающие очистку буферов записи операционной системы.
$file = fopen("sample.txt", "w");
fwrite($file, "Some content");
if (fsync($file)) {
echo "File has been successfully persisted to disk.";
}
fclose($file);
Поскольку синхронизация диска - это операция файловой системы, функция fsync будет работать только с обычными файловыми потоками. При попытке синхронизации нефайловых потоков будет выдано предупреждение.
Теперь вы можете использовать 0o (нуль и маленькая буква o) и 0O (нуль и большая буква O) для обозначения восьмеричных чисел. Предыдущее обозначение с префиксом числа "0" по-прежнему работает:
016 === 0o16; // true
016 === 0O16; // true
Перечисления будут добавлены в PHP 8.1.
Добавление перечислений было бы значительным улучшением в PHP, поэтому я, со своей стороны, с нетерпением жду дальнейшего развития этого RFC. Чтобы вы могли быстро увидеть, как они будут выглядеть, вот пример кода:
enum Status {
case Pending;
case Active;
case Archived;
}
И вот как они будут использоваться:
class Order
{
public function __construct(
private Status $status = Status::Pending;
) {}
public function setStatus(Status $status): void
{
// …
}
}
$order->setStatus(Status::Active);
Файберы - также известные как «зеленые потоки» - это низкоуровневый механизм управления параллелизмом. Вероятно, вы не будете использовать их непосредственно в своих приложениях, но такие фреймворки, как Amphp и ReactPHP, будут широко их использовать.
Вот простой пример использования файберов:
$fiber = new Fiber(function (): void {
$valueAfterResuming = Fiber::suspend('after suspending');
// …
});
$valueAfterSuspending = $fiber->start();
$fiber->resume('after resuming');
Дмитрий Стогов добавил некоторые улучшения в opcache, он называет это "кешем наследования". Эта функция позволяет кэшировать ссылки между классами, так же как связанные классы могут быть предварительно загружены в PHP 7.4.
Дмитрий сообщает о приросте производительности от 5% до 8% благодаря этому изменению, приятная небольшая деталь, на которую следует обратить внимание в PHP 8.1.
Этот RFC позволяет использовать ключевое слово new в определениях функций в качестве параметра по умолчанию, а также в аргументах атрибутов и в других местах.
class MyClass {
public function __construct(
private Logger $logger = new Logger(),
) {}
}
Более подробно можно почитать здесь.
Свойства класса могут быть помечены как доступные только для чтения, что означает, что они могут быть записаны только один раз.
class PostData {
public function __construct(
public readonly string $title,
public readonly DateTimeImmutable $date,
) {}
}
Попытка изменить свойство только для чтения после его инициализации приведет к ошибке:
$post = new Post('Title', /* … */);
$post->title = 'Other';
Error: Cannot modify readonly property Post::$title
Теперь можно будет выполнить закрытие вызываемого объекта, вызвав этот вызываемый объект и передав (...) в качестве аргумента:
function foo(int $a, int $b) { /* … */ }
$foo = foo(...);
$foo(a: 1, b: 2);
Вы уже знаете о типах объединения в PHP 8.0, и типы пересечений представляют собой аналогичную функцию. Если для типов объединения требуется, чтобы входные данные были одного из заданных типов, для типов пересечений входные данные должны быть всех указанных типов. Типы пересечений особенно полезны, когда вы работаете с большим количеством интерфейсов:
Типы пересечений в настоящее время изначально не поддерживаются языком. Вместо этого нужно использовать аннотации phpdoc и/или использовать типизированные свойства , как показано в следующем примере:
class Test {
private ?Traversable $traversable = null;
private ?Countable $countable = null;
/** @var Traversable&Countable */
private $both = null;
public function __construct($countableIterator) {
$this->traversable =& $this->both;
$this->countable =& $this->both;
$this->both = $countableIterator;
}
}
Типы чистых пересечений, позволяют указать с использованием синтаксиса T1&T2&... и их можно использовать везде там, где в настоящее время принимаются типы:
function generateSlug(HasTitle&HasId $post) {
return strtolower($post->getTitle()) . $post->getId();
}
Если вам нравится этот стиль программирования, вам нужно будет создать новый Sluggable интерфейс и реализовать его в $post, типы пересечений избавляются от этих накладных расходов.
Константы классов в PHP можно переопределить во время наследования:
class Foo
{
public const X = "foo";
}
class Bar extends Foo
{
public const X = "bar";
}
Начиная с PHP 8.1, вы можете пометить такие константы final, чтобы предотвратить это:
class Foo
{
final public const X = "foo";
}
class Bar extends Foo
{
public const X = "bar";
Fatal error: Bar::X cannot override final constant Foo::X
}
$_FILES- это суперглобальная переменная в PHP, до PHP 8.1 массив $_FILES содержал относительные пути в файловой системе пользователя для учета подкаталогов, содержащих файлы с одинаковыми именами. В PHP 8.1 $_FILES содержит новый ключ с именем full_path, который содержит полный путь, указанный браузером.
var_dump($_FILES);
array(1) {
["myupload"]=> array(6) {
["name"]=> array(2) {
[0]=> string(8) "file.txt"
[1]=> string(8) "file.txt"
}
["full_path"]=> array(2) {
[0]=> string(19) "foo/test1/file.txt"
[1]=> string(19) "foo/test2/file.txt"
}
["tmp_name"]=> array(2) {
[0]=> string(14) "/tmp/phpV1J3EM"
[1]=> string(14) "/tmp/phpzBmAkT"
}
// ... + error, type, size
}
}
Здесь вы можете почитать более подробно о новом ключе full_path.
Хотя PHP 8.1 является последующей версией после PHP 8, все же будут внесены некоторые изменения, которые технически могут быть критическими, а также устаревшими. Давайте обсудим их по очереди.
Скорее всего, вы можете столкнуться с этим уведомлением об устаревании при обновлении до PHP 8.1:
Return type should either be compatible with IteratorAggregate::getIterator(): Traversable,
or the #[ReturnTypeWillChange] attribute should be used to temporarily suppress the notice
Вы можете заметить, эта ошибка выскакивают при использовании phpunit/phpunit, symfony/finder и некоторых других популярных пакетов с открытым исходным кодом. Произошло то, что внутренние функции начали использовать правильные возвращаемые типы. Если вы расширяете класс из стандартной библиотеки (например, IteratorAggregate), вам также необходимо добавить эти возвращаемые типы.
Исправление простое: обновите код вашего vendor, если ошибка возникает в стороннем пакете (большинство из них уже исправлены в последних выпусках). Если ошибка возникает в вашем коде, вы можете добавить атрибут ReturnTypeWillChange, подавляя ошибку до PHP 9.0. Вот пример расширения класса DateTime:
class MyDateTime extends DateTime
{
/**
* @return DateTime|false
*/
#[ReturnTypeWillChange]
public function modify(string $modifier)
{
return false;
}
}
Или вы можете просто добавить тип возврата:
class MyDateTime extends DateTime
{
public function modify(string $modifier): DateTime|false
{
return false;
}
}
Небольшое изменение способа использования $GLOBALS существенно повлияет на производительность всех операций с массивами. Никита Попов отлично справляется с объяснением проблемы и решения в RFC . Это изменение означает, что в некоторых крайних случаях работать с $GLOBAL больше нельзя.
«То, что больше не поддерживается, - это запись в $GLOBALS, взятую как единое целое».
Все нижеперечисленное вызовет ошибку времени компиляции:
$GLOBALS = [];
$GLOBALS += [];
$GLOBALS =& $x;
$x =& $GLOBALS;
unset($GLOBALS);
Кроме того, передача $GLOBALS по ссылке вызовет ошибку времени выполнения:
by_ref($GLOBALS); // Run-time error
Никита проанализировал 2000 лучших пакетов на сайте packagist и нашел только 23 случая, на которые повлияет это изменение. Можно сделать вывод, что влияние этого технически критического изменения будет незначительным, поэтому internals решили добавить его в PHP 8.1. Помните, что большинство из нас выиграет от этого изменения, учитывая положительное влияние на производительность, которое оно оказывает во всем нашем коде.
Эти изменения являются частью долгосрочного видения преобразования всех ресурсов в выделенные объекты. Вы можете прочитать об этом здесь.
Fileinfo функции с объектами finfo. Функции вроде finfo_file и finfo_open используются для приема и возврата ресурсов. Начиная с PHP 8.1, они работают с объектами finfo.
Функции IMAP с объектами IMAPConnection. Как и при изменении информации о файле, IMAP работает как imap_body, а imap_open больше не работает с ресурсами.
Это изменение простое: внутренние функции в настоящее время принимают null аргументы, которые не допускают значения NULL, этот RFC не рекомендует такое поведение. Например, сейчас это возможно:
str_contains("string", null);
В PHP 8.1 такие ошибки будут вызывать предупреждение об устаревании, в PHP 9 они будут преобразованы в ошибки типа.
PHP изначально допускает автовивификацию (автоматическое создание массивов из ложных значений). Эта функция очень полезна и используется во многих проектах PHP, особенно если переменная не определена. Однако есть небольшая странность, позволяющая создать массив из ложного и нулевого значения.
Вы можете прочитать подробности на странице RFC. Таким образом, это поведение устарело:
$array = false;
$array[] = 2;
Automatic conversion of false to array is deprecated
Вот краткое изложение наиболее значительных изменений:
На данный момент это все, имейте в виду, что я буду регулярно обновлять этот пост в течение года, поэтому можете следить за этим постом.
Веб-разработчик со стажем программирования более 13 лет, постоянно учусь, люблю делать новые проекты.