Что нового в 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 5.3 - 8.2 — Синтаксис, Новинки