WordPress как на ладони
Очень Удобный и Быстрый Хостинг для сайтов на WordPress. Пользуюсь сам и вам рекомендую!

PHP 5.3 — 8.2 — Синтаксис, Новинки

В этой статье я рассмотрю интересные моменты в развитии синтаксиса PHP - получится своего рода список изменений PHP. Что появилось в версиях PHP 5.3 и выше. Основная цель этой статьи создать некую карту изменений синтаксиса, чтобы можно было быстро освежить в памяти «фишки» синтаксиса PHP. У меня в последнее время частенько возникает необходимость убедиться, что можно в PHP 5.3, а чего нельзя.

При написании плагинов или тем WordPress можно использовать только возможности PHP 5.6. Все что можно в поздних версиях, нужно решать через полифилы или в виде старого синтаксиса.

Оглавление:

PHP 5.3

В PHP 5.3, как и во всей пятой ветке PHP, включена новая машина-интерпретатор скриптов Zend Engine 2.0. Благодаря этому PHP стал работать быстрее примерно на 15-20%.

?: — сокращение тернарного оператора

С PHP 5.3 стало возможным не писать среднюю часть тернарного оператора. Выражение expr1 ?: expr3 возвращает expr1 если expr1 не пустой, и expr3 в противном случае.

Тернарный — состоящий из трёх частей, компонентов.

$a = $expr1 ?: $expr3;
// равносильно записи:
$a = $expr1 ? $expr1 : $expr3;

Пример тернарного оператора:

// полная запись
if ( $a > 100 )
	$result = "Больше";
else
	$result = "Меньше";

// краткая запись
$result = $a > 100 ? "Больше" : "Меньше";

В короткой записи есть еще момент производительности, например:

// полная запись
if ( get_post_meta(25, 'meta_key', 1) )
	echo esc_html( get_post_meta(25, 'meta_key', 1) );
else
	echo 'Мета поля нет';

// короткая запись
echo esc_html( get_post_meta(25, 'meta_key', 1) ?: 'Мета поля нет' );

В полной записи функция get_post_meta() вызывается 2 раза. В короткой один раз, и если она что-то вернула, второму аргументу тернарного оператора сразу передается полученное значение: не нужны дополнительные переменные...

$func = function() use (){ } — анонимные (лямбда) функции

Лямбда-функции еще называют «анонимными функциями», потому что для них не указывается название.

Лямбда-функции представляют собой замыкание (англ. closure) — это особый вид функции, которая определена в теле другой функции и создаётся каждый раз во время её выполнения. Синтаксически это выглядит как функция, находящаяся целиком в теле другой функции. Насколько я понял, любая функция — это замыкание текущего контекста, т.е. контекст не будет очищен пока работает функция. Но если в функции есть лямбда-функция, то она становится замыканием, и если в неё передаются переменные из «верхней» функции, то они не будут очищены, до тех пор пока работает вложенная-функция, даже если «верхняя» функция работу закончила...

В ранних версиях, анонимные функции создавались с помощью функции create_function().

Пример создания анонимной функции для сортировки usort():

$arr = array(3, 2, 5, 6, 1);

usort( $arr, function($a, $b) {
	if ( $a == $b )
		return 0;

	return ( $a > $b ) ? -1 : 1;
});

Еще одна фишка лямбда-функций — это лексическое связывание (использование переменных из текущей области видимости) с помощью оператора use:

$var = 'Превед, Медвед!';
$func = function() use ( $var ) { echo $var; };
$func(); //> Превед, Медвед!

Переменные передаются как значение, но можно передать и ссылку на переменную, указав &:

$var = 'Превед, Медвед!';
$func = function() use ( & $var ) { $var = $var .' Мы в гости!'; };
$func(); // вызовем
echo $var; //> Превед, Медвед! Мы в гости!

method()->var — получение объекта из метода/функции

Это удобно:

$object->method()->method()->method();
$object->method()->method()->member = 5;

В PHP ниже 5.3 писали как-то так:

$tmp = & $object->method();
$tmp = & $tmp->method();
$tmp->method();

<<<'DOC' — поддержка NOWDOC

В php 5.3 можно использовать аналог HEREDOC, который называется NOWDOC. Особенность его в том, что внутри него переменные остаются простым текстом, как если бы мы указали её в строке с одинарными кавычками: 'текст $foo':

$foo = 'Лето';

// HEREDOC был в 5.2
$str = <<<DOC
	Текст с переменной '$foo'
DOC;

echo $str; // Текст с переменной 'Лето'

// NOWDOC появился в 5.3
$str = <<<'DOC'
	Текст с переменной '. $foo .'
DOC;

echo $str; // Текст с переменной '. $foo .'

namespace — поддержка пространств имен

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

Пример ниже должен объяснить почти все, что возможно в пространствах имен. За подробностями идем в официальную документацию.

<?php
#
# Объявлять пространство нужно в самом начале файла содержащего пространство имен,
# т.е. до любого кода, кроме зарезервированного declare(encoding='...');.
# Также ничего не должно выводиться на экран до объявления пространства
# Одно и тоже пространство имен можно определять в разных файлах. Так эти файлы будут относиться к одному пространству

# Объявляем пространство my\name
namespace my\name;

// Динамичное получения названия пространства --------------
$s = __NAMESPACE__; //> my\name
$s = __NAMESPACE__ . '\HELLO'; //> my\name\HELLO
// namespace: есть еще специальное слово namespace, которое используется для динамичного
// получения названия текущего пространства при вызове функций/методов/констант (см. ниже)

// ГЛОБАЛЬНЫЕ функции/классы/константы в нашем пространстве --------------
$s = strlen('hi');           // вызовет my\name\strlen() - если функция есть в нашем пространстве, иначе глобальную функцию strlen()
define('HELLO', 'HI всем');  // добавит константу в глобальное пространство "\HELLO"

# Доступ к глобальным классам/функциям/константам из пространства имен
$a = \strlen('hi'); // вызывает глобальную функцию strlen()
$b = \ABSPATH;      // получает доступ к глобальной константе ABSPATH
$c = new \WP_Query; // создает экземпляр глобального класса WP_Query

// ФУНКЦИЯ в нашем пространстве --------------
function my_func(){ return 'Моя функция'; }

// Вызов
my_func();           //> "Моя функция"
namespace\my_func(); //> "Моя функция"
\my\name\my_func();  //> "Моя функция"
// my\name\my_func();   //> ошибка: будет вызвана функция my\name\my\name\my_func()
						// такой синтаксис можно использовать для доступа к под-пространствам нашего пространства

// ФУНКЦИЯ в нашем пространстве, которая существует в глобальном --------------
function trim( $str ){
	return \trim( $str, '-' ); # если вызвать trim( $str, '-' ), то функция вызовет сама себя...
}

// Вызов
$s = trim('-foo');           // вызов trim() из текущего пространства. Выведет: foo
$s = \my\name\trim('-foo');  // тоже что в строке выше
$s = namespace\trim('-foo'); // тоже что в строке выше

$s = \trim('-foo'); // вызов trim() из глобального пространства. Выведет: -foo

// КОНСТАНТЫ в нашем пространстве --------------
const HELLO = 'HI';                     // добавим константу в текущее пространство
define('my\name\HELLO', 'HI');          // тоже что в строке выше
define(__NAMESPACE__ . '\HELLO', 'HI'); // тоже что в строке выше

// Вызов
$s = HELLO;           //> HI - если константа есть в текущем пространстве, или значение глобальной константы
$s = \my\name\HELLO;  //> HI
$s = namespace\HELLO; //> HI
$s = \HELLO;          //> HI всем - глобальная константа HELLO

// КЛАСС в нашем пространстве --------------
class MyClass {
	function method(){ return 'метод MyClass'; }
	static function static_method(){ return 'статический метод MyClass'; }
}

// Вызов
$a = new MyClass;            // обращение к MyClass из текущего пространства
$a = new \my\name\MyClass;   // тоже что в строке выше

$s = namespace\MyClass::static_method(); //> 'статический метод MyClass' - вызывает статический метод "static_method" класса my\name\MyClass.
$s = $a::static_method();                // тоже что в строке выше

$s = $a->method();                       //> 'метод MyClass' - вызывает метод "method" класса my\name\MyClass
										 // namespace\MyClass->method() - такой вызов метода, вызовет ошибку - syntax error

// ВНЕДРЕНИЕ функций/методов/констант в наше пространстве из других пространств --------------

