Что нового в 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 на это не влияет.

Заметка встроена в: PHP 5.3 - 8.5 — Синтаксис, Новинки