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

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

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

Обратите внимание, что Вы можете расширять классы, доступные только для чтения, только в том случае, если дочерний класс также доступен только для чтения. (Подробнее про принципы наследования в PHP Вы можете прочитать тут.)

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

Отказ от динамических свойств

Можно сказать, что это перемена к лучшему, но для кого-то это будет немного болезненно. Динамические свойства устарели в PHP 8.2 и будут вызывать ошибки исключения в 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';

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

Новое расширение random

PHP 8.2 добавляет новый генератор случайных чисел, который устраняет множество проблем с предыдущим: он более производителен, более безопасен, его проще поддерживать и он не зависит от глобального состояния; устранен ряд трудно обнаруживаемых ошибок при использовании random функций 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 как самостоятельные типы

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.

Типы в виде дизъюнктивной нормальной формы (ДНФ)

Типы в ДНФ позволяют нам комбинировать union- и intersection-типы, следуя строгому правилу: при объединении union- и intersection-типов, последние должны быть сгруппированы с помощью скобок. На практике это выглядит так:

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

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

В этом случае (HasTitle&HasId)|null - это тип ДНФ.

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

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

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

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

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

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

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

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*/

Извлечение свойств enum в выражениях const

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

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

enum A: string 
{
    case B = 'B';
    
    const C = [self::B->value => self::B];
}

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

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

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

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

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

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

Устаревание utf8_encode() и utf8_decode()

В PHP 8.2 использование и utf8_encode(), и utf8_decode() вызовет следующие уведомления об устаревании:

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

Смысл данного изменения в том, что эти функции имеют неточное имя, которое часто вызывает путаницу: эти функции преобразуются только между ISO-8859-1 и UTF-8, в то время как имя функции предполагает более широкое использование.

Здесь Вы найдете более подробное объяснение ситуации.

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

И 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

Из руководства по UPDATING

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

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

Устаревание вставки в строку с помощью ${}

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

"Hello ${world}";
//Deprecated: Using ${} in strings is deprecated

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

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

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

Устаревание частично поддерживаемых вызываемых объектов

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

Все эти вызываемые объекты зависят от контекста. Метод, на который ссылается «self::method», зависит от того, из какого класса выполняется вызов или проверка возможности вызова. На практике это обычно справедливо и для последних двух случаев, когда используется в форме [new Foo, "parent::method"].
Уменьшение зависимости вызываемых объектов от контекста является вторичной целью этого изменения. После этого изменения единственная оставшаяся зависимость — это видимость метода: «Foo::bar» может быть виден в одной области видимости, но не в другой. Если бы в будущем вызываемые объекты были ограничены общедоступными методами (в то время как частные методы должны были бы использовать вызываемые объекты первого класса или Closure::fromCallable(), чтобы сделать их независимыми от области действия), то вызываемый тип стал бы четко определенным и мог бы использоваться как тип свойства. Однако изменения в обработке видимости не предлагаются как часть этого изменения.

Поделиться:

Facebook Twitter Vkontakte