// ЗАМЕТКА: операторы use можно комбинировать: указывать через запятую
// Например: use other\name\OtherClass as Another, other\name\NSname;

use other\name\OtherClass as Another;
$obj = new Another; // создает объект класса other\name\OtherClass

use other\name; // теперь name = other\name
name\other_func(); // вызывает функцию other\name\other_func();

// импорт глобального класса
use WP_Query;
$a = new WP_Query(); // создаст экземпляр класса WP_Query
					 // без выражения "use WP_Query;" создавался бы экземпляр my\name\WP_Query

// импорт функции (PHP 5.6+)
use function other\name\other_func;
$s = other_func(); //> "Другая Функция" - работа функции other\name\other_func()

// импорт функции под псевдонимом func (PHP 5.6+)
use function other\name\other_func as func;
$s = func();         //> "Другая Функция" - работа функции other\name\other_func()

//const other\name\HELLO2 = 'И снова здрасте!'; // вызовет ошибку синтаксиса, что странно
define('other\name\HELLO2', 'И снова здрасте!');

// импорт константы (PHP 5.6+)
use const other\name\HELLO2;
$s = HELLO2; //> "И снова здрасте!" - содержимое константы other\name\HELLO2

// ЕЩЕ ОДНО ПРОСТРАНСТВО в одном файле --------------
// Подробнее: http://php.net/manual/ru/language.namespaces.definitionmultiple.php

namespace other\name;

class OtherClass {}
function other_func() { return 'Другая Функция'; }

// При описании нескольких пространств в одном файле лучше использовать синтаксис со скобками:
/*
namespace MyProject {
	function connect() {}
}

namespace AnotherProject {
	function connect() {}
}
*/

__DIR__ — новая магическая константа

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

__DIR__ можно заменить:

dirname(__FILE__)

$class::$foo — динамичное указание класса

Это дает динамичный доступ к статическим методам/свойствам класса:

class C {
	static $foo = 'foo';
}

$class = 'C';
echo $class::$foo; //> foo

const — ключевое слово для создания констант вне классов

Сразу пример, где все понятно:

define('SHORTINIT', 'true');

// теперь можно объявить константу и так:
const SHORTINIT = 'true';

В отличие define(), такие константы, должны быть объявлены в самой верхней области видимости, потому что они определяются при компилировании скрипта. Это значит, что их нельзя объявлять внутри функций/циклов/выражений if или try/ catch блоков.

static::method() — статическое связывание

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

class A {
	static function who() {
		echo __CLASS__;
	}

	static function test1() {
		self::who();
	}

	static function test2() {
		static::who(); // статическое связывание
	}
}

class B extends A {
	static function who() {
		echo __CLASS__;
	}
}

echo B::test1(); //> A
echo B::test2(); //> B
echo B::who(); //> B

Подробнее про статическое связывание читайте в документации.

goto hell; — оператор goto

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

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

Также нельзя перейти внутрь любой циклической структуры или оператора switch. Но можно выйти из любой циклической структуры, поэтому «goto» удобен как замена многоуровневых break.

Пример использования goto:

function zayac(){
	$i = 1;
	$out = '';
	start: $out .= ($i > 1 ? '-' : '' ) .$i;

	if( $i++ < 5 ){ goto start; }

	return $out . ' вышел зайчик погулять';
}

echo zayac(); //> 1-2-3-4-5 вышел зайчик погулять

Пример использования goto в цикле:

for( $i=0, $j=50; $i<100; $i++ ) {
	while( $j-- ) {
		if( $j==17 ) goto end;
	}
}
echo "i = $i"; // будет пропущено

end: echo 'j дошло до 17';

__callStatic(), __invoke() — магические методы

__callStatic() — срабатывает, когда вызывается несуществующий метод из статического контекста: Foo::bar():

class A {
	static function __callStatic( $name, $args ){
		return $name .' '. print_r( $args, 1 );
	}
}
echo A::no_matter_what('bar');
/* Выведет:
no_matter_what Array
(
	[0] => bar
)
*/

__invoke() — срабатывает, когда объект выполняется как функция: $obj():

class A {
	function __invoke( $var ){
		var_dump( $var );
	}
}
$obj = new A;
$obj('foo'); //> string(3) "foo"

PHP 5.4

<?= — короткая запись вывода на экран работает всегда

Wiki: https://wiki.php.net/rfc/shortags

Короткая запись о которой идет речь это: <?= вместо <?php echo.

Для работы такой короткой записи вывода на экран в версиях ниже 5.4 нужно было, чтобы опция short_open_tag в php.ini была включена.

Пример длинной и короткой записи:

<a href="#"><?php echo $page ?></a>
<a href="#"><?= $page ?></a>

[1,2] — запись массива, без слова array

wiki: Short syntax for arrays

$a = [ 1, 2, 3, 4 ];
$a = [ 'one' => 1, 'two' => 2, 'three' => 3, 'four' => 4 ];

trait Class {} — примеси (трейты)

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

Несколько примесей можно задавать через запятую:

trait TR_A {
	public $var = 'var';
	function foo() { return 'foo'; }
}

trait TR_B {
	function bar() { return 'bar'; }
}

class A {
	use TR_A, TR_B; // подмешиваем

	function hello() { return 'hello A'; }
}

$A = new A();
echo $A->foo();   // foo
echo $A->bar();   // bar
echo $A->hello(); // hello A
echo $A->var;     // var

class B extends A {
	use TR_A, TR_B;

	function hello() { return 'hello B'; }
}

$B = new B();
echo $B->foo();   // foo
echo $B->bar();   // bar
echo $B->hello(); // hello B

Приоритеты трейтов

При совпадении названий свойств/методов приоритеты расставляются так: текущий класс имеет наивысший приоритет, затем трейт, а затем расширяемый класс. Другими словами: элементы из текущего класса переопределяют элементы в трейте, которые в свою очередь переопределяют унаследованные элементы.

Статический доступ к методу примеси из класса

Когда в класс подмешивается trait, то его методы становятся методами класса, включая статические и статический доступ:

trait A {
	static function func(){ echo 'A'; }
}

class B {
	use A;
}

B::func(); //> A

Подробнее про трейты читайте в документации

foo()[0] — быстрое получение элемента массива

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

$foo = func()[0];
$foo = Class::func()[0];

(new Foo)->method() — доступ к элементу объекта при его создании

$foo = (new Foo)->method();
$foo = (new Foo)->property;
$foo = (new Foo)[0];

// было так
$obj = new Foo;
$foo = $obj->method();

Class::{'foo'}() — динамичное указание метода

Чтобы вызвать статический метод/свойство класса, не нужно запоминать его в отдельную переменную:

class A {
	static function foo() {
		echo "Hello world!";
	}
 }
 $x = "f";
 A::{ $x .'oo' }();

callable — новый тип для аргументов функции/метода

Авто-проверка передаваемых данных в функции/методы, известная как «контроль типа» (typehint), продолжает развиваться и теперь понимает слово callable.

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

Теперь, можно указать еще: callable — значит, что передаваемый аргумент должен быть вызываемым, т.е. удовлетворяет условию is_callable( $arg, false ).

Пример:

function func( callable $callback ){
	return true;
}

func('trim'); //> true

func( function(){} ); //> true

$db = new wpdb();
func( array($db, 'query') ); //> true

func('my_trim'); //> fatal error: Argument 1 passed to func() must be callable, string given

@ — улучшена производительность

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

if( @ $_GET['foo'] ) echo 'OK';

// или так
if( isset($_GET['foo']) && $_GET['foo'] )  echo 'OK';
// раньше так работало быстрее раз в 20, теперь раз в 5

Использовать @ нужно как можно реже и очень осторожно, потому что часто заметки и предупреждения дают понять, что логика кода работает неправильно. Например, у меня бывало что лезу поправить казалось бы безобидный NOTICE, но при анализе выясняется что ошибка появилась из-за неправильной логики кода, которая изменилась в процессе расширения кода...

PHP 5.5

[1,3,4][2], "foobar"[2] — разыменования созданных массивов/строк

echo array(1, 2, 3)[0]; // 1
echo [1,3,4][2];        // 4
echo "foobar"[0]        // f

// это может пригодиться для быстрой генерации:
echo 'abcdefghijk'[ rand(0,10) ]; // получим одну из букв: 'abcdefghijk'

empty() — можно применять к результатам функций и выражений

Раньше empty() мог принимать только переменные, теперь можно передавать сами выражения без необходимости сохранять результат в отдельную переменную:

empty( $object->get() );

list() в foreach

В foreach стало возможным использовать list():

