WordPress как на ладони
Недорогой хостинг для сайтов на WordPress: wordpress.jino.ru

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

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

Ищем WP-разработчика! Фулл-тайм, удаленка, хорошая зарплата, соц. пакет. Подробности.
Компания Boosta.

При написании плагинов или тем WordPress можно использовать только возможности PHP 5.3, выше пожалуй не стоит... Все что можно в PHP 5.4 и выше, нужно решать через создание вспомогательных переменных. Впрочем, даже 5.3 не всегда работает, есть еще серверы с PHP 5.2, но хорошо что это редкость. К слову, сам WordPress еще поддерживает PHP 5.2.

Итак, к новинкам...

Вопросы для собеседований по PHP (англ.) https://interview-guru.club/php

Оглавление:

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

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

Короткая запись о которой идет речь это: <?= вместо <?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( $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 ){
	return print_r( $numbers, 1 );
}

echo 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 и получение значения

Новый оператор слияния с 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 — три сравнения сразу: больше, равно, меньше

Новый оператор сравнения <=> — «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}; — группировка импорта

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

// 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 — изменена логика работы

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

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

Переключение указателей и влияние на работу цикла в PHP 7:

// Пример 1: автоматически не переключает внутренний переключатель next()
$a = [1,2,3];           foreach($a as $v)   { echo "$v-". current($a) .' '; } // 1-1 2-1 3-1 (php5: 1-2 2-2 3-2)
$a = [1,2,3]; $b = &$a; foreach($b as $v)   { echo "$v-". current($b) .' '; } // 1-1 2-1 3-1 (php5: 1-2 2-3 3-)
$a = [1,2,3]; $b = $a;  foreach($b as $v)   { echo "$v-". current($b) .' '; } // 1-1 2-1 3-1 (php5: 1-1 2-1 3-1)
$a = [1,2,3];           foreach($a as & $v) { echo "$v-". current($a) .' '; } // 1-1 2-1 3-1 (php5: 1-2 2-3 3-)
// В PHP 7 все строки выведут один результат: 1-1 2-1 3-1
// В PHP 5 он будет разный

// Пример 2: внутренний переключатель можно переключить с помощью next()
$a = [1,2,3];           foreach($a as $v)   { echo "$v-". current($a) .' '; next($a); } // 1-1 2-2 3-3 (php5: 1-2 2-3 3-)
$a = [1,2,3]; $b = &$a; foreach($b as $v)   { echo "$v-". current($b) .' '; next($b); } // 1-1 2-2 3-3 (php5: 1-2 2-3 3-)
$a = [1,2,3]; $b = $a;  foreach($b as $v)   { echo "$v-". current($b) .' '; next($b); } // 1-1 2-2 3-3 (php5: 1-2 2-3 3-)
$a = [1,2,3];           foreach($a as & $v) { echo "$v-". current($a) .' '; next($a); } // 1-1 2-2 3-3 (php5: 1-2 2-3 3-)
// В PHP 7 все строки выведут один результат: 1-1 2-2 3-3
// В PHP 5 результат тоже будет один, но другой: 1-2 2-3 3-

// Пример 3:
// при &$v foreach работает с оригиналом $a и изменение массива влияет на цикл
$a = [1,2,3];           foreach($a as &$v) { echo "$v "; unset($a[1]); } // 1 3 (php5: 1 3)
$a = [1,2,3]; $b = &$a; foreach($b as &$v) { echo "$v "; unset($a[1]); } // 1 3 (php5: 1 3)
// в PHP 5 и PHP 7 совпадают

// при $v foreach работает с копией $a и изменение массива НЕ влияет на цикл
$a = [1,2,3];           foreach($a as $v) { echo "$v "; unset($a[1]); } // 1 2 3 (php5: 1 2 3)
$a = [1,2,3]; $b = &$a; foreach($b as $v) { echo "$v "; unset($b[1]); } // 1 2 3 (php5: 1 3)
// в PHP 5 и PHP 7 результаты отличаются
меню

$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 gen() — делегирование генераторов

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

PHP 7.3

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 (??smile. Например:

$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, вы пишите код чаще всего?

  • Добавить ответ
11 комментов
Вопросы 1 Все
    Войти