Verification: a143cc29221c9be0

Php 8 версия что нового

Php 8 версия что нового

Содержание

JIT

Появление JIT как раз во время. Компилятор обещает значительные улучшения производительности, хотя и не всегда это будет в контексте веба. На данный момент не было сделано никаких точных ориентиров, но они обязательно появятся. Что такое JIT  и для чего он будет нужен, можно почитать здесь

Union Types 2.0 (Объединенные типы)

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

Type или null, используя специальный синтаксис "?Type"

array или Traversable, используя специальный тип iterable.

Однако произвольные объединенные типы в настоящее время не поддерживаются языком. Вместо этого необходимо использовать аннотации phpdoc, например, в следующем примере:

class Number {
/**
* @var int|float $number
*/
private $number;

/**
* @param int|float $number
*/
public function setNumber($number) {
$this->number = $number;
}

/**
* @return int|float
*/
public function getNumber() {
return $this->number;
}
}

Объединенные типы указываются с использованием синтаксиса T1|T2|… и могут использоваться во всех позициях, где типы в настоящее время принимаются:

class Number {
private int|float $number;

public function setNumber(int|float $number): void {
$this->number = $number;
}

public function getNumber(): int|float {
return $this->number;
}
}

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

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

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

Оператор nullsafe rfc

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

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

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

С добавлением оператора nullsafe мы теперь можем иметь поведение методов, подобное слиянию null!

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

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

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

// Использование позиции аргументов:
array_fill(0, 100, 50);

// Использование наименований аргументов:
array_fill(start_index: 0, num: 100, value: 50);

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

htmlspecialchars($string, double_encode: false);
//то же самое что и
htmlspecialchars($string, ENT_COMPAT | ENT_HTML401, 'UTF-8', false);

Аттрибуты

Атрибуты, обычно известные в других языках, как аннотации или декораторы, предлагают способ добавлять метаданные в классы, без распарсивания докблоков. Широкое использование парсинга комментариев к документам пользователя показывает, что это очень востребованная функция сообщества. Атрибуты представляют собой специально отформатированный текст, заключенный в «>» путем повторного использования существующих токенов T_SL и T_SR.

Атрибуты могут применяться ко многим вещам в языке:

  • функции (включая замыкания и короткие замыкания)
  • классы (включая анонимные классы), интерфейсы, трейты
  • константы класса
  • свойства класса
  • методы класса
  • параметры функции/метода

Вот пока примерный взгляд из RFC:

use App\Attributes\ExampleAttribute;

>
class Foo
{
>
public const FOO = 'foo';

>
public $x;

>
public function foo(> $bar) { }
}

>
class ExampleAttribute
{
public $value;

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

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

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

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

Вместо этого

switch (1) {
case 0:
$result = 'Foo';
break;
case 1:
$result = 'Bar';
break;
case 2:
$result = 'Baz';
break;
}

echo $result;

Можно писать так:

echo match (1) {
0 => 'Foo',
1 => 'Bar',
2 => 'Baz',
};

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

$result = match($input) {
0 => "Какой-то Вывод",
'1', '2', '3' => "Вывод условий 1,2,3",
};

Объявление свойств в конструкторе RFC

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

class Point {
public float $x;
public float $y;
public float $z;

public function __construct(
float $x = 0.0,
float $y = 0.0,
float $z = 0.0,
) {
$this->x = $x;
$this->y = $y;
$this->z = $z;
}
}

Свойства повторяются 1) в объявлении свойства, 2) в параметрах конструктора и 3) два раза в назначении свойства. Кроме того, тип свойств так же повторяется дважды.

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

class Point { 
публичная функция __construct (
публичный список $ x = 0.0 ,
публичный список $ y = 0.0 ,
публичный список $ z = 0.0 ,
) { }
}

Или еще пример, вместо этого:

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,
) {}
}

Этот сокращенный код строго эквивалентен предыдущему примеру, но более лаконичен. Выбор синтаксиса взят из родственного языка Hack. Более подробно можно почитать в посте объявление свойств в конструкторе.

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

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

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