$array = [
	[1, 2],
	[3, 4],
];

foreach( $array as list($a, $b) ){
	echo $a;
	echo $b;
}
// получим: 1234

finally — в конструкции try/catch

Выбрасывать и ловить исключения можно с PHP 5. Такой подход позволяет контролировать выполнение кода, если есть подозрение, что в нем что-то может пойти не так.

А с версии 5.5. в эту конструкцию добавили третий блок finally. Блок finally выполняется всегда после завершается конструкции try/catch. Он выполняется даже когда код try вызвал фатальную ошибку:

try {
	echo 'Тут что-то деламе... ';
	// выбрасываем
	throw new Exception('Лови меня! ');
}
// ловим
catch( Exception $e ){
	echo $e->getMessage(); // выведет: Лови меня!
}
finally {
	echo 'А это выводиться всегда!';
}

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

Пару домонстрационных примеров:

Меньше кода

Допустим, нам нужно выполнить функцию close() в любому случае, было выброшено исключение или нет:

try {
	my_function();
}
catch( Exception $e ){
	// close(); // эта строка нужна была бы без finally
	echo $e->getMessage(); // выведет: Лови меня!
}
finally {
	close();
}
//close(); // эта строка нужна была бы без finally

Больше возможностей

Допустим мы открыли соединение с БД до выполнения кода и есть вероятность что код вызовет ошибку и открытое соединение не закроется, а нам нужно его закрыть в любом случае. finally как раз кстати:

$db = mysqli_connect();

try {
   my_function( $db ); // результат работы функции может вызвать фатальную ошибку...
}
// исключение можно не обрабатывать
finally {
   mysqli_close($db);
}

Class::class — для получение имени класса в пространствах

Появилось ключевое слово class для классов, которое выводит название класса. В обычном режиме нам это не нужно, а вот при работе с пространствами (namespace) — это удобно:

namespace test;
class A {}

echo A::class; //> test\A

yield — создание генераторов

Если говорить простым языком: yield похожа на return, она также возвращает значение, но она не обрывает работу функции, а приостанавливает её до тех пор пока не будет запрошено следующее значение. Благодаря этому создавать генераторы стало удобнее.

Пример генератора:

function generator() {
	for( $i = 1; $i <= 3; $i++ ){
		yield $i; // выброс значения
	}
}

foreach( generator() as $value ){
	echo "$value ";
}
// выведет: '1 2 3 '

Как это работает на самом деле?

yield возвращает специальный объект — Generator. Когда функция generator() вызывается в цикле, например foreach, PHP выполнит код функции до первой встречи слова yield, на котором PHP прервет работу функции, запомнит позицию и выбросит значение (объект Generator). Затем, foreach обработает значение и вызовет метод next() у полученного объекта Generator. PHP снова выполнит код функции generator(), только начнет его не с начала, а с прошлой позиции, и опять, до слова yield, которое опять выбросит объект Generator. Работа цикла прервется тогда, когда функция generator() дойдет до конца (не вернет yield), или если она будет прервана с помощью return;.

Пример передачи генератора в переменную (повторный вызов в этом случае не работает):

function generator() {
	for( $i = 1; $i <= 3; $i++ ){
		yield $i; // выброс значения
	}
}

$gen = generator();

foreach( $gen as $value ){
	echo "$value ";
}
// выведет: '1 2 3 '

// Fatal error: Uncaught Exception: Cannot traverse an already closed generator
foreach( $gen as $value ){
	echo "$value ";
}

Пример генератора который возвращает пару: ключ/значение:

function generator( $input ){
	foreach( explode('.', $input) as $part ){
		list( $num, $name ) = explode(' - ', $part );

		yield $num => trim($name);
	}
}

$input = '1 - один. 2 - два. 3 - три';

foreach( generator( $input ) as $num => $name ){
	echo "$num ($name) ";
}

Кратко о генераторах

  • Не добавляют нового функционала в язык
  • Быстрее
  • Возобновление работы генератора происходит с последнего «выброса» yield
  • В генератор можно отправлять значения и исключения (через метод throw())
  • Генераторы однонаправлены, т.е. нельзя вернуться назад
  • Меньше кода в большинстве случаев, более простые для понимания конструкции

API для хэширования паролей

Теперь PHP из коробки предлагает правильный способ хэшировать пароли. Новый API хэширования паролей предоставляет четыре функции:

  1. password_hash() — используется для хэширования пароля. В WP для этого есть своя функция wp_hash_password().

    $hash = password_hash( $passwod, PASSWORD_DEFAULT );
  2. password_verify() — используется для проверки пароля на соответствие хэшу. В WP для этого есть своя функция wp_check_password().

    if( password_verify( $password, $hash ) ){
    	// Success!
    }
  3. password_needs_rehash() — используется для проверки необходимости создать новый хэш.

  4. password_get_info() — возвращает имя алгоритма хеширования и различные параметры, используемые при хэшировании.

PHP 5.6

const PLUS = 1 + 2; — скалярные выражения в константах/свойствах/аргументах функции

Теперь стало возможным указывать в значения констант примитивные PHP выражения (выражения из скаляров).

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

const ONE = 1;
const TWO = ONE * 2;

class C {
	const THREE = TWO + 1;
	const ONE_THIRD = ONE / self::THREE;
	const SENTENCE = 'Значение THREE равно '. self::THREE;

	public function f( $a = ONE + self::THREE ){
		return $a;
	}
}

echo (new C)->f() .' - '. C::SENTENCE; //> 4 - Значение THREE равно 3

const ARR = ['a', 'b']; — константа может хранить массив

Стало возможным держать в константе массивы:

const ARR = ['a', 'b'];

echo ARR[0]; //> a

func( ...$args ) или func( ...[2, 3] ) — запаковка/распаковка параметров функции

Wiki: Argument Unpacking

Оператор ... еще называют «Splat Оператор», «Scatter operator» или «Spread operator».

Когда мы не знали заранее сколько параметров может получить функция, нам приходилось внутри функции обрабатывать переданные параметры с помощью специальных функций: func_num_args(), func_get_arg(), func_get_args().

Запаковка передаваемых параметров в одну переменную при декларации функции

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

function sum( ...$numbers ){
	$plus = 0;
	foreach( $numbers as $n ){
		$plus += $n;
	}
	return $plus;
}

echo sum( 1, 2, 3 ); //> 6

Еще пример:

function func( ...$numbers ){
	print_r( $numbers, 1 );
}

func( 1, 2, 3 );
/*
Получим:
Array
(
	[0] => 1
	[1] => 2
	[2] => 3
)
*/
Распаковка передаваемых параметров при вызове функции

Теперь с помощью splat оператора ..., можно указать параметры функции сразу из значений массива:

function plus( $a, $b, $c ){
	return $a + $b + $c;
}

$array = [ 2, 3 ];
echo plus( 1, ...$array ); //> 6

// или так
echo plus( 1, ...[ 2, 3 ] ); //> 6
Замена функции call_user_func_array()

Теперь call_user_func_array( $callback, $param_arr ), которая обычно не самая быстрая, можно заменить так:

$params = [ 1, 2, 3 ];
$callback( ...$params );
Распаковка в массив

Ассоциативные массивы распаковывать нельзя.

// $arr1 = [ 'key' => 1 ] вызовет ошибку Fatal error
$arr1 = [ 'foo', 100 ];
$arr2 = [ 'val', 200 ];
$arr = [ 1,89, 'str', ...$arr1, 22 ,...$arr2, 456, 52 ];

print_r( $arr );

/*
Array
(
	[0] => 1
	[1] => 89
	[2] => str
	[3] => foo
	[4] => 100
	[5] => 22
	[6] => val
	[7] => 200
	[8] => 456
	[9] => 52
)
*/

** — оператор возведения в степень

До php 5.6, чтобы возвести число в степень нужно было использовать функцию pow(2,2);, а теперь есть оператор **:

// пример 1
echo $a = 2 ** 2; //> 4

// пример 2
$a = 2;
echo $a **= 2; //> 4

// пример 3
echo $a = 2 ** 3 ** 2; //> 512 = 2^9

use function и use const — импорт функций и констант в пространство

Теперь стало возможным при помощью ключевого слова use подключать функции или константы другого пространства в наше:

namespace our\space {
	const FOO = 42;
	function func() { echo __FUNCTION__; }
}

namespace my\space {
	use const our\space\FOO;
	use function our\space\func;

	echo FOO .' - '. func(); //> 42 - our\space\func
}

Куда делся PHP 6?

