Что нового в PHP 8.0
- Release: https://www.php.net/releases/8.0/ru.php
- Wiki: PHP 8.0
- github: Список изменений PHP 8.0
__construct( public int $num ) — объявление свойств в конструкторе
wiki: https://wiki.php.net/rfc/constructor_promotion
doc: https://www.php.net/manual/ru/language.oop5.decon.php#language.oop5.decon.constructor.promotion
Эта новинка позволяет писать меньше шаблонного кода для определения и инициализации свойств.
class Point {
public function __construct(
public float $x = 0.0,
public float $y = 0.0,
public float $z = 0.0,
) {
}
}
Раньше тоже самое записывалось так:
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;
}
}
Если декларация аргумента конструктора включает модификатор видимости (public, protected, private), PHP интерпретирует его и как аргумент конструктора, и как свойство объекта, и автоматически присвоит свойству значение, переданное в конструктор.
Код конструктора выполнится после того, как все аргументы присвоятся всем соответствующим свойствам.
Если не предполагается какой-либо дополнительной логики, тело конструктора можно оставить пустым.
func(foo: 'bar') — именованные аргументы (параметры)
wiki: https://wiki.php.net/rfc/named_params
Плюсы:
- Позволяют пропускать значения по умолчанию.
- Порядок аргументов не важен.
- Aргументы самодокументируемы.
Минусы:
- Нельзя просто так взять и поменять название параметра функции/метода. Теперь он важен, так как указывается при вызове функции/метода.
Пример:
// PHP < 8: array_fill(0, 100, 50); // PHP 8+: array_fill(start_index: 0, num: 100, value: 50); // Или можно нарушить порядок array_fill(value: 50, num: 100, start_index: 0);
Можно миксовать именованные и нет параметры:
htmlspecialchars($string, double_encode: false); // Тоже что: htmlspecialchars($string, ENT_COMPAT|ENT_HTML401, 'UTF-8', false);
Пример того, как получается авто-документация:
array_slice($array, $offset, $length, true); // и array_slice($array, $offset, $length, preserve_keys: true);
Распаковка массива с параметрами:
$input = [ 'start_index' => 5, 'num' => 100, 'value' => 50, ]; array_fill(...$input); // или так (порядковые параметры можно указывать без ключа) $input = [ 5, 'num' => 100, 'value' => 50, ]; array_fill(...$input);
Важно: Если в массиве будет элемент (ключ), которого нет в параметрах фукнции, то мы получим ошибку.
Nullsafe (?) — оператор проверки на null
wiki: https://wiki.php.net/rfc/nullsafe_operator
Вместо проверки на null можно использовать цепочку вызовов (chained calls) с новым оператором Nullsafe. Если один из элементов последовательности возвращает null, выполнение прерывается и вся последовательность возвращает null.
$country = $session?->user?->getAddress()?->country;
Раньше то же самое записывалось так:
$country = null;
if ( $session !== null ) {
$user = $session->user;
if ( $user !== null ) {
$address = $user->getAddress();
if ( $address !== null ) {
$country = $address->country;
}
}
}
Этот оператор назвают еще:
- null-safe
- safe navigation
- optional chaining
- null-conditional
match — аналог switch или if...elseif...else
wiki: https://wiki.php.net/rfc/match_expression_v2
Аналогично оператору switch, выражение match принимает на вход выражение, которое сравнивается с указанными значениями.
В отличие от switch:
- Используется строгое сравнение
===. - Возвращает результат.
- Исполняется только одна, первая подошедшая, ветвь кода, а в switch происходит сквозное исполнение начиная с подошедшего условия и до первого встретившегося break.
Если проверяемое выражение не совпало ни с одним из условий, то будет выброшено исключение UnhandledMatchError.
Пример использования:
$food = 'cake';
$return_value = match( $food ){
'apple' => 'Яблоко',
'banana' => 'Банан',
'cake' => 'Торт',
};
echo $return_value; //> Торт
Сравнение со switch:
// Before
switch( $this->type ){
case T_SELECT:
$statement = $this->SelectStatement();
break;
case T_UPDATE:
$statement = $this->UpdateStatement();
break;
case T_DELETE:
$statement = $this->DeleteStatement();
break;
default:
$this->syntaxError( 'SELECT, UPDATE or DELETE' );
break;
}
// After
$statement = match( $this->type ){
T_SELECT => $this->SelectStatement(),
T_UPDATE => $this->UpdateStatement(),
T_DELETE => $this->DeleteStatement(),
default => $this->syntaxError('SELECT, UPDATE or DELETE'),
};
match лениво проверят совпадения и лениво запускает обработку значений.
Левая часть выполняется последовательно, пока не будет найдено подходящее условие.
Правая часть выполняется, только если сработало левое условие (проверка подошла).
Пример:
$result = match ($x) {
foo() => foo_val(), // foo_val() не выполниться, если $x === $this->bar() или $this->baz
$this->bar() => ..., // $this->bar() не будет выполнен, если $x === foo()
$this->baz => beep(), // beep() будет выполнен только если $x === $this->baz
// и т.д.
};
Условия в match могут быть множественными.
В этом случае их следует разделять запятыми. Множественные условия работают по принципу логического ИЛИ и, по сути, являются сокращённой формой для случаев, когда несколько условий должны обрабатываться идентично.
$result = match( $x ){
// Множественное условие:
$a, $b, $c => 5,
// Аналогично трём одиночным:
$a => 5,
$b => 5,
$c => 5,
};
default значение:
$result = match( $x ){
1, 2 => foo(),
3, 4 => bar(),
default => baz(),
};
Использование match для проверки сложных условий
match можно использовать для любых выражений, возвращающих логическое значение. В этом случае в качестве входного параметра передаётся выражение true:
$age = 23;
$result = match( true ){
( $age >= 65 ) => 'пожилой',
( $age >= 25 ) => 'взрослый',
( $age >= 18 ) => 'совершеннолетний',
default => 'ребёнок',
};
echo $result; //> совершеннолетний
Использование match для ветвления в зависимости от содержимого строки
$text = 'Bienvenue chez nous';
$result = match( true ){
str_contains( $text, 'Welcome' ) || str_contains( $text, 'Hello' ) => 'en',
str_contains( $text, 'Bienvenue' ) || str_contains( $text, 'Bonjour' ) => 'fr',
// ...
};
echo $result; //> fr
foo( int|string $val ) — объединение типов
wiki: https://wiki.php.net/rfc/union_types_v2
doc: https://www.php.net/manual/ru/language.types.declarations.php#language.types.declarations.composite.union
Вместо аннотаций PHPDoc для объединённых типов вы можете использовать объявления типа union, которые проверяются во время выполнения.
class Number {
public function __construct(
private int|float $number
) {
}
}
new Number('str'); // TypeError
До этого нужно было писать так:
class Number {
/** @var int|float */
private $number;
/**
* @param float|int $number
*/
public function __construct( $number ) {
$this->number = $number;
}
}
new Number('NaN'); // Нет ошибки
Еще пример
function foo( int|string $val ) {
var_dump( $val );
}
echo foo( 'one' ); // string(3) "one"
echo foo( 1 ); // int(1)
'' + 123 — Fatal Error
RFC: https://wiki.php.net/rfc/invalid_strings_in_arithmetic
Фатал при сложении числа и нечисловой строки
В PHP 8 выражение типа 1 + 'foo' вызывает TypeError, а не silently приводит строку к 0 как раньше.
Это сделано ради безопасности и предсказуемости: теперь баги видны сразу.
Флаг strict_types на это не влияет.
—