PHP 8 — какие возможности появятся в новой версии языка и как их использовать

Скриптовый язык PHP 26 ноября 2020 года обновился до новой основной версии — PHP 8, а 18 июня 2020 года уже вышла его первая альфа-версия.

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

Новые особенности

Разработчики языка отмечают, что PHP 8 до сих пор находится в стадии активной разработки, поэтому список новых функций со временем будет увеличиваться.

Union Types

Учитывая динамически типизированную природу PHP, существует множество случаев, когда использование типов объединения может быть полезным. Union Types представляют собой совокупность двух или более типов, которые указывают, что можно использовать любой из них.

public function foo(Foo|Bar $input): int|float;

При этом void никогда не может быть частью Union Types, так как он означает «вообще никакого возвращаемого значения». Кроме того, с помощью |null или ?-нотации можно объявить nullable-объединения:

public function foo(Foo|null $foo): void;

public function bar(?Bar $bar): void;

JIT

Разработчики обещают улучшения в производительности PHP 8 в том числе благодаря стратегии компиляции JIT (just in time, «в нужный момент»). В JIT код сначала переводится в промежуточное представление, и только потом — в машинный код, зависящий от архитектуры. Это значит, что он в ходе исполнения будет превращаться в машинный код, который будет выполняться не виртуальной машиной Zend VM, на которой построен язык, а непосредственно на процессоре.

Nullsafe-оператор

Если вы работали с оператором объединения с null, вы уже знакомы с его недостатками: его нельзя применить в цепочке вызовов методов. Вместо этого вам нужны промежуточные проверки или придется полагаться на optional помощников, предоставляемых некоторыми платформами:

$startDate = $dateAsString = $booking->getStartDate();

$dateAsString = $startDate ? $startDate->asDateTimeString() : null;

С добавлением nullsafe-оператора разработчик может видеть поведение методов, подобное поведению при объединению с null.

$dateAsString = $booking->getStartDate()?->asDateTimeString();

Именованные аргументы

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

function foo(string $a, string $b, ?string $c = null, ?string $d = null)
{ /* … */ }
foo(
    b: 'value b',
    a: 'value a',
    d: 'value d',
);

Атрибуты

Атрибуты — в других языках программирования известные как аннотации, предлагают новый способ добавить метаданные в классы, без необходимости парсить докблок-комментарии.

Вот пример того, как выглядят атрибуты:

use AppAttributesExampleAttribute;

@@ExampleAttribute

class Foo
{
    @@ExampleAttribute
    public const FOO = 'foo';
    @@ExampleAttribute
    public $x;
    @@ExampleAttribute
    public function foo(@@ExampleAttribute $bar) { }
}

@@Attribute

class ExampleAttribute
{
    public $value;
    
    public function __construct($value)
    {
        $this->value = $value;
    }
}

Выражение соответствия

Выражение match можно назвать старшим братом выражения switch — оно может возвращать значения, комбинировать условия, и при этом не требует break операторов. Кроме того, match не выполняет никакого приведения типов.

$result = match($input) {
  0 => "hello",
  '1', '2', '3' => "world",
};

Краткий синтаксис для объединения свойств класса и конструктора

В PHP 8 появится синтаксический сахар для создания объектов значений или объектов передачи данных. Вместо отдельного указания определения свойств класса и конструктора для них, PHP теперь может объединять их в один краткий синтаксис.

Вместо такого кода:

class Money 
{
    public Currency $currency;

    public int $amount;

    public function __construct(
        Currency $currency,
        int $amount,
    ) {
        $this->currency = $currency;
        $this->amount = $amount;
    }
}

// Теперь можно будет кратко писать вот так:

class Money 
{
    public function __construct(
        public Currency $currency,
        public int $amount,
    ) {}
}

Новый тип возврата static

Несмотря на то, что в PHP уже были возвращаемые типы — self и parent, до сих пор static не был допустимым типом возврата для этого языка программирования. Учитывая динамически типизированный характер PHP, эта функция будет полезна для многих разработчиков.

class Foo
{
    public function test(): static
    {
        return new static();
    }
}

Новый тип mixed

С добавлением скалярных типов в PHP 7, обнуляемых — в PHP 7.1, и, наконец, — объединенных типов в 8.0, PHP-разработчики могут явно объявлять информацию о типе для большинства параметров функции, callable-функций, а также свойств класса. Тем не менее, PHP не всегда поддерживает типы, и, скорее всего, он иногда будет пропускать информацию о них. Это приводит к тому, что значение будет неоднозначным, когда отсутствует информация о типе.