Умер не родившись... В ядро PHP 6 планировали внедрить полную поддержку юникода, но затея оказалась слишком амбициозной, а объем работ слишком велик. К тому моменту, когда это стало понятно, о PHP 6 уже было написано не мало статей. Чтобы не было путаницы, из-за того что новая версия стала преследовать совсем другие цели (производительность) и сильно отличалась по концепции от PHP 6, было решено пропустить PHP 6. Еще одной причиной стало наличие весомого количества недоделанного кода в репозитории PHP, который решили не трогать, чтобы тот в ответ тоже никого не трогал...

PHP 7

3 декабря 2015 года было объявлено о выходе PHP 7. Новая версия основывается на экспериментальной ветке PHP, которая изначально называлась phpng (PHPNextGeneration - следующее поколение), и разрабатывалась с упором на увеличение производительности и уменьшение потребления памяти.

Самой важной новинкой стало изменение ядра интерпретатора: теперь он называется PHPNG (Next Generation). Благодаря PHPNG удалось увеличить скорость обработки скриптов почти в двое по сравнению с PHP 5.x. Так же появился более эффективный менеджер памяти.

Прирост в скорости на практике хорошо виден на этой картинке. А для WordPress прирост в скорости выглядит так:

php7-benchmark

Подробнее смотрите в тестах PHP 7

Синтаксические новинки PHP 7:

$a ?? '' — isset и получение значения

Wiki: Null Coalesce Operator

Новый оператор слияния с NULL (NULL coalescing operator) ?? — это сокращение проверки isset и получения значения, если проверка пройдена.

Такая проверка часто была нужна в тернарном операторе ?::

// Получит значение $_GET['foo'], если переменная установлена или не пустая, иначе получит 'default'
$foo = $_GET['foo'] ?? 'default';

// Запись равносильна этой
$foo = isset($_GET['foo']) ? $_GET['foo'] : 'default';
// или этой
$foo = @ $_GET['foo'] ?: 'default';

// удобная проверка при получении $_GET параметра
if( $_GET['foo'] ?? 0 ){ }
// раньше писали так
if( isset($_GET['foo']) && $_GET['foo'] ){ }

Так же, проверять можно по цепочке:

$foo = $_GET['foo'] ?? $_POST['foo'] ?? 'default';
// вернет: $_GET['foo'], если его нет, то $_POST['foo'], если нет, то 'default'

$a <=> $b — три сравнения сразу: больше, равно, меньше

Wiki: Combined Comparison (Spaceship) Operator

Новый оператор сравнения <=> — «spaceship operator» (космический корабль). Сравнивает 2 переменные и возвращает результат сравнения в виде числа:

  • -1 — если в сравнении подходит первый символ оператора <
  • 0 — подходит второй символ =
  • 1 — подходит третий символ >
// Числа
echo 1 <=> 1; // 0
echo 1 <=> 2; // -1
echo 2 <=> 1; // 1

// Дробные числа
echo 1.5 <=> 1.5; // 0
echo 1.5 <=> 2.5; // -1
echo 2.5 <=> 1.5; // 1

// Строки
echo "a" <=> "a"; // 0
echo "a" <=> "b"; // -1
echo "b" <=> "a"; // 1
Оператор Эквивалент <=>
$a < $b ($a <=> $b) === -1
$a <= $b ($a <=> $b) === -1 || ($a <=> $b) === 0
$a == $b ($a <=> $b) === 0
$a != $b ($a <=> $b) !== 0
$a >= $b ($a <=> $b) === 1 || ($a <=> $b) === 0
$a > $b ($a <=> $b) === 1

Удобен для использования в usort():

usort( $products, function( $product1, $product2 ){
	return $product1->price() <=> $product2->price();
} );

Можно использовать такой хак, чтобы не писать вложенных тернарных операторов:

$count = 1;

// это
$class = ( $count === 0 ) ? 'null' : ( $count > 0 ? 'plus' : 'minus' ); // plus

// можно записать так
$class = [ 'minus', 'null', 'plus' ][ ( $count <=> 0 ) + 1 ]; // plus

define( 'FOO', [1,2] ); — массив в define константе

Константы могут содержать массивы еще с PHP 5.6. Но тогда их можно было передавать только через ключевое слово const. Теперь их можно указывать еще и через define().

define('ANIMALS', ['dog', 'cat', 'bird']);

echo ANIMALS[2]; //> bird

use name\space\{A, B, C as c}; — группировка импорта

Wiki: Group Use Declarations

Теперь для краткой записи, импорт данных в наше пространство можно группировать:

// PHP 7

use some\namespace\{ ClassA, ClassB, ClassC as C };
use function some\namespace\{ fn_a, fn_b, fn_c };
use const some\namespace\{ СonstA, ConstB, ConstC };

// тоже самое до PHP 7

use some\namespace\ClassA;
use some\namespace\ClassB;
use some\namespace\ClassC as C;

use function some\namespace\fn_a;
use function some\namespace\fn_b;
use function some\namespace\fn_c;

use const some\namespace\ConstA;
use const some\namespace\ConstB;
use const some\namespace\ConstC;

int, float, bool — новые типы параметров функции/метода

Авто-проверка типа передаваемых данных в функции/методы, известная как «контроль типа» (typehint), продолжает развиваться и теперь понимает скаляры: int, float, bool, string. Раньше понимались только типы: array, имя класса или callable (с версии 5.4).

Пример:

function foo( int $a, bool $b, callable $с, array $d, WP_Post $e ) {
	return var_dump( $a, $b, $c, $d, $e );
}

foo( 1, true, 'trim', array(1), get_post(1) );

/* выведет:
int(1)
bool(true)
NULL
array(1) { [0]=> int(1) }
object(WP_Post)#2660 (24) { ...данные объекта... }
*/

// если указать неверный тип:
foo( 'foo', true, 'trim', array(1), get_post(1) );
// Получим ошибку Fatal error: Argument 1 passed to A::foo() must be of the type integer, string given

Режим строгой типизации

Если указан тип int и передать строку '123' то проверка все равно будет пройдена, и php превратить строку в число.

function func( int $num ){
	var_dump( $num );
}
func('123'); //> int(123)

Но что, если нужно получать именно число 123? Для этого можно включить режим строгой типизации, поместив в самое начало файла такую строку:

declare(strict_types=1);

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

Заметка: если строгая типизация указана в файле X, но не указана в файле Y и в файле Y вызывается функция из файла X. То вызов такой функции не будет подвержен строгой типизации!

Читайте по типизации статью на Хабре и вот еще интересная статья.

int, float, bool, array — типы возврата функции/метода

Указывать принимаемый тип, можно еще с версии PHP 5.3. А вот указать какой тип функция/метод должна вернуть доступно только с версии PHP 7. Тут понимаются все типы: string, int, float, bool, array, callable, self (в методах), parent (в методах) , Closure, имя класса, имя интерфейса.

Синтаксис:

function func( $var ): int{ /* код функции */ }
function func( $var ): string{  }
function func( $var ): float{  }
function func( $var ): bool{  }
function func( $var ): array{  }
function func( $var ): callable{  }
function func( $var ): Closure{  }
function func( $var ): WP_Post{  } // может вернуть только объект класса WP_Post

class A extends B {
	function func( $var ): self{ }
	function func( $var ): parent{ }
}

Рабочие примеры:

// Пример 1:
function func( $var ): int {
	return $var;
}
echo func( 123 );    //> 123
echo func( 'asfd' ); //> вызовет ошибку: Fatal error: Uncaught TypeError: Return value of func() must be of the type integer, string returned

// Пример 2: Closure
function func(): Closure {
	return function( $var ){ return $var .' + 2 = 3'; };
}
echo func()( 1 ); //> 1 + 2 = 3

Возвращаемые типы при наследовании методов класса

При наследовании в классах, дочерние методы должны иметь такие же возвращаемые типы как и в родительском классе/интерфейсе:

class A {
	function func() : int {
		return 123;
	}
}
class B extends A {
	function func() : string {
		return '123';
	}
	// такое объявление функции вызовет ошибку:
	// Fatal error: Declaration of B::func(): string must be compatible with A::func(): int
	// т.е. тип int должен совпадать!
}

Навороченный пример того, как можно писать в PHP 7

Тут сразу несколько новинок:

  1. принимаемый и возвращаемый тип;
  2. объединение и распаковка параметров с помощью ...;
  3. пример создания анонимной функции с указанием возвращаемого типа данных.