Новый тип mixed v2

С добавлением скалярных типов в PHP 7, обнуляемых в 7.1, объектов в 7.2 и, наконец, типов объединения в 8.0, люди, пишущие код PHP, могут явно объявлять информацию о типах для большинства параметров функции, возвращаемых функций, а также свойств класса. Однако в PHP не всегда поддерживаются типы, и, скорее всего, он всегда будет позволять опускать информацию о типах. Но это приводит к проблеме того, что ее значение неоднозначно, когда отсутствует информация о типе:

  • Функция не возвращает ничего или null
  • Мы ожидаем один из нескольких типов
  • Мы ожидаем, тип, который не может быть подсказан в PHP

Из-за причин, приведенных выше, хорошо, что тип  mixed был наконец добавлен, Сам по себе mixed означает один из этих типов:

  • array
  • bool
  • callable
  • int
  • float
  • null
  • object
  • resource
  • string

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

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

Throw выражения

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

// This was previously not possible since arrow functions only accept a single expression while throw was a statement.
$callable = fn() => throw new Exception();

// $value is non-nullable.
$value = $nullableValue ?? throw new InvalidArgumentException();

// $value is truthy.
$value = $falsableValue ?: throw new InvalidArgumentException();

// $value is only set if the array is not empty.
$value = !empty($array)
? reset($array)
: throw new InvalidArgumentException();

Есть и другие места, где он может быть использован, которые являются более спорными. Эти случаи разрешены в данном RFC

$condition && throw new Exception();
$condition || throw new Exception();
$condition and throw new Exception();
$condition or throw new Exception();

Наследование приватных методов

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

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

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

Weak maps (Слабые карты)

Построенный на RFC слабых ссылок, который был добавлен в PHP 7.4, В PHP 8 WeakMap  добавляет ​​реализацию, в которой хранятся ссылки на объекты, которые не препятствуют сборке мусора этими объектами.

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

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

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

class Foo 
{
private WeakMap $cache;

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

Использование ::class на объектах

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

//раньше
$bar = new Foo();
echo Foo::class;
//или
echo get_class($bar);
//'Foo'

//теперь можно будет так
$foo = new Foo();
echo $foo::class;
//'Foo'

Неподхваченные уловы

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

try {
//Что-то идет не так
} catch (MySpecialException $exception) {
Log::error("Что-то пошло не так");
}

Теперь вы можете сделать это:

try {
// Что-то идет не так
} catch (MySpecialException) {
Log::error("Что-то пошло не так");
}

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

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

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

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

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

Вы уже можете создать DateTime объект из DateTimeImmutable объекта, используя DateTime::createFromImmutable($immutableDateTime), но наоборот было сложно. Добавление DateTime::createFromInterface() и DatetimeImmutable::createFromInterface() теперь позволяет получить обобщенный способ конвертировать DateTime и DateTimeImmutable объекты друг в друга.

DateTime::createFromInterface(DateTimeInterface $other);

DateTimeImmutable::createFromInterface(DateTimeInterface $other);

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

Появится новый интерфейс Stringable, который автоматически добавляется в классы, которые реализуют магический метод __toString(), и нет необходимости реализовывать его вручную. 

У данного RFC две цели:

  • разрешить использовать, string|Stringable чтобы выразить string|object-with-__toString()
  • предоставить прямой путь обновления с PHP 7 до 8
class Foo
{
public function __toString(): string
{
return 'foo';
}
}

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

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

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

str_contains проверяет, содержится ли строка в другой строке, и возвращает логическое значение (true/false), независимо от того, была ли найдена строка. Некоторые могут сказать, что это давно пора, и нам, наконец, больше не нужно полагаться на 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

Как правило, эта функциональность достигается за счет использования существующих строковых функций, таких как substr, strpos/strrpos, strncmp или substr_compare(часто в сочетании с strlen), которые имеют различные недостатки. Что интересно,функциональность str_starts_with и str_ends_with настолько необходима, что ее поддерживают многие основные PHP-фреймворки, включая Symfony, Laravel, Yii, FuelPHP и Phalcon.

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

Новая функция  fdiv делает что-то подобное типа функций fmod и intdiv, что позволяет произвести деление на 0. Но вместо ошибок вы получите INF, -INF или NAN, в зависимости от случая.

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

get_debug_type() возвращает тип переменной. Что-то похоже выдает gettype(), но get_debug_type() возвращает более полезный вывод для массивов, строк, анонимных классов и объектов. Например, вызов gettype() класса \Foo\Bar вернется object. Использование get_debug_type() вернет имя класса.

Полный список различий между get_debug_type()и gettype() можно найти в RFC.

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

Ресурсы - это специальные переменные в PHP, ссылающиеся на внешние ресурсы. Например, соединение MySQL, или дескриптор файла.

Каждому из этих ресурсов присваивается идентификатор, хотя ранее единственным способом узнать, что это идентификатор, было преобразование ресурса в int:

$resourceId = (int) $resource;

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

$resourceId = get_resource_id($resource);

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

Трейты - это «механизм повторного использования кода в языках с единичным наследованием, таких как PHP». Обычно они используются для объявления методов, которые можно использовать в нескольких классах. Трейты так же могут содержать абстрактные методы, которые должны быть реализованы классами, использующими их. Однако есть предостережение: до 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;
}
}

Как бы то ни было, по словам автора RFC Никиты Попова , проверка подписи в настоящее время применяется только точечно:

  • Это не применяется в наиболее распространенном случае, когда реализация метода обеспечивается в используемом классом
  • Это принудительно, если реализация исходит из родительского класса
  • Это принудительно, если реализация исходит от дочернего класса

Объектно-ориентированная альтернатива token_get_all()

Функция token_get_all() возвращает массив значений. Этот RFC добавляет класс PhpToken с методом PhpToken::getAll(). Эта реализация работает с объектами вместо простых значений, его легче читать, и потребляет меньше памяти.


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

Унифицированный синтаксис переменной RFC устранил ряд несоответствий в синтаксисе переменной PHP. Этот RFC намеревается решить небольшую горстку пропущенных дел.

Тип аннотации для внутренних функций

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

Расширение ext-json всегда доступен

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


"Сломанные изменения"

PHP 8 - серьезное обновление и, следовательно, будут серьезные изменения. Лучшее, что можно сделать, это взглянуть на полный список критических изменений в документе ОБНОВЛЕНИЕ . Однако многие из этих критических изменений устарели в предыдущих версиях 7. *, поэтому, если вы были в курсе последних лет, не так уж сложно перейти на PHP 8.

Согласованные постоянные ошибки типов

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

Переклассифицированы предупреждения

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

  • Undefined variable (Неопределенная переменная): Error исключение вместо уведомления
  • Undefined array index (Неопределенный индекс массива): предупреждение вместо уведомления
  • Division by zero (Деление на ноль): DivisionByZeroError исключение вместо предупреждения
  • Attempt to increment/decrement property '%s' of non-object (Попытка увеличить/уменьшить свойство "%s" необъекта): Error исключение вместо предупреждения
  • Attempt to modify property '%s' of non-object (Попытка изменить свойство "%s" необъекта): Error исключение вместо предупреждения
  • Attempt to assign property '%s' of non-object (Попытка назначить свойство "%s" необъекта): Error исключение вместо предупреждения
  • Creating default object from empty value (Создание объекта по умолчанию из пустого значения): Error исключение вместо предупреждения
  • Trying to get property '%s' of non-object (Попытка получить свойство "%s" необъекта): предупреждение вместо уведомления
  • Undefined property (Неопределенное свойство): %s::$%s: предупреждение вместо уведомления
  • Cannot add element to the array as the next element is already occupied (Невозможно добавить элемент в массив, так как следующий элемент уже занят): Error исключение вместо предупреждения
  • Cannot unset offset in a non-array variable (Невозможно сбросить смещение в переменной, не являющейся массивом): Error исключение вместо предупреждения
  • Cannot use a scalar value as an array (Нельзя использовать скалярное значение в качестве массива): Error исключение вместо предупреждения
  • Only arrays and Traversables can be unpacked (Только массивы и Traversables могут быть распакованы): TypeError исключение вместо предупреждения
  • Invalid argument supplied for foreach() (Указан неверный аргумент для foreach ()): TypeError исключение вместо предупреждения
  • Illegal offset type (Недопустимый тип смещения): TypeError исключение вместо предупреждения
  • Illegal offset type in isset or empty (Недопустимый тип смещения в isset или empty): TypeError исключение вместо предупреждения
  • Illegal offset type in unset (Недопустимый тип смещения в unset): TypeError исключение вместо предупреждения
  • Array to string conversion (Преобразование массива в строку): предупреждение вместо уведомления
  • Resource ID#%d used as offset, casting to integer (%d) (Идентификатор ресурса #%d, используемый в качестве смещения, приведение к целому числу (%d)): предупреждение вместо уведомления
  • String offset cast occurred (Произошло приведение смещения строки): предупреждение вместо уведомления
  • Uninitialized string offset: %d (Смещение неинициализированной строки: %d): предупреждение вместо уведомления
  • Cannot assign an empty string to a string offset (Невозможно назначить пустую строку для смещения строки): Error исключение вместо предупреждения

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

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

