PHP 8.2: Что нового. Изменения и новый функционал.

Источник: «What's new in PHP 8.2»
Релиз PHP 8.2 запланирован на 8 декабря 2022 года. В этой статье мы рассмотрим новые возможности, улучшения производительности, изменения и что объявлено устаревшим.

Readonly-классы RFC

Readonly-свойства были введены в PHP 8.1. Этот RFC основан на них и добавляет синтаксический сахар, чтобы сделать все свойства класса доступными только для чтения. Вместо того чтобы писать так:

class Post
{
public function __construct(
public readonly string $title,
public readonly Author $author,
public readonly string $body,
public readonly DateTime $publishedAt,
) {}
}

Теперь вы можете писать так:

readonly class Post
{
public function __construct(
public string $title,
public Author $author,
public string $body,
public DateTime $publishedAt,
) {}
}

Функционально, сделать класс доступным только для чтения — то же самое, что сделать каждое свойство доступным только для чтения. Но это не даст добавить динамические свойства в класс:

$post = new Post(/* … */);

$post->unknown = 'wrong';
Uncaught Error: Cannot create dynamic property Post::$unknown

Обратите внимание, что вы можете расширять readonly-класс, только если дочерний класс также будет readonly-класс.

PHP сильно изменился и readonly-классы — долгожданное дополнение. Если хотите, можете посмотреть моё видео об эволюции PHP:

Динамические свойства объявлены устаревшими RFC

Я бы сказал, что это изменение к лучшему, но это будет немного болезненным. Динамические свойства объявлены устаревшими в PHP 8.2 и вызовут ErrorException в PHP 9.0:

class Post
{
public string $title;
}

// …

$post->name = 'Name';

Имейте в виду, что классы реализующие __get и __set, по-прежнему будут работать, как и предполагалось:

class Post
{
private array $properties = [];

public function __set(string $name, mixed $value): void
{
$this->properties[$name] = $value;
}
}

// …

$post->name = 'Name';

Если хотите узнать больше о deprecated, чем они полезны и как с ними разобраться, можете прочитать мою статью о том, как разобраться с устаревшей функциональностью или посмотрите мой видеоблог:

Новое расширение генерации случайных чисел RFC

В PHP 8.2 добавлен новый генератор случайных чисел устраняющий множество проблем с предыдущим: он более производительный, более безопасный, его проще поддерживать и не зависит от глобального состояния. Устранён ряд трудно обнаруживаемых ошибок при использовании функции случайных чисел PHP.

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

$rng = $is_production
? new Random\Engine\Secure()
: new Random\Engine\Mt19937(1234);

$randomizer = new Random\Randomizer($rng);
$randomizer->shuffleString('foobar');

null, true и false как автономные типы RFC

PHP 8.2 вводит три новых типа — или что-то похожее. Мы не будем углубляться в кроличью нору безопасности типов в этой статье, но технически null, true и false сами по себе могут считаться допустимыми типами. Типичными примерами являются встроенные функции PHP, где false используется в качестве возвращаемого типа при возникновении ошибки. Например, в file_get_contents:

file_get_contents(/* … */): string|false

До PHP 8.2 вы уже могли использовать false вместе с другими типами, как объединение типов. Но теперь он может использоваться как отдельный тип:

function alwaysFalse(): false
{
return false;
}

То же самое относится к true и null.

Дизъюнктивная нормальная форма типов RFC

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

function generateSlug((HasTitle&HasId)|null $post)
{
if ($post === null) {
return '';
}

return
strtolower($post->getTitle())
. $post->getId();
}

В данном случае (HasTitle&HasId)|null — это DFN тип.

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

Константы в трейтах RFC

Теперь вы можете использовать константы в трейтах:

trait Foo
{
public const CONSTANT = 1;

public function bar(): int
{
return self::CONSTANT;
}
}

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

trait Foo
{
public const CONSTANT = 1;

public function bar(): int
{
return Foo::CONSTANT; // Ошибка
}
}

Foo::CONSTANT; // Ошибка

Однако вы можете получить доступ к константе через класс, который использует трейт, учитывая, что он публичный:

class MyClass
{
use Foo;
}

MyClass::CONSTANT; // 1

Редактирование параметров в трассировках RFC

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

PHP 8.2 позволяет помечать такие конфиденциальные параметры атрибутом #[\SensitiveParameter] и вам не нужно беспокоиться и том, что они будут отображены в трассировке стека, когда что-то пойдёт не так. Вот пример из RFC:

function login(
string $user,
#[\SensitiveParameter] string $password
) {
// …

throw new Exception('Error');
}

login('root', 'root');
Fatal error: Uncaught Exception: Error in login.php:8
Stack trace:
#0 login.php(11): login('root', Object(SensitiveParameterValue))
#1 {main}
thrown in login.php on line 8

