Что нового в PHP 8.2

Что нового в PHP 8.2

PHP 8.2 будет выпущен 24 ноября 2022 года. В этом посте мы рассмотрим все функции, улучшения производительности, изменения и устаревший функционал

null и false как самостоятельные типы rfc

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


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

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


function alwaysFalse(): false
{
return false;
}

Что интересно данный RFC не поддерживает true как отдельный тип, да и разве типы не должны представлять в целом категории вместо отдельных значений? Оказывается, в системах типов существует концепция, называемая единичным типом, то есть тип, допускающий только одно значение. Но действительно ли это полезно и поощряет ли это чистый дизайн приложения? Может быть, а может и нет.

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

class Post 
{
public function getAuthor(): ?string { /* … */ }
}

class NullPost extends Post
{
public function getAuthor(): null { /* … */ }
}

 

NullPost::getAuthor() всегда будет возвращать только null, а не null или string, что раньше было невозможно.


true как самостоятельный тип rfc

Как и предыдущий RFC с возможностью задавать null и false как самостоятельные типы, PHP 8.2 позволяет использовать true как отдельный тип и как часть Union Type. 


class Foo {

private true $processed;

public function process(string|int|true $value): true {
}
}

 Конечно же, есть некоторые ограничения, например нельзя использовать  true тип в сочетании с типом bool, потому что тип bool уже включает в себя true. Попытка объявить объединенные типы с bool приводит к фатальной ошибке:


function foo(true|bool $value) {}

Fatal error: Duplicate type true is redundant in ... on line ...

Или поскольку тип bool по сути такой же, как true|false, использование true|false в качестве типа объединения не допускается:


function foo(true|false $value) {}

Fatal error: Type contains both true and false, bool should be used instead in ... on line ...

  

Классы только для чтения rfc

Свойства только для чтения были введены в 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

Новое расширение Random rfc

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

Есть новый класс с именем Randomizer, который принимает механизм рандомизатора. Теперь вы можете изменить работу движка, в зависимости от ваших потребностей. Например, чтобы различать "боевую" среду и среду тестирования:


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

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

Типы дизъюнктивных нормальных форм rfc

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


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

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

В данном случае это тип дизъюнктивных нормальных форм  - (HasTitle&HasId) | null . 

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

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

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


trait Foo {
public const FLAG_1 = 1;
protected const FLAG_2 = 2;
private const FLAG_3 = 2;

public function doFoo(int $flags): void {
if ($flags & self::FLAG_1) {
echo 'Got flag 1';
}
if ($flags & self::FLAG_2) {
echo 'Got flag 2';
}
if ($flags & self::FLAG_3) {
echo 'Got flag 3';
}
}
}

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

trait T {
public const CONSTANT = 42;

public function doSomething(): void {
// Fatal Error
echo T::CONSTANT;

// OK
echo self::CONSTANT;
echo static::CONSTANT;
echo $this::CONSTANT;
}
}

class Base {
use Foo;
}

class Child extends Base {
public function doSomething(): void {
// OK
echo parent::CONSTANT;
}
}

// OK
echo Base::CONSTANT;
echo Child::CONSTANT;
echo (new Base)::CONSTANT;
$child = new Child;
echo $child::CONSTANT;

// Fatal Error
echo T::CONSTANT;

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

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

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


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

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

 


Устаревшие динамические свойства rfc

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


class Post
{
public string $header;
}

// …

$post->title = 'Заголовок';

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


class Post
{
private array $properties = [];

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

То же самое касается объектов stdClass, они будут поддерживать динамические свойства.

Если вам не нужны эти предупреждения при обновлении до PHP 8.2, вы можете сделать пару вещей.

Вы можете использовать атрибут для классов, которые должны разрешать эти свойства: #[AllowDynamicProperties]


#[AllowDynamicProperties]
class Post
{
public string $header;
}

// …

$post->title = 'Заголовок'; //Все пройдет нормально

Другой вариант — просто отключить предупреждения об устаревших функциях. Я бы не рекомендовал этого делать, так как у вас будут проблемы с PHP 9.0, но вот как вы все равно можете отключить предупреждения об устаревании в PHP: 


error_reporting(E_ALL ^ E_DEPRECATED);

Редактирование параметров в бэк трейсах rfc

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

PHP 8.2 позволяет вам помечать такие «конфиденциальные параметры» атрибутом, так что вам не нужно беспокоиться о том, что они будут перечислены в вашей трассировке стека, когда что-то пойдет не так. Вот пример из RFC:


function test(
$foo,
#[SensitiveParameter] $bar,
$baz
) {
throw new Exception('Error');
}

test('foo', 'bar', 'baz');


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

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

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


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

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


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

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

str_split возвращает пустые массивы для пустых строк rfc

До PHP 8.2 функция str_split неправильно возвращала массив, содержащий пустую строку (""): 


str_split('') === [""];

Начиная с PHP 8.2, вызов str_split для пустой строки возвращает пустой массив: 


str_split('') === [];

ksort(..., SORT_REGULAR) изменения порядка сортировки

До версии PHP 8.2 ksort буквенные ключи помещались перед числовыми:


$array = ["a" => '', "1" => '', "b" => '', "2" => ''];
ksort($array, SORT_REGULAR);
var_dump($array);

["a" => '', "b" => '', "1" => '', , "2" => ''];

 Теперь результат будет:


 ["1" => '', "2" => '', "a" => '', , "b" => ''];


Устаревание 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 утверждает, что эти функции имеют неточное имя, которое часто вызывает путаницу: эти функции преобразуют только ISO-8859-1 и UTF-8, в то время как имя функции предполагает более широкое использование. В RFC есть более подробное объяснение рассуждений.

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


Устаревшие частично поддерживаемые вызываемые объекты 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» может быть виден в одной области видимости, но не в другой. Если бы в будущем вызываемые объекты были ограничены public методами (в то время как private методы должны были бы использовать вызываемые объекты первого класса или Closure::fromCallable(), чтобы сделать их независимыми от области видимости), то вызываемый тип стал бы четко определенным и мог бы использоваться как тип свойства. Однако изменения в обработке видимости не предлагаются как часть этого RFC. 


Нечувствительный к локали strtolower() и strtoupper() rfc

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

Изменения подписи для нескольких методов 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";

Удаленные функции  

MySQLi больше не может быть скомпилирован с libmysqli

Исторически PHP поддерживал две библиотеки для подключения баз данных MySQL: mysqlnd и  libmysql. Эти библиотеки предоставляли базовые функции для подключения, запроса, поиска и обработки данных. Начиная с PHP 5.4, mysqlnd - это библиотека по умолчанию, но можно было компилировать mysqli и pdo_mysql расширения с libmysql с помощью флага конфигурации компиляции.

Начиная с PHP 8.2 и более поздних версий, компиляция mysqli расширения с помощью libmysql больше не поддерживается.

Сергей Мухин

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

Есть вопросы?

Я почти всегда в режиме онлайн

Связаться со мной