Стандартный режим ошибки PDO

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

Этот RFC изменяет ошибку по умолчанию, которая изменится на PDO::ERRMODE_EXCEPTION.

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

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

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

Хотя это изменение уже устарело в PHP 7.4, теперь это изменение вступает в силу. Если бы вы написали что-то вроде этого:

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

PHP ранее интерпретировал бы это так:

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

PHP 8 сделает так, чтобы он интерпретировался так:

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

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

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

[] % [42];
$object + 4;

Имена в пространствах имен являются одним токеном rfc

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

// In the library:
namespace iter\fn;

function operator($operator, $operand = null) { ... }

// In the using code:
use iter\fn;

iter\map(fn\operator('*', 2), $nums);

Более разумные числовые строки rfc

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

Более разумное сравнение чисел и строк rfc

Этот RFC исправляет очень странный случай в PHP, когда 0 == "foo" возвращает результат как true. Есть и другие крайние случаи, подобные этому, и этот RFC исправляет их.

Изменения подписи метода отражения

Три сигнатуры методов классов отражения были изменены:

ReflectionClass::newInstance($args);
ReflectionFunction::invoke($args);
ReflectionMethod::invoke($object, $args);

Теперь стали:

ReflectionClass::newInstance(...$args);
ReflectionFunction::invoke(...$args);
ReflectionMethod::invoke($object, ...$args);

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

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

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

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

Стабильные сортировки полезны, прежде всего, при сортировке сложных данных только по некоторой части этих данных. Рассмотрим этот пример:

usort($users, function($user1, $user2) {
return $user1->age $user2->age;
});

Этот код сортирует пользователей по возрасту. В настоящее время порядок пользователей в одной возрастной группе будет произвольным. При стабильной сортировке исходный порядок объектов будет сохранен. Например, если $users уже был отсортирован по имени, то $users теперь будут отсортированы по возрасту первым и по имени вторым. Конечно, в этом случае можно было бы явно отсортировать по двум критериям:

usort($users, function($user1, $user2) {
return $user1->age $user2->age
?: $user1->name $user2->name;
});

Однако это не всегда возможно, поскольку критерий, по которому данные были первоначально отсортированы, явно не сохраняется. Недавним случаем, с которым я столкнулся, был список git коммитов с метаданными, которые хранились в порядке push (но порядок push не был явно сохранен при каждом коммите).

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

$array  =  [ 
'c' => 1 ,
'd' => 1 ,
'a' => 0 ,
'b' => 0 ,
];
asort($array) ;

// При стабильной сортировке результат всегда:
[ 'a' => 0 , 'b' => 0 , 'c' => 1 , 'd' => 1 ]

