PHP 7.1: Обзор новых возможностей

в 11:52, , рубрики: changelog, php, PHP holywars, php7, php7.1, rfc, what's new, обзор, ооп, Программирование, Разработка веб-сайтов

image На Хабре уже был перевод с обзором несколько месяцев назад, но недавно вышел первый релиз-кандидат PHP 7.1, а значит никаких существенных изменений больше не будет и можно сказать, какие точно изменения будут в релизе. Я решил немного оживить сухой “changelog” своим вольным переводом изменений, которые принесет нам новая минорная версия 7.х ветки.

Новая функциональность

Добавлен возвращаемый тип «void» (RFC)

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

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

Возврат какого-то значения из метода/функции, который помечен как void, будет генерировать исключение уровня Fatal Error. Обратите внимание, что NULL значение не приравнивается к void (отсутствию значения), то есть возращать NULL нельзя.

Кстати, это не значит что $x = someNethod(); не вернет ничего. Как и прежде в $x будет значение NULL. Так же void нельзя использовать как тип к параметру.


function bar(void $foo) {}
// Выбросит: Fatal error: void cannot be used as a parameter type in....

Добавлен новый псевдо-тип: «iterable» (RFC)


function walkList(iterable $list): iterable {
    foreach ($list as $value) {
        yield $value[‘id’];
    }
}

Этот тип по сути объединяет примитивный тип array и интерфейс Traversable (а значит и его производные: Iterator, Generator, etc). Проблема возникла на почве того, что к примеру, foreach может работать с обоими типами, но функция с типом array не примет объект с интерфейсом Traversable и наоборот.

Так же в рамках этого RFC была добавлена новая функция is_iterable(), которая работает аналогично другим is_* функциям.

Появилась возможность разрешать null в типизированных и возвращаемых параметрах (Nullable RFC)


    function callMethod(?Bar $bar): ?Bar {}
    $this->callMethod($bar); // Работает
    $this->callMethod(null);   // Работает
    $this->callMethod();        // НЕ работает

Обратите внимание, что использование "?" и значение null по умолчанию не одно и тоже что


 function callMethod(int $bar = null) {}
$this->callMethod(1); // Работает
$this->callMethod(null); // Работает
$this->callMethod(); // Тоже работает

Причем добавление "?" оставляет поведение обратно совместимым

function callMethod(?Bar $bar = null) {}
 // Работает так же как и без “?”

Также важный момент по наследованию:


interface Fooable {
    function foo(int $i): ?Fooable;
}
interface StrictFooable extends Fooable {
    function foo(?int $i): Fooable; // valid
}

В наследнике можно делать «строже» возвращаемый тип (то есть запрещать nullable), а параметр наоборот расширять до nullable, НО не наоборот!

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

echo $msg[-1]; // вернет последний символ
echo $msg{-3}; // Причем RFC явно рекомендует использовать способ $str{} так как $str[] может сбивать с толку И в будущем может быть объявлен как устаревшим.

Отрицательные значения так же стали разрешены в некоторых строковых функциях: strpos, stripos, substr_count, grapheme_strpos, grapheme_stripos, grapheme_extract, iconv_strpos, file_get_contents, mb_strimwidth, mb_ereg_search_setpos, mb_strpos, mb_stripos.

Везде это означает считать смещение с конца строки.

Разрешено использовать строковые ключи в конструкции list() (RFC)

Так же был добавлен короткий синтаксис для list (RFC).


["test" => $a, "name" => $b] = ["name" => "Hello", "test" => "World!"];
var_dump($a); // World!
var_dump($b); // Hello

Особенности:

  • нельзя использовать смешанный синтаксис (если указываем ключи — то указываем их везде, если нет, то используются обычные индексы 0, 1, 2… как обычно):
    
    // Parse error: syntax error, ...
    ["a" => $a, $b] = ["a" => 1, 2]
    

  • пустые элементы с ключами тоже же не разрешены:
    
    // Parse error: syntax error, ...
    list(,,,, "key" => $keyed) = $array;
    

  • если ключа в исходном массиве нет, то будет выброшено предупреждение Notice: Undefined index: name, а в переменной будет NULL
  • при использовании вложенной конструкции list способы можно комбинировать
    
    $points = [
        ["x" => 1, "y" => 2],
        ["x" => 2, "y" => 1]
    ];
    [["x" => $x1, "y" => $y1], ["x" => $x2, "y" => $y2]] = $points;
    

Конвертация callable выражений в замыкание (RFC)

Closure::fromCallable(callable $calback);

Вот наглядный пример применения:

class A {
public function getValidator(string $name = 'byDefault') {
    return Closure::fromCallable([$this, $name]);
}
private function byDefault(...$options) {
	echo "Private default with:".print_r($options, true);
}
public function __call ( string $name , array $args ) {
	echo "Call $name with:".print_r($args, true);
}
}

$a = new A();
$a->getValidator("test")(1,2,3);
// Call test with: Array ( [0] => 1 [1] => 2 [2] => 3 )
$a->getValidator()(‘p1’, ‘p2’);
// Private default with: Array ( [0] => ‘p1’, [1] => ‘p2’)
// Внимание Closure::fromCallable передает контекст ($this) в момент вызова внутрь замыкания, тем самым разрешая обращаться к приватным методам
// если оставить только return [$this, $name]; то 
$a->getValidator()(‘p1’, ‘p2’);
// вернет 
// Call byDefault with:Array ( [0] => p1 [1] => p2 )
// то есть вызовет только публичный метод и не будет иметь доступа к приватным методам объекта

Поддержка модификаторов видимости для констант класса (RFC)


class Token {
	// Константа без модификатора по умолчанию “public”
	const PUBLIC_CONST = 0;
 