Явный mixed тип позволит разработчикам добавлять типы в параметры, свойства класса и возвращаемые значения из функции, чтобы указать, что информация о типе не была забыта, а её просто нельзя указать более точно.

mixed сам по себе означает один из этих типов:

  • array

  • bool

  • callable

  • int

  • float

  • null

  • object

  • resource

  • string

Обратите внимание, что mixed также может использоваться как параметр или тип свойства, а не только как тип возвращаемого значения.

Также обратите внимание, что, поскольку mixed уже включает null, его нельзя делать nullable. Такой код вызовет ошибку:

// Fatal error: Mixed types cannot be nullable, null is already part of the mixed type.
function bar(): ?mixed {}

(звезда) Зачем изучать PHP: рейтинг, перспективы, сферы применения

Throw-выражения

В PHP оператор throw не может создавать исключения там, где были разрешены только выражения, например стрелочные функции, оператор объединения и тернарный оператор. После обновления PHP будет преобразовывать инструкцию throw в выражение, это расширит функциональность использования языка.

$triggerError = fn () => throw new MyError();

$foo = $bar['offset'] ?? throw new OffsetDoesNotExist('offset');

Наследование частными методами

Раньше PHP применял одинаковые проверки наследования для public, protected и private- методов. Другими словами: private методы должны были быть определены так же, как protected и public. При этом такое определение не имеет смысла, так как private методы не будут доступны дочерним классам.

Это обновление изменило поведение таких методов — теперь проверки наследования больше не выполняются для private методов. Кроме того, использование final private function и раньше не имело особого смысла, поэтому теперь оно вызовет предупреждение:

Warning: Private methods cannot be final as they are never overridden by other classes

Weak maps

Слабая карта — это набор объектов, на которые в коде слабо ссылаются ключи, что может привести к их удалению сборщиками мусора. В PHP 8 добавляется класс WeakMaps для создания сохранения ссылки на объект, при этом она не препятствует удалению самого объекта.

Например, возьмем пример ORM — они часто реализуют кэши, где содержатся ссылки на классы сущностей, чтобы улучшить производительность отношений между сущностями. Эти сущности не могут быть собраны сборщиком мусора, пока кэш имеет ссылку на них, даже если кэш является единственной реальной ссылкой на них. Если теперь кэш будет использовать слабые ссылки и WeakMaps, то PHP сможет справляться с мусором. Особенно в случае с ORM, которые могут управлять несколькими сотнями, если не тысячами объектов в ходе запроса.

Пример использования WeakMaps в PHP 8:

class Foo 
{
    private WeakMap $cache;

    public function getSomethingWithCaching(object $obj): object
    {
        return $this->cache[$obj]
           ??= $this->computeSomethingExpensive($obj);
    }
}

::class на объектах

Небольшая, но полезная новая фича: теперь можно использовать ::class на объектах, вместо того, чтобы использовать get_class(). Работает так же, как и get_class().

$foo = new Foo();

var_dump($foo::class);

Неименованные исключения

Всякий раз, когда вы хотели перехватить исключение до PHP 8, нужно было сохранить его в переменной независимо от того, использовали ли вы эту переменную или нет. Теперь можно перехватить исключения без указания переменной.

Если раньше программисту приходилось писать такой код:

try {
    // Something goes wrong
} catch (MySpecialException $exception) {
    Log::error("Something went wrong");
}

// То в PHP 8 это будет выглядеть уже так:
try {
    // Something goes wrong
} catch (MySpecialException) {
    Log::error("Something went wrong");
}

Обратите внимание, что необходимо всегда указывать тип, поскольку PHP 8 не разрешает иметь пустой catch. Если вы хотите перехватить все исключения и ошибки, вы можете использовать их Throwable как тип исключения.

Завершающая запятая в списках параметров

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

public function(
    string $parameterA,
    int $parameterB,
    Foo $objectfoo,
) {
    // …
}

Обратите внимание на запятую после $objectfoo.

Создать DateTime объекты из интерфейса

Вы уже можете создать DateTime объект из DateTimeImmutable объекта. Однако наоборот в PHP было сделать очень сложно. Теперь в языке появился новый обобщенный способ конвертировать эти объекты друг в друга.

DateTime::createFromImmutable($immutableDateTime)DateTime::createFromInterface()DatetimeImmutable::createFromInterface()DateTimeDateTimeImmutable

DateTime::createFromInterface(DateTimeInterface $other);

DateTimeImmutable::createFromInterface(DateTimeInterface $other);

Новый Stringable интерфейс