Получение свойств перечислений в константных выражениях RFC

Из RFC:

В этом RFC предлагается разрешить использование ->/?-> для получения свойств перечислений в константных выражениях. Основная причина этого изменения — позволить извлекать имя и значение свойства в местах, где объекты перечисления не разрешены, например, в ключах массива.

Это означает, что следующий код теперь валиден:

enum A: string
{
case B = 'B';

const C = [self::B->value => self::B];
}

Изменение возвращаемого типа DateTime::createFromImmutable() и DateTimeImmutable::createFromMutable()

Ранее эти методы выглядели так:

DateTime::createFromImmutable(): DateTime
DateTimeImmutable::createFromMutable(): DateTimeImmutable

В PHP 8.2 сигнатуры методов изменены следующим образом:

DateTime::createFromImmutable(): static
DateTimeImmutable::createFromMutable(): static

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

utf8_encode() и utf8_decode() объявлены устаревшими RFC

В PHP 8.2 использование utf8_encode() или utf8_decode() вызовет следующее предупреждение об использовании устаревшей функции:

Deprecated: Function utf8_encode() is deprecated
Deprecated: Function utf8_decode() is deprecated

RFC утверждает, что эти функции имеют неточное имя, которые часто вызывает путаницу. Эти функции преобразуют только между SO-8859-1 и UTF-8, в то время, как имя функции предполагает более широкое использование. В RFC есть более подробное объяснение рассуждений.

Альтернатива? RFC предлагает использовать вместо них mb_convert_encoding()

strtolower() и strtoupper() больше не зависят от локализации RFC

strtolower() и strtoupper() больше не зависят от локализации. Вы можете использовать mb_strtolower() и mb_strtoupper, если вам нужно локализованное преобразование регистра.

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

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

SplFileInfo::_bad_state_ex()
SplFileObject::getCsvControl()
SplFileObject::fflush()
SplFileObject::ftell()
SplFileObject::fgetc()
SplFileObject::fpassthru()
SplFileObject::hasChildren()
SplFileObject::getChildren()

Новый модификатор n в PCRE

Теперь, вы можете использовать новый модификатор n (NO_AUTO_CAPTURE) в pcre* функциях.

Экранирование имени пользователя и пароля в ODBC

Из руководства по обновлению:

Расширение ODBC теперь экранирует имя пользователя и пароль в обоих случаях, когда они передаются как строка подключения, так и имя пользователя/пароль.

Тоже применимо к PDO_ODBC

Интерполяция строк ${} объявлена устаревшей RFC

В PHP есть несколько способов встраивания переменных в строки. Этот RFC объявляет устаревшими два способа сделать это, поскольку они редко используются и приводят к путанице:

"Hello ${world}";
Deprecated: Using ${} in strings is deprecated
"Hello ${(world)}";
Deprecated: Using ${} (variable variables) in strings is deprecated

Для ясности: два популярных способа интерполяции строк всё ещё работают:

"Hello {$world}";
"Hello $world";

Частично поддерживаемые вызываемые объекты объявлены устаревшими RFC

Ещё одно изменение, хотя и с меньшим влиянием, заключается в том, что частично поддерживаемые объекты теперь также объявлены устаревшими. Частично поддерживаемые объекты — вызываемые объекты, которые можно вызвать с помощью call_user_func($callable), но не путём прямого вызова $callable(). Между прочим, список этих вызываемых объектов довольно короткий:

"self::method"
"parent::method"
"static::method"
["self", "method"]
["parent", "method"]
["static", "method"]
["Foo", "Bar::method"]
[new Foo, "Bar::method"]

Причина всего этого? Это шаг в правильном направлении к возможности использовать callable для типизированных свойств. Никита хорошо объясняет это в RFC:

…все эти вызываемые объекты зависят от контекста. Метод, который ссылается на self:method, зависит от того, из какого класса выполняется вызов или проверка возможности вызова. На практике, это обычно справедливо и для последних двух случаев, когда используется в форме [new Foo, "parent::method"]

Уменьшение зависимости вызываемых объектов от контекста является вторичной целью этого RFC. После этого RFC единственная оставшаяся зависимость от области видимости — "Foo::bar" может быть видим в одной области видимости, но не в другой. Если бы вызываемые объекты были ограничены публичными методами в будущем (пока приватные методы должны были бы использовать вызываемые объекты первого класса или Closure::fromCallable(), что бы стать независимыми от области видимости), то вызываемый тип стал бы чётко определённым и мог бы использоваться как тип свойства. Однако изменения в обработке видимости не предлагаются как часть этого RFC.


Это всё на данный момент. Я буду обновлять этот список в течение года.

Дополнительные материалы

Предыдущая Статья

PHP 8.2: Readonly-классы / классы только для чтения

Следующая Статья

PHP: Когда использовать трейт