PHP 8.0: Полный обзор новых возможностей (1/4)
Вышла версия PHP 8.0. Она настолько насыщена новинками, как ни одна версия до этого. Их представление потребовало целых четыре отдельных статьи. В этой первой мы рассмотрим, что нового она приносит с точки зрения языка.

Прежде чем мы погрузимся в PHP, знайте, что текущая версия Nette полностью готова к восьмерке. Более того, в качестве подарка даже вышла Nette 2.4, которая полностью с ней совместима, так что с точки зрения фреймворка вам ничто не мешает начать использовать новую версию.
Именованные аргументы
И начнем сразу с бомбы, которую смело можно назвать game changer. Теперь можно передавать аргументы функциям и методам не только позиционно, но и по именам. Что абсолютно замечательно в случае, если у метода действительно много параметров:
class Response implements IResponse
{
public function setCookie(
string $name,
string $value,
string|DateInterface|null $time,
string $path = null,
string $domain = null,
bool $secure = null,
bool $httpOnly = null,
string $sameSite = null
) {
...
}
}
Первые два аргумента передадим позиционно, остальные по имени: (именованные всегда должны следовать после позиционных)
$response->setCookie('lang', $lang, sameSite: 'None');
// вместо сумасшедшего
$response->setCookie('lang', $lang, null, null, null, null, null, 'None');
До появления этой функции планировалось
создать в Nette новый API для отправки cookies,
потому что количество параметров
setCookie()
действительно выросло, и
позиционная запись была нечитаемой. Теперь
это не нужно, потому что именованные
аргументы в этом случае являются самым
практичным API. IDE будет их подсказывать, и у
них есть проверка типов.
Они отлично подходят и для пояснения
логических параметров, где их
использование хоть и не обязательно, но
само по себе true
или false
мало
что говорит:
// раньше
$db = $container->getService(Database::class, true);
// теперь
$db = $container->getService(Database::class, need: true);
Имена параметров теперь становятся частью публичного API. Их нельзя произвольно менять, как раньше. По этой причине Nette также проходит аудит, чтобы убедиться, что все параметры имеют подходящие имена.
Именованные аргументы можно использовать также в сочетании с variadics:
function variadics($a, ...$args) {
dump($args);
}
variadics(a: 1, b: 2, c: 3);
// в $args будет ['b' => 2, 'c' => 3]
Теперь массив $args
может содержать и
нечисловые ключи, что является
определенным BC break. То же самое касается и
поведения функции
call_user_func_array($func, $args)
, где теперь ключи
в массиве $args
играют значительную
роль. Напротив, функции семейства
func_*()
изолированы от именованных
аргументов.
С именованными аргументами также тесно
связан тот факт, что splat оператор ...
теперь может распаковывать и ассоциативные
массивы:
variadics(...['b' => 2, 'c' => 3]);
Удивительно, но это пока не работает внутри массивов:
$arr = [ ...['a' => 1, 'b' => 2] ];
// Fatal error: Cannot unpack array with string keys
Комбинация именованных аргументов и variadics
дает возможность наконец-то иметь строгий
синтаксис, например, для метода презентера
link()
, которому теперь можно
передавать именованные аргументы так же,
как и позиционные:
// раньше
$presenter->link('Product:detail', $id, 1, 2);
$presenter->link('Product:detail', [$id, 'page' => 1]); // должен был быть массив
// теперь
$presenter->link('Product:detail', $id, page: 1);
Синтаксис для именованных аргументов
гораздо сексуальнее, чем написание
массивов, поэтому Latte
немедленно взял его на вооружение, где он
может быть использован, например, в тегах
{include}
и {link}
:
{include 'file.latte' arg1: 1, arg2: 2}
{link default page: 1}
К именованным параметрам мы еще вернемся в третьей части в связи с атрибутами.
Выражение может выбрасывать исключение
Выброс исключения теперь является
выражением. Его можно, например, обернуть в
скобки и добавить в условие if
. Хм,
звучит не очень практично. Но вот это уже
интереснее:
// раньше
if (!isset($arr['value'])) {
throw new \InvalidArgumentException('значение не установлено');
}
$value = $arr['value'];
// теперь, когда throw является выражением
$value = $arr['value'] ?? throw new \InvalidArgumentException('значение не установлено');
Поскольку стрелочные функции до сих пор могли содержать только одно выражение, благодаря этой функции они могут выбрасывать исключения:
// только одно выражение
$fn = fn() => throw new \Exception('ой');
Выражения Match
Конструкция switch-case
имеет два
больших недостатка:
- использует нестрогое сравнение
==
вместо===
- нужно быть внимательным, чтобы случайно
не забыть
break
Поэтому PHP предлагает альтернативу в виде
новой конструкции match
, которая
использует строгое сравнение и, наоборот,
не использует break
.
Пример кода switch
:
switch ($statusCode) {
case 200:
case 300:
$message = $this->formatMessage('ок');
break;
case 400:
$message = $this->formatMessage('не найдено');
break;
case 500:
$message = $this->formatMessage('ошибка сервера');
break;
default:
$message = 'неизвестный код состояния';
break;
}
И то же самое (только со строгим
сравнением), записанное с помощью
match
:
$message = match ($statusCode) {
200, 300 => $this->formatMessage('ок'),
400 => $this->formatMessage('не найдено'),
500 => $this->formatMessage('ошибка сервера'),
default => 'неизвестный код состояния',
};
Обратите внимание, что match
— это не
управляющая структура, как switch
, а
выражение. Его результирующее значение в
примере мы присваиваем переменной. В то же
время отдельные “опции” также являются
выражениями, поэтому нельзя записать
несколько шагов, как в случае switch
.
Если совпадения ни с одним из вариантов не
произошло (и отсутствует ветка default),
выбрасывается исключение
UnhandledMatchError
.
Кстати, в Latte также существуют теги
{switch}
, {case}
и {default}
. Их
функционирование точно соответствует
новому match
. Они используют строгое
сравнение, не требуют break
, и в
case
можно указать несколько значений,
разделенных запятыми.
Nullsafe оператор
Необязательная цепочка вызовов (optional
chaining) позволяет писать выражение,
вычисление которого прекращается, если
встречается null. И это благодаря новому
оператору ?->
. Он заменит множество
кода, который в противном случае
многократно проверял бы на null:
$user?->getAddress()?->street
// означает примерно
$user !== null && $user->getAddress() !== null
? $user->getAddress()->street
: null
Почему «означает примерно»? Потому что на
самом деле выражение вычисляется более
продуманно, и ни один шаг не повторяется.
Например, $user->getAddress()
вызывается
только один раз, поэтому не может
возникнуть проблема, вызванная тем, что
метод в первый и второй раз вернул бы что-то
другое.
Эту свежую фишку год назад Latte принес. Теперь она попадает в сам PHP. Отлично.
Продвижение свойств конструктора
Синтаксический сахар, который экономит двойное написание типа и четырехкратное написание переменной. Жаль только, что он не появился во времена, когда у нас не было таких умных IDE, которые сегодня пишут это за нас 🙂
class Facade
{
private Nette\Database\Connection $db;
private Nette\Mail\Mailer $mailer;
public function __construct(Nette\Database\Connection $db, Nette\Mail\Mailer $mailer)
{
$this->db = $db;
$this->mailer = $mailer;
}
}
class Facade
{
public function __construct(
private Nette\Database\Connection $db,
private Nette\Mail\Mailer $mailer,
) {}
}
С Nette DI работает, можете сразу начинать использовать.
Строгое поведение арифметических и битовых операторов
То, что когда-то вывело динамические скриптовые языки на вершину славы, со временем стало их самым слабым местом. PHP когда-то избавлялся от “magic quotes”, регистрации глобальных переменных, а теперь свободное поведение сменяется строгостью. Время, когда в PHP можно было складывать, умножать и т.д. практически любые типы данных, для которых это не имело абсолютно никакого смысла, давно прошло. Начиная с версии 7.0, PHP становится все более строгим, а с версии 8.0 попытка использования любого арифметического/битового оператора для массивов, объектов или ресурсов заканчивается TypeError. Исключением является сложение массивов.
// арифметические и битовые операторы
+, -, *, /, **, %, <<, >>, &, |, ^, ~, ++, --:
Более разумное сравнение строк и чисел
Или make loose operator great again.
Казалось бы, для loose оператора ==
уже
нет места, что это просто опечатка при
написании ===
, но это изменение
возвращает его снова на карту. Раз уж он у
нас есть, пусть ведет себя разумно.
Следствием прежнего “неразумного”
сравнения было, например, поведение
in_array()
, которое могло вас неприятно
подвести:
$validValues = ['foo', 'bar', 'baz'];
$value = 0;
dump(in_array($value, $validValues));
// удивительно, но возвращало true
// с PHP 8.0 возвращает false
Изменение в поведении ==
касается
сравнения чисел и “числовых” строк и
показано в следующей таблице:
Сравнение | Раньше | PHP 8.0 |
---|---|---|
0 == "0" |
true | true |
0 == "0.0" |
true | true |
0 == "foo" |
true | false |
0 == "" |
true | false |
42 == " 42" |
true | true |
42 == "42 " |
true | true |
42 == "42foo" |
true | false |
42 == "abc42" |
false | false |
"42" == " 42" |
true | true |
"42" == "42 " |
false | true |
Удивительно, что это BC break в самой основе языка, который был одобрен без какого-либо сопротивления. И это хорошо. Здесь JavaScript мог бы сильно позавидовать.
Сообщение об ошибках
Многие внутренние функции теперь
вызывают TypeError и ValueError вместо
предупреждений, которые легко можно было
пропустить. Был переклассифицирован ряд
предупреждений ядра. Оператор подавления
ошибок @
теперь не заглушает
фатальные ошибки. А PDO в режиме по умолчанию
выбрасывает исключения.
Nette всегда старался каким-то образом решать эти проблемы. Tracy изменяла поведение оператора подавления ошибок, Database переключал поведение PDO, Utils содержит замены стандартных функций, которые выбрасывают исключения вместо незаметных предупреждений и т.д. Приятно видеть, что строгое направление, заложенное в ДНК Nette, становится нативным направлением языка.
Массивы с отрицательным индексом
$arr[-5] = 'first';
$arr[] = 'second';
Каким будет ключ второго элемента? Раньше
это был 0
, с PHP 8 это -4
.
Завершающая запятая
Последнее место, где не могла быть завершающая запятая, было определение параметров функции. Это уже в прошлом:
public function __construct(
Nette\Database\Connection $db,
Nette\Mail\Mailer $mailer, // завершающая запятая
) {
....
}
$object::class
Магическая константа ::class
работает
и с объектами $object::class
, тем самым
полностью заменяя функцию get_class()
.
catch без переменной
И наконец: в блоке catch не обязательно указывать переменную для исключения:
try {
$container->getService(Database::class);
} catch (MissingServiceException) { // нет $e
$logger->log('....');
}
В следующих частях нас ждут существенные новинки в типах данных, мы покажем, что такое атрибуты, какие новые функции и классы появились в PHP, и представим Just in Time Compiler.
Чтобы оставить комментарий, пожалуйста, войдите в систему