Интерфейс Stringable можно использовать для аннотации типов или имплементации метода __toString (). Кроме того, всякий раз, когда класс, содержащий __toString (), будет автоматически имплементировать этот интерфейс «под капотом», и нет необходимости вручную реализовывать его.

class Foo
{
    public function __toString(): string
    {
        return 'foo';
    }
}

function bar(Stringable $stringable) { /* … */ }

bar(new Foo());
bar('abc');

Новая функция проверки str_contains()

Наконец-то на PHP больше не нужно полагаться на strpos(), чтобы узнать, содержит ли строка другую строку.

// Вместо этого:
if (strpos('string with lots of words', 'words') !== false) { /* … */ }

// Можно использовать вот это:
if (str_contains('string with lots of words', 'words')) { /* … */ }

Новые функции str_starts_with() и str_ends_with()

Ещё две давно назревшие функции. str_starts_with() проверяет, начинается ли строка с другой строки, и возвращает логическое значение ( true/ false).

str_ends_with() проверяет, заканчивается ли строка другой строкой, и возвращает логическое значение ( true/ false).

str_starts_with('haystack', 'hay'); // true

str_ends_with('haystack', 'stack'); // true

Новая функция деления на 0 fdiv()

Новая fdiv() функция делает процессы, похожие на функции fmod()и intdiv() функции — позволяет делить на 0. Вместо ошибки в результате вы получите INF, -INF или NaN, в зависимости от случая.

Новая функция get_debug_type()

Функция get_debug_type() возвращает тип переменной. При этом get_debug_type() возвращает более полезный вывод для массивов, строк, анонимных классов и объектов, чем стандартный gettype().

Например, вызов gettype() для класса FooBar вернет object. Использование get_debug_type() вернет имя класса.

Новая функция get_resource_id()

Ресурсы — это специальные переменные в PHP, ссылающиеся на внешние ресурсы. Например, соединение MySQL или обработчик файла — каждому из них присваивается идентификатор, хотя ранее единственным способом узнать, что это идентификатор, было преобразование ресурса в int:

$resourceId = (int) $resource;

PHP 8 добавляет get_resource_id() функции, делая эту операцию более очевидной и безопасной для типов:

$resourceId = get_resource_id($resource);

Улучшение абстрактных методов трейтов

Трейты могут содержать абстрактные методы, которые должны быть реализованы классами, использующими их. При этом важно, что до PHP 8 сигнатура этих реализаций методов не проверялась. То есть раньше приходилось писать так:

trait Test {
    abstract public function test(int $input): int;
}

class UsesTrait
{
    use Test;

    public function test($input)
    {
        return $input;
    }
}

PHP 8 будет выполнять правильную проверку сигнатуры метода при использовании признака и реализации его абстрактных методов. Это означает, что вам нужно написать теперь так:

class UsesTrait
{
    use Test;

    public function test(int $input): int
    {
        return $input;
    }
}

Объект реализации token_get_all()

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

Изменения синтаксиса переменных

В этом обновлении изменились правила синтаксиса при работе с переменными в PHP. Например, при их разыменовывании.

Аннотация аргументов внутренней функции и типов возвращаемых значений

Теперь разработчики смогут добавлять информацию о типе внутренних функций. Это была давняя проблема языка, и, наконец, её удалось решить. Это означает, что внутренние функции и методы будут иметь полную информацию о типе в Reflection.

Запрет на выключение JSON

В настоящее время возможно скомпилировать PHP без расширения JSON с помощью ./configure —disable-json. Тем не менее, JSON чрезвычайно полезен, потому что он широко используется во многих случаях — в работе сайтов, выходных данных журналов, в качестве формата данных, который можно использовать для обмена данными со многими приложениями и языками программирования. Поэтому разработчики языка запретили выключать JSON.

Исправления критических ошибок языка

Стандартизация ошибок разных типов

Пользовательские функции в PHP уже генерируют ошибки TypeError, но внутренние функции — пока нет, они скорее выдают предупреждения и возвращают null. Начиная с PHP 8, поведение внутренних функций стало согласованным с пользовательскими.

Изменение классификации некоторых предупреждений