function arraysSum( array ...$arrays ): array {
	return array_map( function( array $array ): int {
		return array_sum( $array );
	}, $arrays );
}

print_r(  arraysSum( [1,2,3], [4,5,6], [7,8,9] )  );
/*
Выведет:
Array
(
	[0] => 6
	[1] => 15
	[2] => 24
)
*/

foo()(), $a::$b::$c, $$foo->bar — единый синтаксис: СЛЕВА НАПРАВО

Важная новинка! Теперь обращения к сложносочиненным переменным разбираются последовательно СЛЕВА НАПРАВО.

Примеры новых возможностей:

// можно не указывать комбинирующие скобки
$foo()['bar']()
[ $obj1, $obj2 ][0]->prop
getStr()[0]

// поддерживает вложенность ::
$foo['bar']::$baz   // > ( $foo['bar'] )::$baz
$foo::$bar::$baz    // > ( $foo::$bar )::$baz
$foo->bar()::baz()  // > ( $foo->bar() )::$baz

// поддерживает вложенные ()
foo()()        // вызывает результат foo() → ( foo() )()
$foo->bar()()  // > ( $foo->bar() )()
Foo::bar()()   // > ( Foo::bar() )()
$foo()()       // > ( $foo() )()

// Операторы над выражениями заключенными в ()
( function() { ... } )() // IIFE синтаксис JS
( $obj->closure )()
// и т.д.
(...)['foo']
(...)->foo
(...)->foo()
(...)::$foo
(...)::foo()
(...)()

// все операции по разименованию скаляров
"string"->toLower()
[ $obj, 'method' ]()
'Foo'::$bar

Примеры разницы старого и нового распознавания:

// строка             // старое понимание       // новое понимание
$$foo['bar']['baz']   ${ $foo['bar']['baz'] }   ( $$foo )['bar']['baz']
$foo->$bar['baz']     $foo->{ $bar['baz'] }     ( $foo->$bar )['baz']
$foo->$bar['baz']()   $foo->{ $bar['baz'] }()   ( $foo->$bar )['baz']()
Foo::$bar['baz']()    Foo::{ $bar['baz'] }()    ( Foo::$bar )['baz']()

Старый код написанный с использованием {} для обработки переменных возможно не будет работать в новой версии PHP 7.

foreach — изменена логика работы

wiki: Fix "foreach" behavior

Теперь foreach не переключает автоматически внутренний указатель перебираемого массива, т.е. next() не работает автоматически:

$arr = [ 1, 2, 3, 4 ];
foreach( $arr as $val ){
	echo key( $arr ) . ' ';
}
// PHP 5.6: 1 1 1 1
// PHP 7.0: 0 0 0 0

$arr = [ 1, 2, 3, 4 ];
foreach( $arr as & $val ){
	echo key( $arr ) . ' ';
}
// PHP 5.6: 1 2 3
// PHP 7.0: 0 0 0 0

Еще пример магического поведения в старых версиях:

$a = [1,2,3];          foreach( $a as $v ) { echo current($a) . " "; }
$a = [1,2,3]; $b = $a; foreach( $a as $v ) { echo current($a) . " "; }

// PHP 5.6: 2 2 2 1 1 1
// PHP 7.0: 1 1 1 1 1 1

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

$arr = [ 1, 2, 3, 4 ];
foreach( $arr as $val ){
	echo "$val ";
	unset( $arr[1] );
}

// PHP 5.6: 1 2 3 4
// PHP 7.0: 1 2 3 4

ОДНАКО, если значение массива передается по ссылке, то foreach всегда работает с исходным массивом, т.е. изменение массива внутри foreach изменит и результат foreach:

$arr = [ 1, 2, 3, 4 ];
foreach( $arr as & $val ){
	echo "$val ";
	unset( $arr[1] );
}

// PHP 5.6: 1 3 4
// PHP 7.0: 1 3 4

$class = new class{} — анонимные классы

Wiki: Anonymous Classes

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

$class = new class {
	public function echo( $msg ){
		echo $msg;
	}
};
$class->echo('Привет!'); // выведет на экран: "Привет!"

Расширение классов работает как и ожидается:

class Foo {}

$child = new class extends Foo {};

var_dump( $child instanceof Foo ); //> true

Использование треитов:

trait Foo {
	public function method() {
	  return "bar";
	}
}

$class = new class {
	use Foo;
};

var_dump( $class->method() ); //> string(3) "bar"

Подробнее про анонимные классы читайте в документации.

yield ... return 99; — возврат выражений в генераторах

Wiki: Generator Return Expressions

Функции-генераторы появились в PHP 5.5. Но там можно было использовать return, только чтобы прервать работу генератора. Теперь return может возвращать выражение (значение/массив/другой генератор), а не только NULL. Но сделать это можно только в конце работы генератора.

Получить возвращенное значение можно методом getReturn(), но только по завершении работы генератора.

Возможность явно вернуть последнее значение упрощает работу с генераторами:
теперь не нужно проверять является ли значение последним, а просто вызываем getReturn().

function gen() {
	yield 1;
	yield 2;

	return 3;
}

$gen = gen();

// если генератор еще ничего не вернул, то вызов такой строки
// echo $gen->getReturn();
// вызовет ошибку: Fatal error: Uncaught Exception: Cannot get return value of a generator that hasn't returned

foreach( $gen as $val ) {
	echo $val;
}

echo $gen->getReturn();

// результат работы этого кода выведет на экран: 123

yield from func() — делегирование генераторов

Wiki: Generator Delegation

Позволяет разбить сложный генератор на несколько простых.

Для этого используется новый синтаксис: yield from <expr>, где <expr> может быть значением (скаляром), массивом или другим генератором.

<expr> будет работать до тех пор, пока возвращает данные, затем выполнение продолжится в генераторе откуда <expr> был вызван. Смотрите пример:

function gen() {
	yield 1;
	yield from gen2();
	yield 4;
}

function gen2(){
	yield 2;
	yield 3;
}

$gen = gen();

foreach ( $gen as $val ) {
	echo $val;
}

// результат работы этого кода: 1234

Пример с массивом:

function g() {
  yield 1;
  yield from [2, 3, 4];
  yield 5;
}

$g = g();
foreach ( $g as $yielded ) {
	echo $yielded;
}

// выведет: 12345

Пример с return из дочернего генератора:

function gen() {
  yield 1;
  $sub_gen = yield from sub_gen();
  yield 4;

  return $sub_gen;
}

function sub_gen() {
  yield 2;
  yield 3;

  return 42;
}

$gen = gen();
foreach( $gen as $val ) {
	echo $val;
}
echo ' - '. $gen->getReturn();

// выведет: 1234 - 42

Остальные новинки PHP 7.0

  1. Синтаксис конструкторов в стиле PHP 4 (имя метода конструктора совпадает с именем класса) теперь считается устаревшим.

  2. Статичные вызовы :: нестатичных методов теперь считаются устаревшими.

  3. list() — изменение поведения. В PHP 5, list() устанавливал значения начиная с правого крайнего значения указанного массива, в PHP 7 параметры устанавливаются начиная с левого крайнего значения массива. Так же в PHP 5 list() умела разбивать строки на символы, в PHP 7 не работает со строками вообще...

    // Пример 1: обратное чтение
    // Если используются обычные переменные, то разницы нет
    list( $a, $b, $c ) = ['apple', 'bannana', 'cherry', 'damson'];
    var_dump( $a, $b, $c ); // php5 и php7 вернут: apple bannana cherry
    
    // А вот если устанавливаются элементы массива, то порядок будет отличаться
    $arr = [];
    list( $arr['a'], $arr['b'], $arr['c'] ) = ['apple', 'bannana', 'cherry', 'damson'];
    print_r( $arr );
    /*
    PHP 7
    Array
    (
    	[a] => apple
    	[b] => bannana
    	[c] => cherry
    )
    
    PHP 5
    Array
    (
    	[c] => cherry
    	[b] => bannana
    	[a] => apple
    )
    */
    
    // Пример 2: разбивание строк
    $str = 'ab';
    list( $a, $b ) = $str;
    var_dump( $a, $b );
    // В PHP 7: NULL NULL
    // В PHP 5: string(1) "a" string(1) "b"
  4. Поддержка юникод управляющих (escape-) последовательностей. Т.е. в строках "" и heredoc можно использовать конструкцию \uXXXX для создания юникод символа. Вот так:

    echo "\u{1F602}"; //> ?

    Wiki: Unicode Codepoint Escape Syntax

  5. Класс IntlChar. Cодержит методы и константы для работы с юникодом.

    printf('%x', IntlChar::CODEPOINT_MAX); // 10ffff
    
    echo IntlChar::ord('@'); //> 64
    echo IntlChar::chr( 64 ); //> @
    
    echo "\u{1F602}"; //> ?
    echo IntlChar::ord("\u{1F602}"); //> 128514
    echo IntlChar::chr( 128514 ); //> ?
  6. Функция intdiv() — делит 2 числа и возвращает только целую часть от деления:

    echo intdiv(10, 3); //> 3
    echo intdiv(5, 2); //> 2
  7. session_start() умеет получать параметры (стандартные настройки сессий из php.ini):

    session_start(['cache_limiter' => 'private']);
  8. Функция preg_replace_callback_array() — альтернатива preg_replace_callback(). Позволяет передать в качестве обратной функции - массив ['/regex'/ => callback, ...]:

    $str = 'a1a2a3';
    $array = [
    	'~[0-9]~' => function ( $m ){   return $m[0] * 2;   },
    	'~a~' => function ( $m ){   return $m[0] . '-';   }
    ];
    
    echo preg_replace_callback_array( $array, $str ); //> a-2a-4a-6
  9. Можно использовать глобальные ключевые слова в названиях методов. Т.е. раньше нельзя было назвать метод словами: with/new/for/foreach/... — это приводило к ошибке. Теперь можно:
    Class::new('Project Name');
    $class->for('purpose here');

