Скриптовый язык PHP 26 ноября 2020 года обновится до новой основной версии — PHP 8, а 18 июня 2020 года уже вышла его первая альфа-версия. Подробно разбираем, какие функции появятся в новой версии языка, сильно ли увеличится производительность и какие изменения придется внедрить в свой код, чтобы запустить его на восьмой версии PHP.
Разработчики языка отмечают, что PHP 8 до сих пор находится в стадии активной разработки, поэтому список новых функций со временем будет увеличиваться.
Учитывая динамически типизированную природу PHP, существует множество случаев, когда использование типов объединения может быть полезным. Union Types представляют собой совокупность двух или более типов, которые указывают, что можно использовать любой из них.
public function foo(Foo|Bar $input): int|float;
При этом void
никогда не может быть частью Union Types, так как он означает «вообще никакого возвращаемого значения». Кроме того, с помощью |null
или ?-нотации можно объявить nullable-объединения:
public function foo(Foo|null $foo): void;
public function bar(?Bar $bar): void;
Разработчики обещают улучшения в производительности PHP 8 в том числе благодаря стратегии компиляции JIT (just in time, "в нужный момент"). В JIT код сначала переводится в промежуточное представление, и только потом — в машинный код, зависящий от архитектуры. Это значит, что он в ходе исполнения будет превращаться в машинный код, который будет выполняться не виртуальной машиной Zend VM, на которой построен язык, а непосредственно на процессоре.
Если вы работали с оператором объединения с null, вы уже знакомы с его недостатками: его нельзя применить в цепочке вызовов методов. Вместо этого вам нужны промежуточные проверки или придется полагаться на optional
помощников, предоставляемых некоторыми платформами:
$startDate = $dateAsString = $booking->getStartDate();
$dateAsString = $startDate ? $startDate->asDateTimeString() : null;
С добавлением nullsafe-оператора разработчик может видеть поведение методов, подобное поведению при объединению с null.
$dateAsString = $booking->getStartDate()?->asDateTimeString();
Именованные аргументы позволяют передавать значения в функцию, указав имя этого значения. Это позволит разработчику не учитывать порядок значений, а также пропустить какие-нибудь необязательные параметры.
function foo(string $a, string $b, ?string $c = null, ?string $d = null)
{ /* … */ }
foo(
b: 'value b',
a: 'value a',
d: 'value d',
);
Атрибуты — в других языках программирования известные как аннотации, предлагают новый способ добавить метаданные в классы, без необходимости парсить докблок-комментарии.
Вот пример того, как выглядят атрибуты:
use App\Attributes\ExampleAttribute;
@@ExampleAttribute
class Foo
{
@@ExampleAttribute
public const FOO = 'foo';
@@ExampleAttribute
public $x;
@@ExampleAttribute
public function foo(@@ExampleAttribute $bar) { }
}
@@Attribute
class ExampleAttribute
{
public $value;
public function __construct($value)
{
$this->value = $value;
}
}
Выражение match
можно назвать старшим братом выражения switch
— оно может возвращать значения, комбинировать условия, и при этом не требует break
операторов. Кроме того, match
не выполняет никакого приведения типов.
$result = match($input) {
0 => "hello",
'1', '2', '3' => "world",
};
В PHP 8 появится синтаксический сахар для создания объектов значений или объектов передачи данных. Вместо отдельного указания определения свойств класса и конструктора для них, PHP теперь может объединять их в один краткий синтаксис.
Вместо такого кода:
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,
) {}
}
Несмотря на то, что в PHP уже были возвращаемые типы — self
и parent
, до сих пор static
не был допустимым типом возврата для этого языка программирования. Учитывая динамически типизированный характер PHP, эта функция будет полезна для многих разработчиков.
class Foo
{
public function test(): static
{
return new static();
}
}
С добавлением скалярных типов в PHP 7, обнуляемых — в PHP 7.1, и, наконец, — объединенных типов в 8.0, PHP-разработчики могут явно объявлять информацию о типе для большинства параметров функции, callable-функций, а также свойств класса. Тем не менее, PHP не всегда поддерживает типы, и, скорее всего, он иногда будет пропускать информацию о них. Это приводит к тому, что значение будет неоднозначным, когда отсутствует информация о типе.
Явный mixed
тип позволит разработчикам добавлять типы в параметры, свойства класса и возвращаемые значения из функции, чтобы указать, что информация о типе не была забыта, а её просто нельзя указать более точно.
mixed
сам по себе означает один из этих типов:
array
bool
callable
int
float
null
object
resource
string
Обратите внимание, что mixed
также может использоваться как параметр или тип свойства, а не только как тип возвращаемого значения.
Также обратите внимание, что, поскольку mixed
уже включает null
, его нельзя делать nullable
. Такой код вызовет ошибку:
// Fatal error: Mixed types cannot be nullable, null is already part of the mixed type.
function bar(): ?mixed {}
В PHP оператор throw не может создавать исключения там, где были разрешены только выражения, например стрелочные функции, оператор объединения и тернарный оператор. После обновления PHP будет преобразовывать инструкцию throw
в выражение, это расширит функциональность использования языка.
$triggerError = fn () => throw new MyError();
$foo = $bar['offset'] ?? throw new OffsetDoesNotExist('offset');
Раньше PHP применял одинаковые проверки наследования для public, protected и private- методов. Другими словами: private методы должны были быть определены так же, как protected и public. При этом такое определение не имеет смысла, так как private методы не будут доступны дочерним классам.
Это обновление изменило поведение таких методов — теперь проверки наследования больше не выполняются для private методов. Кроме того, использование final private function
и раньше не имело особого смысла, поэтому теперь оно вызовет предупреждение:
Warning: Private methods cannot be final as they are never overridden by other classes
Слабая карта — это набор объектов, на которые в коде слабо ссылаются ключи, что может привести к их удалению сборщиками мусора. В PHP 8 добавляется класс WeakMaps для создания сохранения ссылки на объект, при этом она не препятствует удалению самого объекта.
Например, возьмем пример ORM — они часто реализуют кэши, где содержатся ссылки на классы сущностей, чтобы улучшить производительность отношений между сущностями. Эти сущности не могут быть собраны сборщиком мусора, пока кэш имеет ссылку на них, даже если кэш является единственной реальной ссылкой на них. Если теперь кэш будет использовать слабые ссылки и WeakMaps, то PHP сможет справляться с мусором. Особенно в случае с ORM, которые могут управлять несколькими сотнями, если не тысячами объектов в ходе запроса.
Пример использования WeakMaps в PHP 8:
class Foo
{
private WeakMap $cache;
public function getSomethingWithCaching(object $obj): object
{
return $this->cache[$obj]
??= $this->computeSomethingExpensive($obj);
}
}
Небольшая, но полезная новая фича: теперь можно использовать ::class
на объектах, вместо того, чтобы использовать get_class()
. Работает так же, как и get_class()
.
$foo = new Foo();
var_dump($foo::class);
Всякий раз, когда вы хотели перехватить исключение до PHP 8, нужно было сохранить его в переменной независимо от того, использовали ли вы эту переменную или нет. Теперь можно перехватить исключения без указания переменной.
Если раньше программисту приходилось писать такой код:
try {
// Something goes wrong
} catch (MySpecialException $exception) {
Log::error("Something went wrong");
}
То в PHP 8 это будет выглядеть уже так:
try {
// Something goes wrong
} catch (MySpecialException) {
Log::error("Something went wrong");
}
Обратите внимание, что необходимо всегда указывать тип, поскольку PHP 8 не разрешает иметь пустой catch
. Если вы хотите перехватить все исключения и ошибки, вы можете использовать их Throwable
как тип исключения.
Несмотря на поддержку висячей запятой в аргументах при вызове функций, она не поддерживалась в списке параметров при объявлении функции.
public function(
string $parameterA,
int $parameterB,
Foo $objectfoo,
) {
// …
}
Обратите внимание на запятую после $objectfoo
.
DateTime
объекты из интерфейсаВы уже можете создать DateTime
объект из DateTimeImmutable
объекта. Однако наоборот в PHP было сделать очень сложно. Теперь в языке появился новый обобщенный способ конвертировать эти объекты друг в друга.
DateTime::createFromImmutable($immutableDateTime)DateTime::createFromInterface()DatetimeImmutable::createFromInterface()DateTimeDateTimeImmutable
DateTime::createFromInterface(DateTimeInterface $other);
DateTimeImmutable::createFromInterface(DateTimeInterface $other);
Интерфейс Stringable
можно использовать для аннотации типов или имплементации метода __toString ()
. Кроме того, всякий раз, когда класс, содержащий __toString ()
, будет автоматически имплементировать этот интерфейс "под капотом", и нет необходимости вручную реализовывать его.
class Foo
{
public function __toString(): string
{
return 'foo';
}
}
function bar(Stringable $stringable) { /* … */ }
bar(new Foo());
bar('abc');
Наконец-то на PHP больше не нужно полагаться на strpos()
, чтобы узнать, содержит ли строка другую строку.
Вместо этого:
if (strpos('string with lots of words', 'words') !== false) { /* … */ }
Можно использовать вот это:
if (str_contains('string with lots of words', 'words')) { /* … */ }
Ещё две давно назревшие функции. str_starts_with()
проверяет, начинается ли строка с другой строки, и возвращает логическое значение ( true/ false).
str_ends_with()
проверяет, заканчивается ли строка другой строкой, и возвращает логическое значение ( true/ false).
str_starts_with('haystack', 'hay'); // true
str_ends_with('haystack', 'stack'); // true
Новая fdiv()
функция делает процессы, похожие на функции fmod()
и intdiv()
функции — позволяет делить на 0. Вместо ошибки в результате вы получите INF
, -INF
или NaN
, в зависимости от случая.
Функция get_debug_type()
возвращает тип переменной. При этом get_debug_type()
возвращает более полезный вывод для массивов, строк, анонимных классов и объектов, чем стандартный gettype()
.
Например, вызов gettype()
для класса \Foo\Bar
вернет object
. Использование get_debug_type()
вернет имя класса.
Ресурсы — это специальные переменные в PHP, ссылающиеся на внешние ресурсы. Например, соединение MySQL или обработчик файла — каждому из них присваивается идентификатор, хотя ранее единственным способом узнать, что это идентификатор, было преобразование ресурса в int
:
$resourceId = (int) $resource;
PHP 8 добавляет get_resource_id()
функции, делая эту операцию более очевидной и безопасной для типов:
$resourceId = get_resource_id($resource);
Трейты могут содержать абстрактные методы, которые должны быть реализованы классами, использующими их. При этом важно, что до 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()
в настоящее время возвращает токены либо в виде односимвольной строки, либо в виде массива с идентификатором токена, текстом токена и номером строки. Это обновление языка предлагает добавить альтернативу token_get_all ()
, которая вместо этого возвращает массив объектов. Это уменьшает использование памяти и делает код, работающий с токенами, более читабельным.
В этом обновлении изменились правила синтаксиса при работе с переменными в PHP. Например, при их разыменовывании.
Теперь разработчики смогут добавлять информацию о типе внутренних функций. Это была давняя проблема языка, и, наконец, её удалось решить. Это означает, что внутренние функции и методы будут иметь полную информацию о типе в Reflection.
В настоящее время возможно скомпилировать PHP без расширения JSON с помощью ./configure --disable-json
. Тем не менее, JSON чрезвычайно полезен, потому что он широко используется во многих случаях — в работе сайтов, выходных данных журналов, в качестве формата данных, который можно использовать для обмена данными со многими приложениями и языками программирования. Поэтому разработчики языка запретили выключать JSON.
Пользовательские функции в PHP уже генерируют ошибки TypeError
, но внутренние функции — пока нет, они скорее выдают предупреждения и возвращают null
. Начиная с PHP 8, поведение внутренних функций стало согласованным с пользовательскими.
Многие ошибки, которые ранее вызывали только предупреждения или уведомления, были преобразованы в правильные ошибки:
• Неопределенная переменная: Error
исключение вместо Notice
;
• Неопределенный индекс массива: предупреждение вместо уведомления;
• Деление на ноль: DivisionByZeroError
исключение вместо предупреждения;
• Попытка увеличить/уменьшить свойство "% s" не-объекта: Error
исключение вместо предупреждения;
• Попытка изменить свойство "% s" не-объекта: Error
исключение вместо предупреждения;
• Попытка назначить свойство "% s" не-объекта: Error
исключение вместо предупреждения;
• Создание объекта по умолчанию из пустого значения: Error
исключение вместо предупреждения;
• Попытка получить свойство "% s" не-объекта: предупреждение вместо уведомления;
• Неопределенное свойство: % s :: $% s:
предупреждение вместо уведомления;
• Невозможно добавить элемент в массив, так как следующий элемент уже занят: Error
исключение вместо предупреждения;
• Невозможно сбросить смещение в переменной, не являющейся массивом: Error
исключение вместо предупреждения;
• Нельзя использовать скалярное значение в качестве массива: Error
исключение вместо предупреждения;
• Только массивы и Traversables
могут быть распакованы: TypeError
исключение вместо предупреждения;
• Указан неверный аргумент для foreach (): TypeError
исключение вместо предупреждения;
• Недопустимый тип смещения: TypeError
исключение вместо предупреждения;
• Недопустимый тип смещения в isset
или empty: TypeError
исключение вместо предупреждения;
• Недопустимый тип смещения в unset: TypeError
исключение вместо предупреждения;
• Преобразование массива в строку: предупреждение вместо уведомления;
• Идентификатор ресурса #% d, используемый в качестве смещения, приведение к целому числу (% d): предупреждение вместо уведомления;
• Произошло смещение строки: предупреждение вместо уведомления;
• Смещение неинициализированной строки: % d: предупреждение вместо уведомления;
• Невозможно назначить пустую строку для смещения строки: Error
исключение вместо предупреждения;
Вполне возможно, что это изменение может выявить ошибки, которые были скрыты до PHP 8. Обязательно установите их на своих рабочих серверах display_errors=Off
Теперь E_ALL
вместо всего, кроме E_NOTICE
и E_DEPRECATED
. Это означает, что может появиться много ошибок, которые ранее игнорировались, хотя, возможно, уже существовали до PHP 8.
Текущий режим ошибок по умолчанию для PDO — беззвучный. Это означает, что при возникновении ошибки SQL, никакие ошибки или предупреждения не могут выдаваться, а также не генерируются исключения, если разработчик не реализует свою собственную явную обработку ошибок. Это обновление изменит ошибку по умолчанию, которая выдается на PHP 8. PDO::ERRMODE_EXCEPTION
Это обновление появилось еще на PHP 7.4, но теперь оно официально вступает в силу. Если бы вы написали что-то вроде этого:
echo "sum: " . $a + $b;
PHP ранее интерпретировал бы это так:
echo ("sum: " . $a) + $b;
PHP 8 сделает так, чтобы он интерпретировался так:
echo "sum: " . ($a + $b);
До PHP 8 можно было применять арифметические или побитовые операторы к массивам, ресурсам или объектам. Это больше не возможно и выдаст TypeError
:
[] % [42];
$object + 4;
На PHP 8 реализовано изменение трех сигнатур методов классов Reflection.
Было:
ReflectionClass::newInstance($args);
ReflectionFunction::invoke($args);
ReflectionMethod::invoke($object, $args);
Стало:
ReflectionClass::newInstance(...$args);
ReflectionFunction::invoke(...$args);
ReflectionMethod::invoke($object, ...$args);
В руководстве к PHP 8 говорится, что если разработчики хотят использовать как обновленную версию языка, так и предыдущие, то допускается использование:
ReflectionClass::newInstance($arg = null, ...$args);
ReflectionFunction::invoke($arg = null, ...$args);
ReflectionMethod::invoke($object, $arg = null, ...$args);
До PHP 8 алгоритмы сортировки были нестабильны. Это означает, что порядок равных элементов не был гарантирован. PHP 8 меняет поведение всех функций сортировки на стабильную сортировку.
Ошибки наследования из-за несовместимых сигнатур методов в настоящее время либо генерируют фатальную ошибку, либо предупреждение — в зависимости от причины ошибки и иерархии наследования. Теперь всегда будет выдаваться фатальная ошибка.
В Хекслете есть большая профессия «PHP-программист», которая подойдет как для начинающих разработчиков, так и для людей, которые понимают основу этого языка. Кроме того, для продвинутых программистов на Хекслете есть специальные курсы — «Веб-разработка на PHP» и «ООП В PHP».