Обзор релиза PHP v.8.3

Обзор релиза PHP v.8.3

PHP 8.3 вышел 23 ноября 2023 г. В нем есть улучшения классов только для чтения, новая функция json_validate(), дополнения к недавно добавленному классу Randomizer, обнаружение переполнения стека и многое другое.

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

Изменения только для чтения

В этом RFC предлагалось два изменения, но было принято только одно: возможность повторной инициализации свойств только для чтения во время клонирования. Этот RFC рассматривает только очень специфический (но важный) крайний случай: перезапись значений свойств внутри __clone(), чтобы обеспечить глубокое клонирование свойств, доступных только для чтения.

readonly class Post
{
    public function __construct(
        public DateTime $createdAt,
    ) {}

    public function __clone()
    {
        $this->createdAt = new DateTime(); 
        // Это допустимо,
        // даже если `createdAt` — свойство только для чтения.
    }
}

Подробную публикацию об этом RFC и примечания можно прочитать здесь .

Типизированные константы класса

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

class Foo
{
    const string BAR = 'baz'; 
}

Атрибут #[Override]

Новый атрибут используется, чтобы показать намерение разработчика. По сути, он говорит: «Я знаю, что этот метод переопределяет родительский метод. Если это когда-нибудь изменится, дайте мне знать».#[Override]

Вот пример:

abstract class Parent
{
    public function methodWithDefaultImplementation(): int
    {
        return 1;
    }
}

final class Child extends Parent
{
    #[Override]
    public function methodWithDefaultImplementation(): int
    {
        return 2; // Переопределяющий метод
    }
}

Теперь давайте представим, что в какой-то момент родительский метод меняет имя своего метода:

abstract class Parent
{
    public function methodWithNewImplementation(): int
    {
        return 1;
    }
}

Благодаря этому атрибуту PHP сможет обнаружить, что метод больше ничего не переопределяет, и выдаст ошибку.

#[Override]Child::methodWithDefaultImplementation()

Подробнее об #[Override]-атрибуте можно прочитать здесь .

Отрицательные индексы в массивах

Ранее, если у вас был пустой массив, и вы добавляли в него сначала один элемент с отрицательным индексом, а затем еще один элемент, второй всегда начинался с индекса 0:

$array = [];
$array[-5] = 'a';
$array[] = 'b';
var_export($array);
//array (
//  -5 => 'a',
//  0 => 'b',
//)

Начиная с PHP 8.3, по индексу будет добавлен следующий элемент -4:

//array (
//  -5 => 'a',
//  -4 => 'b',
//)

Анонимные классы только для чтения

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

$class = new readonly class {
    public function __construct(
        public string $foo = 'bar',
    ) {}
};

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

Раньше единственным способом проверить, является ли строка правильным JSON, было ее декодирование и обнаружение каких-либо ошибок. Новая json_validate()функция полезна, если вам нужно только знать, является ли значение валидным JSON, поскольку она использует меньше памяти по сравнению с декодированием строки.

json_validate(string $json, int $depth = 512, int $flags = 0): bool

Randomizer дополнения

В PHP 8.2 добавлен новый класс Randomizer . В этом обновлении есть несколько небольших дополнений:

Randomizer::getBytesFromString(string $string, int $length): string

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

Randomizer::getFloat(
    float $min,
    float $max,
    IntervalBoundary $boundary = IntervalBoundary::Closed|Open
): float

getFloat() возвращает число с плавающей запятой между $min и $max. Вы можете определить, следует ли включать $min и $max значения, благодаря перечислению IntervalBoundary. Closed означает, что значение включено, а Open означает исключено.

Randomizer::nextFloat(): float {}

Наконец, nextFloat(): оно даст вам случайное число с плавающей запятой между 0 и 1, где 1 исключено.

getFloat(0, 1, IntervalBoundary::Closed|Open)

Извлечение констант динамического класса

PHP 8.3 позволяет получать константы с более лаконичным синтаксисом:

class Foo 
{
    const BAR = 'bar';
}
$name = 'BAR';

// Вместо этого:
constant(Foo::class . '::' . $name);

// Вы можете делать так:
Foo::{$name};

Больше подходящих исключений по дате/времени

Во многих случаях PHP просто выдает объект Exception или Error - или предупреждение или ошибку, если что-то пошло не так при работе с датами и временем. Этот RFC рассматривает все эти крайние случаи и добавляет для них соответствующие исключения.

Теперь у нас есть исключения, такие как DateMalformedIntervalStringException, DateInvalidOperationException и DateRangeError.

В общем, эти дополнения не нарушат какой-либо код, поскольку эти исключения и ошибки классифицируют общие Exception и Error классы. Однако, в этом RFC есть три небольших критических изменения:

  • Теперь Epoch doesn't fit in a PHP integer возвращает новый DateRangeError вместо общего типа ValueError, который не является подклассом. Это проблема только для 32-битных платформ.

  • Предупреждение Only non-special relative time specifications are supported for subtraction при использовании DateTime::sub() и date_sub() становится новым DateInvalidOperationException

  • Предупреждения Unknown or bad format (%s) at position %d (%c): %s и String '%s' contains non-relative elements, создаваемые при подаче неправильных/сломанных DateInterval строк, теперь будут выдавать новые DateMalformedIntervalStringException при использовании с объектно-ориентированным интерфейсом вместо отображения предупреждения и возврата false.

Улучшенная обработка ошибок unserialize()

unserialize()теперь всегда будет выдавать E_WARNING при возникновении проблем вместо E_NOTICE.