PHP 7.1

?string — Обнуляемый тип (и null тип)

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

function testReturn(): ?string {
	return 'elePHPant';
}

var_dump( testReturn() ); // string(10) "elePHPant"

function testReturn(): ?string {
	return null;
}

var_dump( testReturn() ); // NULL

function test( ?string $name ) {
	var_dump( $name );
}

test('elePHPant'); // string(10) "elePHPant"
test(null);        // NULL
test();            // Uncaught Error: Too few arguments to function test(), 0 passed in...

void — возвращаемый тип

Теперь функции и методы, которые не должны ничего возвращать, можно помечать возвращаемым типом void. Оператор return при этом должен отсутствовать или должен быть пустым - return;. Вызов return null; вызовет ошибку.

function someMethod(): void {
	// работает если return отсутствует
	// работает с return;
	// не работает если return null;
	// не работает если return 123;
}

['key'=>$var] = ['key'=>'Значение'] — Деструктурирование массивов

Wiki: Destructuring assignment from an array to variables (short list syntax).

Можно использовать синтаксис [] = [] для деструктуризации массивов и присвоения значений массива переменным - альтернатива функции list().

Пример с индексным массивом:

list( $one, $two ) = [ 'один', 'два' ]; // list style

[ $one, $two ] = [ 'один', 'два' ];     // [] style

echo "$one, $two"; //> один, два

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

$person = [ 'first' => 'Rasmus', 'last' => 'Lerdorf', 'manager' => true ];

// Порядок извлечения не важен
[ 'last' => $lastname, 'first' => $firstname ] = $person;

echo "$lastname, $firstname"; //> Lerdorf, Rasmus

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

[ [$a, $b], [$c, $d] ] = [ [1, 2], [3, 4] ];
$options = [ 'enabled' => true, 'compression' => ['algo' => 'gzip'] ];

[
	'enabled' => $enabled,
	'compression' => [
		'algo' => $algo,
	]
] = $options;

Деструктуризация в foreach.

Синтаксис list() разрешен не только в левой части операции присваивания, но и в качестве переменной в цикле foreach. Новый синтаксис [] так же работает в этом случае:

$persons = [
	[ 'first' => 'Rasmus', 'last' => 'Lerdorf' ],
	[ 'first' => 'Egor',   'last' => 'Drujo' ],
	[ 'first' => 'Telia',  'last' => 'Masterok' ],
];

foreach( $persons as [ 'first' => $first, 'last' => $last ] ){

	echo "$first, $last";
}

list( 'id'=>$id ) = $data — поддержка ключей в list()

Теперь оператор list() поддерживает ключи. Это позволяет деструктурировать массивы с нечисловыми или непоследовательными ключами.

$data = [
	["id" => 1, "name" => 'Tom'],
	["id" => 2, "name" => 'Fred'],
];

// стиль list()
list("id" => $id1, "name" => $name1) = $data[0];

// стиль []
[ "id" => $id1, "name" => $name1 ] = $data[0];

// стиль list()
foreach ( $data as list("id" => $id, "name" => $name) ) {
	// logic here with $id and $name
}

// стиль []
foreach ( $data as ["id" => $id, "name" => $name] ) {
	// logic here with $id and $name
}

Closure::fromCallable() — новый статический метод Closure

В класс Closure добавлен новый статический метод для возможности легко преобразовать callable в объекты типа Closure.

class Test {

	public function exposeFunction(){
		return Closure::fromCallable( [$this, 'privateFunction'] );
	}

	private function privateFunction( $param ){
		var_dump( $param );
	}

}

$privFunc = (new Test)->exposeFunction();

$privFunc('значение'); //> string(16) "значение"

private const — Область видимости констант в классах

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

class ConstClass {
	const CONST_ONE = 1; // public
	public const CONST_TWO = 2;
	protected const CONST_THREE = 3;
	private const CONST_FOUR = 4;
}

iterable — новый псевдо-тип

Wiki: RFC: Iterable

Введен новый тип iterable для передаваемых/возвращаемых значений. Может использоваться при передаче массивов или объектов, которые соответствуют интерфейсу Traversable.

function myfunc( iterable $data ){
	foreach( $data as $k => $v ){
		echo $k, ':', $v, PHP_EOL;
	}
}

// массив
myfunc([10, 20, 30]); // 0:10 1:20 2:30

// объект
myfunc( new SplFixedArray(3) ) // 0: 1: 2:

// генератор
function myGen(){
	yield 10;
	yield 20;
	yield 30;
}
myfunc( myGen() ); // 0:10 1:20 2:30

?int = null — тип передаваемых/возвращаемых значений

Wiki: Nullable Types

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

В PHP 7.1 для разрешения null перед типом параметра указывается ?:

function myfunc( ?int $i ) : ?int {
  var_dump($a);
  return $a;
}

myfunc( 20 );   // int(20)
myfunc( null ); // null
myfunc();       // Ошибка: Uncaught Error: Too few arguments to function name(), 0 passed

$string[-1] — отрицательное значение смещения в строках

Добавлена возможность использовать отрицательное значение для смещения в строках

echo $string[-1]; // последний символ

catch (First | Second $e) — обработка нескольких исключений в одном блоке catch

В блоке catch теперь можно обрабатывать несколько исключений, перечисляя их через символ вертикальной черты (|). Это может быть полезно, если различные исключения обрабатываются одинаково.

try {
	// Какой то код
}
catch ( FirstException | SecondException $e ) {
	// Обрабатываем оба исключения
}

Заметки по PHP 7.1

PHP движется в сторону строгой типизации данных и при переходе на 7.1 я столкнулся с ФАТАЛЬНОЙ ошибкой. И мне это показалось очень странным. Приведу пример:

$foo = '';

$foo['bar'] = 'мир';   // Warning: Illegal string offset 'bar'

$foo['bar'][] = 'мир'; // Fatal error: Uncaught Error: Cannot use string offset as an array
					   // фатальная ошибка: нельзя использовать отступ строки как массив...

При Warning PHP еще работает, а дальше уже нет! А еще в 7.0 код просто работал, даже без предупреждений и нотисов... Похоже на недоработку в PHP 7.1.

PHP 7.2

Закрывающая запятая для любых списков

wiki: https://wiki.php.net/rfc/list-syntax-trailing-commas

В следующих списках допускается использование запятых в конце:

  • Сгруппированные пространства имен.
  • Аргументы функций/методов (объявления и вызовы).
  • Реализации интерфейсов в классе.
  • Реализации признаков в классе.
  • Списки членов класса.
  • Наследование переменных из родительской области видимости в анонимных функциях.
// Arrays (already possible)
$array = [1, 2, 3,];

// Grouped namepaces
use Foo\Bar\{ Foo, Bar, Baz, };

// Function/method arguments (call)
fooCall($arg1, $arg2, $arg3,);

class Foo implements
	// Interface implementations on a class
	FooInterface,
	BarInterface,
	BazInterface,
{
	// Trait implementations on a class
	use
		FooTrait,
		BarTrait,
		BazTrait,
	;

	// Class member lists
	const
		A = 1010,
		B = 1021,
		C = 1032,
		D = 1043,
	;
	protected
		$a = 'foo',
		$b = 'bar',
		$c = 'baz',
	;
	private
		$blah,
	;

	// Function/method arguments (declaration)
	function something(FooBarBazInterface $in, FooBarBazInterface $out,) : bool
	{
	}
}