// При нестабильной сортировке возможны также следующие результаты:
[ 'b' => 0 , 'a' => 0 , 'c' => 1 , 'd' => 1 ]
[ 'a' => 0 , 'b' => 0 , 'd' => 1 , 'c' => 1 ]
[ 'b' => 0 , 'a' => 0 , 'd' => 1 , 'c' => 1 ]

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

Этот RFC предлагает сделать все функции сортировки PHP стабильными. Сюда входят rsort, usort, asort, arsort, uasort, ksort, krsort, uksort, array_multisort, а также соответствующие методы ArrayObject.

Новые возможности

Как и другие выпуски PHP, PHP 8.1 добавит несколько приятных новых возможностей. Также я озвучу некоторые функции, которые еще не были реализованы, но у которых есть хорошие шансы появиться в конечном релизе PHP. Я обязательно буду это отмечать. Итак приступим!

Распаковка массива с помощью строковых ключей rfc

Распаковка массива уже была разрешена в PHP 7.4, но работала только с числовыми ключами. Причина, по которой строковые ключи не поддерживались раньше, заключается в том, что не было единого мнения о том, как объединять дубликаты массивов. RFC четко решает эту проблему, следуя семантике array_merge:

$array = ["a" => 1];

$array2 = ["b" => 2];

$arrayMerge = ["a" => 0, ...$array, ...$array2];

var_dump($arrayMerge); // ["a" => 1, "b" => 2]

Новый тип возврата never rfc

Тип never может быть использован для того, чтобы  указать, что функция фактически остановит поток приложения. Это можно сделать, выбросив исключение, вызывая exit/die или используя другие подобные функции.

function redirect(string $url): never {
header('Location: ' . $url);
exit();
}

redirect('Test'); // код гарантирует не продолжение.
do_something_else();

Тип возвращаемого значения  never аналогичен существующему типу возвращаемого значения void, но тип never гарантирует, что программа завершится или выдаст исключение. Другими словами, объявленная функция/метод never типом вообще не должна вызывать return. 

function foo(): never {
return;
}
foo();
TypeError: dispatch(): Nothing was expected to be returned

Как видите, если функция/метод с типом never никак не сгенерирует исключение, или прекратит  работу, то PHP выдаст исключение TypeError.

А если при never типе вызвать return, то PHP выдаст Fatal Error. Узнать об этом можно будет только во время вызова, а не во время синтаксического анализа.

function foo(): never {
return;
}
foo();
Fatal error: A never function must not return in ... on line 2

Исходный RFC предлагал использовать noreturn для этой цели. Впрочем последующее голосование в RFC  прошло уже в пользу never и он был избран.

Новая функция array_is_list rfc

Возможно, время от времени, вам приходится иметь с этим дело: определять, находятся ли ключи массива в числовом порядке, начиная с индекса 0. Точно так же, как json_encode решает, должен ли массив быть закодирован как массив или объект.

PHP 8.1 добавляет встроенную функцию, чтобы определить, является ли массив списком с этой семантикой или нет:

$list = ["a", "b", "c"];

array_is_list($list); // true

$notAList = [1 => "a", 2 => "b", 3 => "c"];

array_is_list($notAList); // false

$alsoNotAList = ["a" => "a", "b" => "b", "c" => "c"];

array_is_list($alsoNotAList); // false

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

// ключи начинаются не с 0
array_is_list([1 => 'apple', 'orange']); // false

// Ключи не отсортированы
array_is_list([1 => 'apple', 0 => 'orange']); // false

// Не все числовые ключи
array_is_list([0 => 'apple', 'foo' => 'bar']); false

// Непоследовательные ключи
array_is_list([0 => 'apple', 2 => 'bar']); false

  • array_is_list принимает только параметры типа array. Передача любого другого типа вызовет исключение TypeError.
  • array_is_list не принимает iterable или другие объекты класса, подобные массиву, такие как ArrayAccess, SPLFixedArray и т.д.
  • array_is_list объявлен в глобальном пространстве имен.  

Полифил функции будет выглядеть, как:

function array_is_list(array $array): bool {
if (empty($array)) {
return true;
}

$current_key = 0;
foreach ($array as $key => $noop) {
if ($key !== $current_key) {
return false;
}
++$current_key;
}

return true;
}

Новая функция fsync rfc

PHP 8.1 добавляет fsync и fdatasync - функции принудительной синхронизации изменений файлов на диск и и обеспечивающие очистку буферов записи операционной системы.

$file = fopen("sample.txt", "w");

fwrite($file, "Some content");

if (fsync($file)) {
echo "File has been successfully persisted to disk.";
}

fclose($file);

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

Явное восьмеричное целочисленное буквальное обозначение rfc

Теперь вы можете использовать 0o (нуль и маленькая буква o) и 0O (нуль и большая буква O) для обозначения восьмеричных чисел. Предыдущее обозначение с префиксом числа "0" по-прежнему работает:

016 === 0o16; // true
016 === 0O16; // true

Enums (Перечисления) rfc

Перечисления будут добавлены в PHP 8.1.

Добавление перечислений было бы значительным улучшением в PHP, поэтому я, со своей стороны, с нетерпением жду дальнейшего развития этого RFC. Чтобы вы могли быстро увидеть, как они будут выглядеть, вот пример кода:

enum Status {
case Pending;
case Active;
case Archived;
}

И вот как они будут использоваться:

class Order
{
public function __construct(
private Status $status = Status::Pending;
) {}

public function setStatus(Status $status): void
{
// …
}
}

$order->setStatus(Status::Active);

Fibers rfc

Файберы - также известные как «зеленые потоки» - это низкоуровневый механизм управления параллелизмом. Вероятно, вы не будете использовать их непосредственно в своих приложениях, но такие фреймворки, как Amphp и ReactPHP, будут широко их использовать.

Вот простой пример использования файберов: 

$fiber = new Fiber(function (): void {
$valueAfterResuming = Fiber::suspend('after suspending');

// …
});

$valueAfterSuspending = $fiber->start();

$fiber->resume('after resuming');

Улучшения производительности pr

Дмитрий Стогов добавил некоторые улучшения в opcache, он называет это "кешем наследования". Эта функция позволяет кэшировать ссылки между классами, так же как связанные классы могут быть предварительно загружены в PHP 7.4.

Дмитрий сообщает о приросте производительности от 5% до 8% благодаря этому изменению, приятная небольшая деталь, на которую следует обратить внимание в PHP 8.1.

New в инициализаторах rfc

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

class MyClass {
public function __construct(
private Logger $logger = new Logger(),
) {}
}

Свойства только для чтения rfc

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

class PostData {
public function __construct(
public readonly string $title,
public readonly DateTimeImmutable $date,
) {}
}

Попытка изменить свойство только для чтения после его инициализации приведет к ошибке:

$post = new Post('Title', /* … */);

$post->title = 'Other';

Error: Cannot modify readonly property Post::$title

Первоклассный вызываемый синтаксис rfc

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

function foo(int $a, int $b) { /* … */ }

$foo = foo(...);

$foo(a: 1, b: 2);

Типы чистых пересечений rfc

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

Типы пересечений в настоящее время изначально не поддерживаются языком. Вместо этого нужно использовать аннотации phpdoc и/или использовать типизированные свойства , как показано в следующем примере:

class Test {
private ?Traversable $traversable = null;
private ?Countable $countable = null;
/** @var Traversable&Countable */
private $both = null;

public function __construct($countableIterator) {
$this->traversable =& $this->both;
$this->countable =& $this->both;
$this->both = $countableIterator;
}
}

Типы чистых пересечений, позволяют указать с использованием синтаксиса T1&T2&... и их можно использовать везде там, где в настоящее время принимаются типы:

function generateSlug(HasTitle&HasId $post) {
return strtolower($post->getTitle()) . $post->getId();
}

Если вам нравится этот стиль программирования, вам нужно будет создать новый Sluggable интерфейс и реализовать его в $post, типы пересечений избавляются от этих накладных расходов.

Константы финального класса rfc