В этом RFC также предлагалось добавить больше исключений при запуске unserialize(), но эта часть не была принята.

Изменения в range() функции

Из журнала изменений:

  • TypeError теперь выдается при передаче объектов, ресурсов или массивов в качестве граничных входных данных.

  • Более подробное описание ValueError выдается при передаче 0 для аргумента $step

  • ValueError теперь выбрасывается при использовании отрицательного значения $step для увеличения диапазона. Так же оно выбрасывается, если $step это число с плавающей запятой, которое можно интерпретировать как целое число.

  • ValueError теперь выдается, если какой-либо аргумент равен infinity или NAN

  • Теперь выдается E_WARNING, если $start или $end является пустой строкой. Значение приводится к 0.

  • E_WARNING теперь генерируется, если $start или $end имеет более одного байта, только если это нечисловая строка.

  • E_WARNING теперь генерируется, если $start или $end приводится к целому числу, поскольку другое граничное входящее значение является числом. (например range(5, 'z');)

  • Теперь выдается E_WARNING, если $step является числом с плавающей запятой при попытке сгенерировать диапазон символов, за исключением случаев, когда оба граничных значения являются числовыми строками (например, range('5', '9', 0.5); не выдает предупреждение).

  • range() теперь создает список символов, если один из граничных входных данных является строковой цифрой, вместо приведения другого входного значения к int (например range('5', 'z');)

Трейты и статические свойства

Из журнала изменений:

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

Обнаружение переполнения стека

В PHP 8.3 добавлены две новые директивы ini: zend.max_allowed_stack_size и zend.reserved_stack_size. Приложения, которые близки к переполнению стека вызовов, теперь могут выдавать ошибку Error при использовании разницы между zend.max_allowed_stack_size и zend.reserved_stack_size.

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

Значение по умолчанию zend.max_allowed_stack_size — 0, что означает, что PHP автоматически определит значение. Вы также можете указать -1, чтобы определить, что нет ограничения или определенного количества байтов. Директива zend.reserved_stack_size используется для определения «буферной зоны», чтобы PHP мог выдавать ошибку, а не занимать память. Значением здесь должно быть количество байтов, но PHP определит для вас оптимальное значение по умолчанию, поэтому вам не обязательно устанавливать его, если только вы не столкнетесь с исключительными случаями для конкретных программ.

И последнее замечание: для файберов fiber.stack_size директива используется как максимально допустимый размер стека.

zend.max_allowed_stack_size=128K

Новая функция mb_str_pad

Из RFC:

В PHP различные строковые функции доступны в двух вариантах: один для байтовых строк, другой для многобайтовых строк. Однако, заметным отсутствием среди функций многобайтовых строк является mbstring эквивалент str_pad(). В str_pad() функции отсутствует поддержка многобайтовых символов, что вызывает проблемы при работе с языками, использующими многобайтовые кодировки, такие как UTF-8. В этом RFC предлагается добавить в PHP такую ​​функцию, которую мы назовем mb_str_pad().

Функция выглядит следующим образом:

function mb_str_pad(
    string $string, 
    int $length, 
    string $pad_string = " ", 
    int $pad_type = STR_PAD_RIGHT, 
    ?string $encoding = null,
): string {}

Замыкания магических методов и именованные аргументы

Допустим, у вас есть класс, поддерживающий магические методы:

class Test {
    public function __call($name, $args) 
    {
        var_dump($name, $args);
    }
    
    public static function __callStatic($name, $args) {
        var_dump($name, $args);
    }
}

PHP 8.3 позволяет создавать замыкания из этих методов, а затем передавать им именованные аргументы. Раньше это было невозможно.

$test = new Test();
$closure = $test->magic(...);
$closure(a: 'hello', b: 'world');

Инвариантная постоянная видимость

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

interface I {
    public const FOO = 'foo';
}

class C implements I {
    private const FOO = 'foo';
}

Небольшой список устаревшего RFC

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

  • Запрещается передавать отрицательные значения $widths в mb_strimwidth()

  • Устарела и удалена константа NumberFormatter::TYPE_CURRENCY

  • Устарела и удалена неработающая реализация Mt19937 до PHP 7.1 (MT_RAND_PHP)

  • Устарел и удалён вызов ldap_connect() с двумя параметрами $host и $port

  • Не рекомендуется использовать проверку утверждений (assert()) для (вычисляемых) строк

Небольшие, но важные изменения

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

  • При использовании FFI, C-функции, которые возвращали void, теперь, возвращают null вместо FFI\CData:void

  • posix_getrlimit() теперь принимает необязательный $res-параметр, позволяющий получить ограничение на один ресурс.

  • gc_status() имеет четыре новых поля: running, protected, full и buffer_size.

  • class_alias() теперь поддерживает создание псевдонима внутреннего класса.

  • mysqli_poll() теперь вызывает ошибку ValueError, когда передаются аргументы чтения или ошибки.

  • array_pad() теперь ограничено только максимальным количеством элементов, которые может иметь массив. Раньше за раз можно было добавить не более 1048576 элементов.

  • Новые функции posix: posix_sysconf(), posix_pathconf(), posix_fpathconf() и posix_eaccess()

  • Многократное выполнение proc_get_status() теперь всегда будет возвращать правильное значение в системах posix

  • opcache.consistency_checks ini-директива была удалена

  • Улучшенный array_sum() и array_product()

Оригинал статьи - https://stitcher.io/blog/new-in-php-83.