// Inheriting variables from the parent scope in anonymous functions
$foo = function ($bar) use (
	$a,
	$b,
	$c,
) {
	// . . .
};

PHP 7.3

Heredoc и Nowdoc ― Улучшен синтаксис

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

Закрывающий маркер теперь может иметь отступы TAB или Пробелы.

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

Поэтому были внесены два изменения:

Разрешить отступ для закрывающего маркера.
// новый синтаксис:
class foo {
	public $bar = <<<EOT
	bar
	EOT;
}

// раньше приходилось писать так:
<?php
class foo {
	public $bar = <<<EOT
bar
EOT;
}

Еще примеры как это работает с отступами:

// no indentation
echo <<<END
	 b
	c
END;
/*
	 b
	c
*/

// 4 spaces of indentation
echo <<<END
	 b
	c
	END;
/*
 b
c
*/
Убрать требование новой строки после закрывающего маркера.

Стало возможно писать так:

stringManipulator(<<<END
  b
 c
END);

$values = [<<<END
b
c
END, 'd e f'];

Было так:

stringManipulator(<<<END
   a
  b
 c
END
);

$values = [<<<END
a
b
c
END
, 'd e f'];

list( &$a ) — cсылки в list()

В PHP уже давно существует list() и ссылки. Однако до php 7.3 не было возможности использовать присваивание ссылок с помощью list().

$array = [ 1, 2 ];
list( $a, &$b ) = $array;

// или через деструктуризацию
[ $a, &$b ] = $array;

Конечно, это работает так же, как обычно работает list(), поэтому вы можете использовать его с вложенным list() и пропускать значения:

$array = [ 1, 2, 3, [3, 4] ];
list( &$a, $b,, list( &$c, $d ) ) = $array;

Он также работает с функцией foreach():

$array = [ [1, 2], [3, 4] ];
foreach ( $array as list( &$a, $b ) ) {
	$a = 7;
}
var_dump( $array )
/*
array(2) {
  [0]=> array(2) {
	[0]=> int(7)
	[1]=> int(2)
  }
  [1]=> array(2) {
	[0]=> &int(7)
	[1]=>  int(4)
  }
}
*/

is_countable() — функция

Этот RFC предлагает новую функцию типа, которая возвращает true, если заданное значение является типом массива или экземпляром интерфейса Countable.

Нужно было проверять так:

if ( is_array($foo) || $foo instanceof Countable ) {
	// $foo is countable
}

Теперь можно так:

if ( is_countable($foo) ) {
	// $foo is countable
}

Еще примеры:

var_dump(is_countable([1, 2, 3])); // bool(true)
var_dump(is_countable(new ArrayIterator(['foo', 'bar', 'baz']))); // bool(true)
var_dump(is_countable(new ArrayIterator())); // bool(true)
var_dump(is_countable(new stdClass())); // bool(false)
$foo = [ '', [] ];

if ( is_countable( $foo ) ) {
	var_dump( count( $foo ) ); // int(2)
}

array_(key|value)_(first|last)() — новые функции

Поскольку массивы являются мощной структурой данных, в некоторых случаях удобно получить первый или последний ключ/значение массива без обходного пути. Для выполнения этой задачи данный RFC добавляет в ядро четыре функции:

$key = array_key_first( $array );
$key = array_key_last( $array );
$value = array_value_first( $array );
$value = array_value_last( $array );

PHP 7.4

Сравнение результатов теста производительности 7.4 и более старых версий PHP.

[ ...$arr ] — распаковка массива внутри массива

Wiki: Spread operator for array

Оператор ... еще называют «Splat Оператор», «Scatter operator» или «Spread operator».

Распаковка работает с версии PHP 5.6. А с выходом 7.4, мы можем использовать её в массивах.

$parts  = [ 'apple', 'pear' ];
$fruits = [ 'banana', 'orange', ...$parts, 'watermelon' ];
// [ 'banana', 'orange', 'apple', 'pear', 'watermelon' ];

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

Оператор Spread работает как для обычного синтаксиса массива array(), так и для короткого [].

$arr1 = [ 1, 2, 3 ];
$arr2 = [ ...$arr1 ];    // [1, 2, 3]
$arr3 = [ 0, ...$arr1 ]; // [0, 1, 2, 3]
$arr4 = array( ...$arr1, ...$arr2, 111 ); // [1, 2, 3, 1, 2, 3, 111]
$arr5 = [ ...$arr1, ...$arr1 ]; // [1, 2, 3, 1, 2, 3]

Можно распаковать вызов функции, которая возвращает массив.

function getArr(){
	return [ 'a', 'b' ];
}
$arr6 = [ ...getArr(), 'c' ]; // ['a', 'b', 'c']

$arr7 = [ ...new ArrayIterator(['a', 'b', 'c']) ]; // ['a', 'b', 'c']

function arrGen(){
	for( $i = 11; $i < 15; $i++ ){
		yield $i;
	}
}
$arr8 = [ ...arrGen() ]; // [11, 12, 13, 14]

ВАЖНО: Строковые ключи массива не поддерживаются!

Чтобы сохранить совместимость с распаковкой аргументов, ключи в виде строк не поддерживаются. При обнаружении строкового ключа будет выброшена ошибка (recoverable error).

Распаковка по ссылке

Невозможно распаковать массив по ссылке.

$arr1 = [ 1, 2, 3] ;
$arr2 = [ ...&$arr1 ]; // ОШИБКА: неверный синтаксис

Однако если элементы в распаковываемом массиве хранятся по ссылке, то они также будут храниться по ссылке в новом массиве.

$one = 1;
$arr1 = [ & $one, 2, 3 ];
$arr2 = [ 0, ...$arr1 ];
var_dump( $arr2 );
/*
array(4) {
  [0]=>    int(0)
  [1]=>  & int(1)
  [2]=>    int(2)
  [3]=>    int(3)
}
*/
Преимущества над array_merge()
  1. Оператор Spread должен иметь лучшую производительность, чем array_merge(). Потому что он является структурой языка, а array_merge() - вызовом функции, а также для постоянных массивов может быть выполнена оптимизация времени компиляции.

  2. array_merge() поддерживает только массивы, а ... также поддерживает Traversable объекты.

    // когда получаем итераторы
    array_merge( iterator_to_array($iter1), iterator_to_array($iter2) )
    
    // когда может получить итератор или массив
    array_merge(
      is_array($iter1) ? $iter1 : iterator_to_array($iter1),
      is_array($iter2) ? $iter2 : iterator_to_array($iter2)
    )
    
    // учитываются все варианты
    [ ...$iter1, ...$iter2 ]
    

public int $id — типизация для свойств класса

Добавлена поддержка типов для свойств класса. Например:

class User {
	public int $id;
	public string $name;
}

Теперь $user->id может быть только целым числом, а $user->name могут быть присвоены только строки.

Доп информация по этой ссылке RFC: https://wiki.php.net/rfc/typed_properties_v2

fn( $x ) => $x — стрелочные функции

Добавлена поддержка стрелочных функций с неявной привязкой к области видимости по значению. Например:

$factor = 10;
$nums = array_map( fn( $num ) => $num * $factor, $nums );

В качестве еще одного примера полезности такого подхода рассмотрим вариант как писался раньше и как можно писать теперь:

function array_values_from_keys( $arr, $keys ) {
	return array_map( function ($x) use ($arr) { return $arr[$x]; }, $keys );
}

Операция с передачей параметра $arr, выполняемая замыканием, тривиальна, но она несколько теряется в синтаксисе. Стрелочные функции могут сократить эту функцию до следующей:

function array_values_from_keys( $arr, $keys ) {
	return array_map( fn( $x ) => $arr[$x], $keys );
}
Синтаксис
fn( array $x ) => $x;
fn(): int => $x;
fn( $x = 42 ) => $x;
fn( & $x ) => $x;
fn&( $x ) => $x;
fn( $x, ...$rest ) => $rest;

Подробнее на RFC: https://wiki.php.net/rfc/arrow_functions_v2

covariance & contravariance

Добавлена поддержка Ковариантности для return типа (covariance) и Контрвариантности типа аргумента (contravariance). Теперь следующий код будет работать:

class A {}
class B extends A {}