Константы классов в PHP можно переопределить во время наследования:

class Foo
{
public const X = "foo";
}

class Bar extends Foo
{
public const X = "bar";
}

Начиная с PHP 8.1, вы можете пометить такие константы final, чтобы предотвратить это:

class Foo
{
final public const X = "foo";
}

class Bar extends Foo
{
public const X = "bar";

Fatal error: Bar::X cannot override final constant Foo::X

}

Критические изменения

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

Ограничить использование $GLOBALS rfc

Небольшое изменение способа использования $GLOBALS  существенно повлияет на производительность всех операций с массивами. Никита Попов отлично справляется с объяснением проблемы и решения в RFC . Это изменение означает, что в некоторых крайних случаях работать с $GLOBAL больше нельзя.

«То, что больше не поддерживается, - это запись в $GLOBALS, взятую как единое целое».

Все нижеперечисленное вызовет ошибку времени компиляции:

$GLOBALS = [];
$GLOBALS += [];
$GLOBALS =& $x;
$x =& $GLOBALS;
unset($GLOBALS);

Кроме того, передача $GLOBALS по ссылке вызовет ошибку времени выполнения:

by_ref($GLOBALS); // Run-time error

Никита проанализировал 2000 лучших пакетов на сайте packagist и нашел только 23 случая, на которые повлияет это изменение. Можно сделать вывод, что влияние этого технически критического изменения будет незначительным, поэтому internals решили добавить его в PHP 8.1. Помните, что большинство из нас выиграет от этого изменения, учитывая положительное влияние на производительность, которое оно оказывает во всем нашем коде. 

Перенос Ресурсов в объекты

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

Fileinfo функции с объектами finfo. Функции вроде finfo_file и finfo_open используются для приема и возврата ресурсов. Начиная с PHP 8.1, они работают с объектами finfo.

Функции IMAP с объектами IMAPConnection. Как и при изменении информации о файле, IMAP работает как imap_body, а imap_open больше не работает с ресурсами.

Устарела передача null не null-ых аргументов внутренним функциям rfc

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

str_contains("string", null);

 В PHP 8.1 такие ошибки будут вызывать предупреждение об устаревании, в PHP 9 они будут преобразованы в ошибки типа.

Автовивификация на false rfc

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

Вы можете прочитать подробности на странице RFC. Таким образом, это поведение устарело:

$array = false;

$array[] = 2;

Automatic conversion of false to array is deprecated

Вкратце о нововведениях в PHP8

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

  • Union Types
  • JIT
  • Nullsafe-оператор
  • Именованные аргументы
  • Атрибуты
  • Выражение соответствия
  • Новый тип mixed
  • Throw-выражения
  • Weak maps
  • Неименованные исключения

Union Types

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

JIT

Компилятор Just In Time, именуемый JIT, оптимизирует производительность процессов во время работы программ, приложений и т.д., благодаря динамической компиляции. Первоочередно код транслируется в промежуточное представление, затем в машинный код. Выполнение происходит на процессоре, без использования виртуальной машины.

Nullsafe-оператор

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

Внедрение Nullsafe-оператора позволяет разработчику наблюдать поведение методов.

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

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

Атрибуты

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

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

Новинка PHP 8 – match. Это выражение с возможностью сохранения в переменной либо возврата. Новые условия позволяют применять исключительно однострочные выражения без использования конструкции break.

Новый тип mixed

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

Throw-выражения

Обновленная версия позволит преобразование в выражение инструкции throw – в предыдущих выпусках такой опции не было. Такое нововведение расширяет возможности использования инструмента.

Weak maps

Восьмая версия дополнена классом WeakMaps, позволяющим сохранять ссылку на объект. При этом сам объект можно свободно удалить.

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

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

Новые возможности PHP 8

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

С полным списком изменений в версии PHP 8 вы можете ознакомиться на официальном сайте.

Новые функции в ожидаемом PHP8

PHP 8 все еще находится в активной разработке, поэтому этот список со временем будет расти и изменятся.

Что ожидается нового в PHP 8


