PHP 13214 ~ 11 мин.

Что нового в PHP 8.1

Что нового в PHP 8.1

PHP 8.1 был выпущен 25 ноября 2021 года

В этом посте мы последовательно рассмотрим все функции, улучшения производительности, изменения и прекращения поддержки.

Новые возможности

Как и другие выпуски PHP, PHP 8.1 добавит несколько приятных новых возможностей. Также я озвучу некоторые функции, которые еще не были реализованы, но у которых есть хорошие шансы появиться в конечном релизе PHP. Я обязательно буду это отмечать. Итак приступим!

Распаковка массива с помощью строковых ключей rfc

Распаковка массива уже была разрешена в 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 rfc 

Тип 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 и он был избран.

Новая функция array_is_list rfc 

Возможно, время от времени, вам приходится иметь с этим дело: определять, находятся ли ключи массива в числовом порядке, начиная с индекса 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
  • array_is_list принимает только параметры типа array. Передача любого другого типа вызовет исключение TypeError.
  • array_is_list не принимает iterable или другие объекты класса, подобные массиву, такие как ArrayAccess, SPLFixedArray и т.д.
  • array_is_list объявлен в глобальном пространстве имен.  

Полифил функции будет выглядеть, как:


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;
}


Новая функция fsync rfc

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 будет работать только с обычными файловыми потоками. При попытке синхронизации нефайловых потоков будет выдано предупреждение.


Явное восьмеричное целочисленное буквальное обозначение rfc

Теперь вы можете использовать 0o (нуль и маленькая буква o) и 0O (нуль и большая буква O) для обозначения восьмеричных чисел. Предыдущее обозначение с префиксом числа "0" по-прежнему работает:


016 === 0o16; // true
016 === 0O16; // true

Enums (Перечисления) rfc

Перечисления будут добавлены в 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);

Fibers rfc

Файберы - также известные как «зеленые потоки» - это низкоуровневый механизм управления параллелизмом. Вероятно, вы не будете использовать их непосредственно в своих приложениях, но такие фреймворки, как Amphp и ReactPHP, будут широко их использовать.

Вот простой пример использования файберов: 


$fiber = new Fiber(function (): void {
    $valueAfterResuming = Fiber::suspend('after suspending');
    
    // … 
});
 
$valueAfterSuspending = $fiber->start();
 
$fiber->resume('after resuming');

 

Улучшения производительности pr

Дмитрий Стогов добавил некоторые улучшения в opcache, он называет это "кешем наследования". Эта функция позволяет кэшировать ссылки между классами, так же как связанные классы могут быть предварительно загружены в PHP 7.4.

Дмитрий сообщает о приросте производительности от 5% до 8% благодаря этому изменению, приятная небольшая деталь, на которую следует обратить внимание в PHP 8.1.


New в инициализаторах rfc

Этот RFC позволяет использовать ключевое слово new в определениях функций в качестве параметра по умолчанию, а также в аргументах атрибутов и в других местах.


class MyClass {
    public function __construct(
        private Logger $logger = new Logger(),
    ) {}
}

Более подробно можно почитать здесь

Свойства только для чтения rfc

Свойства класса могут быть помечены как доступные только для чтения, что означает, что они могут быть записаны только один раз.


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

Первоклассный вызываемый синтаксис rfc

Теперь можно будет выполнить закрытие вызываемого объекта, вызвав этот вызываемый объект и передав (...) в качестве аргумента:


function foo(int $a, int $b) { /* … */ }

$foo = foo(...);

$foo(a: 1, b: 2);

 

Типы чистых пересечений rfc

Вы уже знаете о типах объединения в 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, типы пересечений избавляются от этих накладных расходов.

Константы финального класса rfc

Константы классов в 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 full_path для загрузки каталога

$_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

Небольшое изменение способа использования $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

Это изменение простое: внутренние функции в настоящее время принимают null аргументы, которые не допускают значения NULL, этот RFC не рекомендует такое поведение. Например, сейчас это возможно:


str_contains("string", null);

 В PHP 8.1 такие ошибки будут вызывать предупреждение об устаревании, в PHP 9 они будут преобразованы в ошибки типа.


Автовивификация на false rfc

PHP изначально допускает автовивификацию (автоматическое создание массивов из ложных значений). Эта функция очень полезна и используется во многих проектах PHP, особенно если переменная не определена. Однако есть небольшая странность, позволяющая создать массив из ложного и нулевого значения.

Вы можете прочитать подробности на странице RFC. Таким образом, это поведение устарело:


$array = false;

$array[] = 2;

Automatic conversion of false to array is deprecated

 

Другие небольшие изменения

Вот краткое изложение наиболее значительных изменений:


  • MYSQLI_STMT_ATTR_UPDATE_MAX_LENGTH больше не действует
  • MYSQLI_STORE_RESULT_COPY_DATA больше не действует
  • PDO::ATTR_STRINGIFY_FETCHES теперь также работает с логическими значениями
  • Целые числа и числа с плавающей запятой в наборах результатов PDO MySQL и Sqlite будут возвращаться с использованием собственных типов PHP вместо строк при использовании эмулированных подготовленных операторов.
  • Такие функции, как htmlspecialchars и htmlentities  по умолчанию, также переходят 'в '; неверно сформированный UTF-8 также будет заменен символом Юникода вместо того, чтобы приводить к пустой строке
  • У hash, hash_file и hash_init есть дополнительный аргумент  $options, по умолчанию он имеет значение [], поэтому не повлияет на ваш код.
  • Новая поддержка для MurmurHash3 и xxHash



 На данный момент это все, имейте в виду, что я буду регулярно обновлять этот пост в течение года, поэтому можете следить за этим постом.


Что думаешь?

Категории
  • PHP 66
  • Заметки 15
  • Безопасность 3
  • Флуд 2
  • Nginx 2
  • ИТ новости 2
  • Видео 1
  • Docker 1
  • Roadmap 1
  • Архитектура 0

Хочешь поддержать сайт?

Делаем из мухи слона

sergeymukhin.com

персональный блог о веб-разработке от Сергея Мухина. Блог был основан в 2018 году, и собирался уделять основное внимание последним тенденциям, учебным пособиям, а также советам и рекомендациям, позволяющим начинающим девелоперам встать быстрее на правильную дорогу веб разработки, но что-то пошло не так 😃

Релизы PHP 8.4

Дата Релиз
8 Июня 2024 Альфа 1
20 Июня 2024 Альфа 2
04 Июля 2024 Альфа 3
16 Июля 2024 Feature freeze
18 Июля 2024 Бета 1
01 Августа 2024 Бета 2
15 Августа 2024 Бета 3
29 Августа 2024 RC 1
12 Сентября 2024 RC 2
26 Сентября 2024 RC 3
10 Октября 2024 RC 4
24 Октября 2024 RC 5
07 Ноября 2024 RC 6
21 Ноября 2024 GA

Что нового?