class Producer {
	public function method(): A {}
}

class ChildProducer extends Producer {
	public function method(): B {}
}

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

Коротко о Covariance и Contravariance.

Подробнее на RFC: https://wiki.php.net/rfc/covariant-returns-and-contravariant-parameters

??= — coalesce assign оператор

Добавлена поддержка оператора coalesce assign ??=. Например:

$this->request->data['comments']['user_id'] = $this->request->data['comments']['user_id'] ?? 'value';

// Теперь такую строку можно записать так
$this->request->data['comments']['user_id'] ??= 'value';

Еще пример:

if ( ! isset( $array['key'] ) ) {
	$array['key'] = computeDefault();
}

// тоже самое, но коротко
$array['key'] ??= computeDefault();

Подробнее на RFC: https://wiki.php.net/rfc/null_coalesce_equal_operator

299_792 — знак _ в числах

Добавлена поддержка разделителей подчеркивания в числовых литералах. Например:

6.674_083e-11; // float
299_792_458;   // decimal
0xCAFE_F00D;   // hexadecimal
0b0101_1111;   // binary

Подробнее RFC: https://wiki.php.net/rfc/numeric_literal_separator

WeakReference — слабые ссылки

Добавлена поддержка слабых ссылок (WeakReferences).

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

final class WeakReference {
	public static function create(object $object) : WeakReference;

	public function get() : ?object;
}

Подробнее на RFC: https://wiki.php.net/rfc/weakrefs

strip_tags( $str, ['a', 'p'] )

strip_tags() теперь также принимает массив разрешенных тегов:

// Вместо
strip_tags( $str, '<a><p>' );

// теперь можно написать
strip_tags( $str, ['a', 'p'] );

__serialize() __unserialize() — магические методы

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

// Возвращает массив, содержащий все необходимые состояния объекта.
public function __serialize(): array;

// Восстанавливает состояние объекта из заданного массива данных.
public function __unserialize( array $data ): void;

Новый механизм сериализации заменяет интерфейс Serializable, который в будущем станет устаревшим.

array_merge() — вызов без аргументов

array_merge() и array_merge_recursive() теперь можно вызывать без аргументов, в этом случае они вернут пустой массив. Это полезно в сочетании с оператором spread, например:

array_merge( ...$arrays )

Исключения из __toString()

Теперь разрешено выбрасывать исключения из __toString(). Ранее это приводило к фатальной ошибке. Существующие recoverable фатальные ошибки при преобразовании строк были преобразованы в исключения Error.

Подробнее на RFC: https://wiki.php.net/rfc/tostring_exceptions

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 вы можете использовать последовательность вызовов с новым оператором 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;
	}
  }
}

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)

PHP 8.1

readonly — свойства объекта

Wiki: https://wiki.php.net/rfc/readonly_properties_v2
Doc: readonly-properties

Модификатор readonly предотвращает изменение свойства объекта после инициализации.

Теперь можно не писать отдельный геттер под приватное свойство, а можно просто указать свойству public readonly:

class BlogData {
	public readonly Status $status;

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

Раньше нужно было делать так:

class BlogData {
	private Status $status;

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

	public function getStatus(): Status {
		return $this->status;
	}
}

readonly может применяться только к типизированным свойствам. Readonly-свойство без ограничений типа можно создать с помощью типа Mixed.

Статические readonly-свойства не поддерживаются.

Readonly-свойство можно инициализировать только один раз и только из области, в которой оно было объявлено. Любое другое присвоение или изменение свойства приведёт к исключению Error.

class Foo {
	public readonly string $prop;
}

$obj = new Foo();
$obj->prop = 'bar'; // Error: инициализация за пределами закрытой области.

Указание значения по умолчанию не допускается, потому что это, по сути, то же самое, что и константа.

class Test {
	// Error: не может быть значения по умолчанию
	public readonly int $prop = 42;
}

Readonly-свойства не могут быть уничтожены с помощью unset() после их инициализации. Но их можно уничтожить до инициализации из области, в которой было объявлено свойство.

Readonly-свойства допускают внутренние изменения. Объекты (или ресурсы), хранящиеся в readonly по-прежнему могут быть изменены внутри:

class Test {
	public function __construct( public readonly object $obj ){
	}
}

$test = new Test( new stdClass );
$test->obj->foo = 1; // Правильное внутреннее изменение.
$test->obj = new stdClass; // Неправильное переопределение.

never — тип возвращаемого значения функции

Функция или метод, объявленные с типом never, указывают на то, что они не вернут значение и либо выбросят исключение, либо завершат выполнение скрипта с помощью вызова функции die(), exit(), trigger_error() или чем-то подобным.

function redirect( string $uri ): never {
	header( "Location: $uri" );
	exit();
}

function redirectToLoginPage(): never {
	redirect( '/login' );

	echo 'Hello'; // <- dead code detected by static analysis
}

enum — перечисления

Doc: https://www.php.net/manual/ru/language.enumerations.php
Wiki: https://wiki.php.net/rfc/enumerations

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

Enum:
enum Color {
	case Red;
	case Black;
	case White;
}

Использование:

function foo( Color $color ){
	if( $color === Color::Red ){
		echo 'I`m red!';
	}
}

foo( Color::Red ); //> I`m red!

Каждый case: Color::Red, Color::Black является отдельным объектом enum(Color::Red), enum(Color::Black). Каждый этот объект наследуется от типа (объекта) Color:

var_dump( Color::Red ); // enum(Color::Red)
var_dump( Color::Red instanceof Color ); // bool(true)

Т.е. под капотом это не числа 0,1,2 как в некоторых языках, а именно объекты. У каждого такого объекта есть встроенное свойство $name:

echo Color::Red->name; //> Red
Enum со значениями:
enum Color: string {
	case Red = 'R';
	case Black = 'B';
	case White = 'W';
}

Использование:

echo Color::Red->name; //> Red
echo Color::Red->value; //> R
var_dump( Color::from( 'R' ) ); // enum(Color::Red)
Зачем это нужно?

Чтобы ответить на этот вопрос, рассмотрим пример без енумов и с ними.

Допустим, у нас продаются машины трех цветов: красные, черные и белые. Как описать цвет, какой тип выбрать?

Если мы опишем цвет машины как string:

class Car {
	private string $color;

	function setColor( string $color ): void {
		$this->color = $color;
	}
}

То при вызове $myCar->setColor(...) непонятно, какую именно строку туда писать: "red" или "RED" или "#ff0000". А также, легко ошибиться, написав не то что нужно (rad или Red, например). Ну и, IDE не подскажет возможные значения, и статический анализатор не сможет проанализировать этот момент.

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

class Color {
	public const RED   = "red";
	public const BLACK = "black";
	public const WHITE = "white";
}

А задавая цвет, пишут:

$myCar->setColor( Color::RED );

Это казалось бы то что нужно. Но если с кодом работает новый разработчик и он впервые видит метод $myCar->setColor(...), он может и не знать, что где-то есть константы для цветов. И все так же может сунуть туда любую строку без какого-либо сообщения об ошибке.

Поэтому здесь нужен не класс с константами, а отдельный тип. И вот тут на помощь приходит enum:

enum Color {
	case Red;
	case Black;
	case White;
}

Теперь мы можем использовать тип Color везде где необходимо:

class Car {
	private Color $color;

	function setColor( Color $color ): void {
		$this->color = $color;
	}
}

Из сигнатуры метода любому новичку сразу видно какие варианты есть (IDE их подскажет). При вызове метода $myCar->setColor() в него нельзя передать ничего кроме: $myCar->setColor( Color::White ). Читаемость и поддерживаемость кода на высоте.

enum как класс

Помимо полей "case" в enum может быть еще много всего. По сути это разновидность класса. Он может содержать методы, может реализовывать интерфейсы и использовать трейты.

interface Colorful {
	public function color(): string;
}

trait Rectangle {
	public function shape(): string {
		return 'Rectangle';
	}
}

enum Suit implements Colorful {
	use Rectangle;

	case Hearts;
	case Diamonds;
	case Clubs;
	case Spades;

	public function color(): string {
		return match( $this ){
			self::Hearts, self::Diamonds => 'Red',
			self::Clubs, self::Spades => 'Black',
		};
	}
}

echo Suit::Spades->color(); //> Black
echo Suit::Hearts->color(); //> Red
echo Suit::Hearts->shape(); //> Rectangle

$this будет тот конкретный объект case, для которого мы вызываем метод.

PHP 8.2

Интересные Видео по теме PHP

9 комментариев
    Войти