Вариантные Типы rfc

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

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

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

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

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

JIT компилятор

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

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

как это работает?

Есть так называемый «монитор», который будет смотреть на код во время его работы. Когда этот монитор обнаруживает повторяющиеся части вашего кода, он помечает эти части как «теплые» или «горячие», в зависимости от частоты.

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

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

Однако, поскольку PHP чаще всего используется в веб-контексте, мы также должны измерить влияние JIT.

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

Пример

Это причина, чтобы отказаться от JIT? Точно нет! Есть веские аргументы, чтобы добавить его, хотя это может не оказать влияния на производительность, на которую мы надеемся.

  • Это открывает дверь для использования PHP как очень производительного языка вне сети.
  • Со временем JIT может быть улучшен, как и наш код.

Это веские аргументы в пользу JIT.

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


Атрибуты

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

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

use App\Attributes\ExampleAttribute;


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

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

Статический тип возврата

Хотя возвращение уже было возможно self, staticон не был допустимым типом возврата до PHP 8.

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

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

Созание (выброс) исключения в выражениях

Этот RFC превращается throw из выражения в выражение, что позволяет создавать исключения во многих новых местах:

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

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

Слабые ссылки(WeakMap) rfc

Построенный на RFC слабых ссылок, который был добавлен в PHP 7.4, WeakMapв PHP 8 добавлена ​​реализация, в которой WeakMapsхранятся ссылки на объекты , которые не препятствуют сборке мусора этими объектами.

Возьмите пример ORM, они часто реализуют кэши, которые содержат ссылки на классы сущностей, чтобы улучшить производительность отношений между сущностями . Об этом говорит сайт https://intellect.icu . Эти объекты сущности нельзя собирать, пока кеш имеет ссылку на них, даже если кеш является единственной ссылкой на них.

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

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

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

Доступность ::class для объектов rfc

Небольшая, но полезная новая функция: теперь можно использовать ::class для объектах,

вместо того, чтобы использовать get_class() их. Это работает так же, как get_class().

$foo = new Foo();

var_dump($foo::class);

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

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

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

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

Вы уже можете создать DateTimeобъект из DateTimeImmutableобъекта, используя DateTime::createFromImmutable($immutableDateTime), но наоборот было сложно. Добавляя DateTime::createFromInterface()и DatetimeImmutable::createFromInterface()теперь есть обобщенный способ конвертировать DateTimeи DateTimeImmutableобъекты друг в друга.

DateTime::createFromInterface(DateTimeInterface $other);

DateTimeImmutable::createFromInterface(DateTimeInterface $other);

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

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

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

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

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

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

Некоторые могут сказать, что это давно пора, но нам, наконец, больше не нужно полагаться на то 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() функции rfc

Две другие давно назревшие, эти две функции теперь добавлены в ядро.

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

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

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


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

get_debug_type()возвращает тип переменной. Похоже, что gettype() подошел бы?

Но get_debug_type() возвращает более полезный вывод для массивов , строк, анонимных классов и объектов.

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

Полный список различий между get_debug_type()и gettype()можно найти в RFC.


Абстрактные методы улучшения примесей rfc

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

Однако есть предостережение: до 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() rfc

token_get_all() Функция возвращает массив значений. Этот RFC добавляет PhpToken класс с PhpToken::getAll() методом. Эта реализация работает с объектами вместо простых значений. Он потребляет меньше памяти и его легче читать.


Изменения в синтаксисе rfc

Из RFC: «Унифицированный синтаксис переменной RFC разрешил ряд несоответствий в синтаксисе переменной PHP.

Этот RFC намеревается решить небольшую порцию пропущенных дел».

https://wiki.php.net/rfc/variable_syntax_tweaks


Тип аннотации для внутренних функций

Много людей просили добавить соответствующие аннотации типов для всех внутренних функций.

Это была давняя проблема, и, наконец, ее можно было решить со всеми изменениями, внесенными в PHP в предыдущих версиях

. Это означает, что внутренние функции и методы будут иметь полную информацию о типе в рефлексии.