        // Константы с различной областью видимости
        private const PRIVATE_CONST = 0;
        protected const PROTECTED_CONST = 0;
        public const PUBLIC_CONST_TWO = 0;
 
        // Весь список имеет одну область видимости
        private const FOO = 1, BAR = 2;
}

Ловить исключения можно объеденяя несколько типов исключений в один блок (RFC)


try {
   echo "OK";
} catch (Exception | DomainException $e) {
   // ... обработка 2ух типов исключений сразу
} catch (TypeError $e) {
   // ...
}

Выбросы ошибок уровня E_NOTICE and E_WARNING при арифметических операциях над строками содержащие не валидные числа (RFC)


$numberOfApples = "10 apples" + "5 pears";
// Выбросит
// Notice: A non well formed numeric string encountered in example.php on line 3
// Notice: A non well formed numeric string encountered in example.php on line 3
$numberOfPears = 5 * "orange";
// Warning: A non-numeric string encountered in example.php on line 3

Это довольно важное изменение, которое теоритически может сломать обратную совместимость приложения если используются свои error handlers для перехвата предупреждений.

Причем есть интересная особенность: пробел в начале строк “ 5” + “ 3” — не даст ошибок. А вот “5 ” + “3 ” — пробел в конце уже даст выдаст предупреждения.

Для обхода последствий неявного преобразования и выброса предупреждений можно явно указывать “cast” в нужный тип: (int)“5 ” + (int)“3 ” или подавлять все принудительно @(“5 ” + “3 ”).

Другие изменения и обратные несовместимости

  • В связи с новыми типами, добавлены новые зарезервированные слова void, iterable, и код который содержит классы, интерфейсы, трейты с такими именами будет давать ошибку в 7.1
  • Поменяли поведение в php экстеншенах, которые продолжали выкидывать Fatal Error вместо генерации Error исключения (как текущее ядро 7.0), плюс ошибки уровня E_ERROR или E_RECOVERABLE_ERROR тоже стали выбрасывать исключения там, где возможно (понятное дело, что при нехватки памяти по прежнему скрипт необратимо падает (RFC)).
  • Изменилось поведение при вызове функций / методов без передачи обязательных аргументов. Теперь вместо привычного Warning предупреждения, будет выброшено исключение ArgumentCountError (наследует тип Error RFC):
    
    function foo($a) {
       var_dump($a);   // теперь исполнение сюда не дойдет и в $a не будет NULL
    }
    foo();
    // Fatal error: Uncaught ArgumentCountError: Too few arguments to function foo(), 0 passed in...
    

  • Следующие функции больше нельзя вызвать динамически через: $func(), call_user_func(), array_map() и тд:
    1. extract()
    2. compact()
    3. get_defined_vars()
    4. func_get_args()
    5. func_get_arg()
    6. func_num_args()
    7. parse_str() с одним аргументом
    8. mb_parse_str() с одним аргументом
    9. assert() больше нельзя использовать строку в качестве агрумента

  • Функции rand() и srand() теперь просто псевдонимы (alias) к функциям mt_rand() и mt_srand().
    Это в свою очередь затронет вывод таких функций:

    1. rand()
    2. shuffle()
    3. str_shuffle()
    4. array_rand()

  • Добавлена функция session_gc(). Теперь можно чистить старые сессии прямо из скриптов.
  • Добавлена функция session_create_id(), которая позволяет сгенерировать валидный автоматический id сесии без запуска новой сесии, который можно будет использовать в session_id() для старта сессии со сгенерированным ранее ID.
  • Ускорили генерацию ID сессии в 2+ раз, убрав хеширование и используя новую функцию из 7.0 php_random_bytes()
    Скорость до: Requests per second: 899.36 [#/sec]
    Скорость после: Requests per second: 2278.59 [#/sec]
    

  • Убрали неконсистентное поведение над переменной $this
    
    function foo($this) { // Fatal error: Cannot use $this as parameter
    }
    static $this; // Fatal error: Cannot use $this as static variable
    global $this; // Fatal error: Cannot use $this as global variable
    try {
      ...
    } catch (Exception $this) { // Fatal error: Cannot re-assign $this
    }
    foreach ($a as $this) { // Fatal error: Cannot re-assign $this
    }
    unset($this); // Fatal error: Cannot unset $this
    $a = "this";
    $$a = 42; // throw new Error("Cannot re-assign $this")
    // и другие кейсы

  • Расширение mcrypt помечено как устаревшее и все mcrypt_* функции будут выкидывать E_DEPRECATED.
  • В curl расширение добавлена поддержка для HTTP/2 Server Push, так же были добавлены новый функции curl_multi_errno(), curl_share_errno(), curl_share_strerror().
  • Опция 'e' для функций mb_ereg_replace() и mb_eregi_replace() обьявлена устаревшей.

На этом мы пожалуй и остановимся, хотя там еще полно мелкий изменений в основном в расширениях. А нам для холивара вполне хватит и этого списка. )

Итог

Лично моё мнение про данный минорный релиз: все очень органично вписалось, именно этого и не хватало в большинстве своем в новом PHP 7.0 и данные изменения лишь подчеркивают и усиливают особенности 7.х ветки.
Рекомендую дождаться 7.1.1 и можно обновляться без страха, что-то сломать (если вы конечно уже перешли на 7.0).

Данная статья не претендует на полное описание ВСЕХ изменений и я мог пропустить что-то важное, рекомендую все равно ознакомиться с первоисточниками:
» https://wiki.php.net/rfc#php_71
» https://github.com/php/php-src/blob/php-7.1.0RC1/UPGRADING

P.S. Примеры можно испытать самому в онлайн песочнице — 3v4l.org/#version=7.1.0RC1

Автор: shandy

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js