Многие ошибки, которые ранее вызывали только предупреждения или уведомления, были преобразованы в правильные ошибки:

  • Неопределенная переменная: Error исключение вместо Notice;

  • Неопределенный индекс массива: предупреждение вместо уведомления;

  • Деление на ноль: DivisionByZeroError исключение вместо предупреждения;

  • Попытка увеличить/уменьшить свойство «% s» не-объекта: Error исключение вместо предупреждения;

  • Попытка изменить свойство «% s» не-объекта: Error исключение вместо предупреждения;

  • Попытка назначить свойство «% s» не-объекта: Error исключение вместо предупреждения;

  • Создание объекта по умолчанию из пустого значения: Error исключение вместо предупреждения;

  • Попытка получить свойство «% s» не-объекта: предупреждение вместо уведомления;

  • Неопределенное свойство: % s :: $% s: предупреждение вместо уведомления;

  • Невозможно добавить элемент в массив, так как следующий элемент уже занят: Error исключение вместо предупреждения;

  • Невозможно сбросить смещение в переменной, не являющейся массивом: Error исключение вместо предупреждения;

  • Нельзя использовать скалярное значение в качестве массива: Error исключение вместо предупреждения;

  • Только массивы и Traversables могут быть распакованы: TypeError исключение вместо предупреждения;

  • Указан неверный аргумент для foreach (): TypeError исключение вместо предупреждения;

  • Недопустимый тип смещения: TypeError исключение вместо предупреждения;

  • Недопустимый тип смещения в isset или empty: TypeError исключение вместо предупреждения;

  • Недопустимый тип смещения в unset: TypeError исключение вместо предупреждения;

  • Преобразование массива в строку: предупреждение вместо уведомления;

  • Идентификатор ресурса #% d, используемый в качестве смещения, приведение к целому числу (% d): предупреждение вместо уведомления;

  • Произошло смещение строки: предупреждение вместо уведомления;

  • Смещение неинициализированной строки: % d: предупреждение вместо уведомления;

  • Невозможно назначить пустую строку для смещения строки: Error исключение вместо предупреждения;

Оператор @ больше не глушит фатальные ошибки

Вполне возможно, что это изменение может выявить ошибки, которые были скрыты до PHP 8. Обязательно установите их на своих рабочих серверах display_errors=Off

Уровень сообщений об ошибках по умолчанию

Теперь E_ALL вместо всего, кроме E_NOTICE и E_DEPRECATED. Это означает, что может появиться много ошибок, которые ранее игнорировались, хотя, возможно, уже существовали до PHP 8.

Режим ошибки PDO по умолчанию

Текущий режим ошибок по умолчанию для PDO — беззвучный. Это означает, что при возникновении ошибки SQL, никакие ошибки или предупреждения не могут выдаваться, а также не генерируются исключения, если разработчик не реализует свою собственную явную обработку ошибок. Это обновление изменит ошибку по умолчанию, которая выдается на PHP 8. PDO::ERRMODE_EXCEPTION

Приоритет конкатенации

Это обновление появилось еще на PHP 7.4, но теперь оно официально вступает в силу.

Если бы вы написали что-то вроде этого:

echo "sum: " . $a + $b;

// PHP ранее интерпретировал бы это так:
echo ("sum: " . $a) + $b;

// PHP 8 сделает так, чтобы он интерпретировался так:
echo "sum: " . ($a + $b);

Более строгие проверки типов для арифметических и побитовых операторов

До PHP 8 можно было применять арифметические или побитовые операторы к массивам, ресурсам или объектам. Это больше не возможно и выдаст TypeError:

[] % [42];

$object + 4;

Изменилась сигнатуры методов классов Reflection

На PHP 8 реализовано изменение трех сигнатур методов классов Reflection.

Было:

ReflectionClass::newInstance($args);

ReflectionFunction::invoke($args);

ReflectionMethod::invoke($object, $args);

Стало:

ReflectionClass::newInstance(...$args);

ReflectionFunction::invoke(...$args);

ReflectionMethod::invoke($object, ...$args);

В руководстве к PHP 8 говорится, что если разработчики хотят использовать как обновленную версию языка, так и предыдущие, то допускается использование:

ReflectionClass::newInstance($arg = null, ...$args);

ReflectionFunction::invoke($arg = null, ...$args);

ReflectionMethod::invoke($object, $arg = null, ...$args);

Стабильная сортировка

До PHP 8 алгоритмы сортировки были нестабильны. Это означает, что порядок равных элементов не был гарантирован. PHP 8 меняет поведение всех функций сортировки на стабильную сортировку.

Неустранимая ошибка для несовместимых сигнатур методов

Ошибки наследования из-за несовместимых сигнатур методов в настоящее время либо генерируют фатальную ошибку, либо предупреждение — в зависимости от причины ошибки и иерархии наследования. Теперь всегда будет выдаваться фатальная ошибка.


Источники:

(1) https://ru.hexlet.io/blog/posts/php-8-kakie-vozmozhnosti-poyavyatsya-v-novoy-versii-yazyka-i-kak-ih-ispolzovat

Tags

Нет комментариев

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.