Новая функциональность
Добавлен возвращаемый тип «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() и тд:
- extract()
- compact()
- get_defined_vars()
- func_get_args()
- func_get_arg()
- func_num_args()
- parse_str() с одним аргументом
- mb_parse_str() с одним аргументом
- assert() больше нельзя использовать строку в качестве агрумента
- Функции rand() и srand() теперь просто псевдонимы (alias) к функциям mt_rand() и mt_srand().
Это в свою очередь затронет вывод таких функций:
- rand()
- shuffle()
- str_shuffle()
- 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") function foo() { var_dump($this); // throws "Using $this when not in object context" // php-7.0 emitted "Undefined variable: this" and printed NULL } foo(); // и другие кейсы
- Расширение 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
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Комментарии (55)
gro
13.09.2016 16:33+1> Добавлен новый псевдо-тип: «iterable»
Продолжаем потихонечку-потихонечнку двигаться вперёд.
В 8.1, наконец введут псевод-тип для объединения array и ArrayAccessshandy
13.09.2016 17:20А что в PHP есть встроенные классы, которые реализуют только интерфейс ArrayAccess?
Насколько я помню (но могу и ошибаться), все кто реализует ArrayAccess так или иначе используют и Traversable интерфейс в конечном итоге. Например, тот же ArrayObject — он будет принят через «iterable» тип.
Другой вопрос самописные классы — но в них нет проблем добавить еще один дополнительный интерфейс Iterator или IteratorAggregate и все так же будет работать…gro
13.09.2016 17:47+2Но «возможность итерировать» и «хранилище с доступом по ключу», это разные интерфейсы.
shandy
13.09.2016 18:07Верно! Тогда почему от хранилища с доступом по ключу ждут возможность итерации (iterable)? )
foreach для ArrayAccess никогда работал — https://3v4l.org/8H8vRgro
14.09.2016 12:12Совершенно верно, никогда не работал и никто не ждёт.
Есть два интерфейса — перебор элементов и доступ к конкретному элементу по ключу. Они не взаимозаменяемые.
Нативный array «реализует» их оба.
И для итерирования, наконец, его слили с объектами с нужным интерфейсом.
А для доступа по ключу, опять забыли.shandy
14.09.2016 15:15Вот теперь понял.
Да действительно, для примитивного типа array и ArrayAccess придется создавать свой псевдотип (например «hashable»?) и оно никак не будет пересекается с «iterable».
Пойти что-ли предложить им «hashable» RFC? ))
SerafimArts
14.09.2016 04:00+1Тогда в php 9.2 можно ожидать наконец введения типов, как в некоторых других языках? =)
type likeArray = array|ArrayAccess; function a(likeArray $a) { ... }
Dreyk
13.09.2016 16:44ну вот зачем отдавать преимущество {} перед [] для доступа к строке? ничего оно путать не будет, в других языках ведь как-то не путаются. а вот разные скобки задолбаешься ставить.
UPD: А не, они еще не решили
Скрытый текстOn the opposite side, it was also suggested that array access and string offsets are so closely-related concepts that we should recommend using '[]' in both cases and disable the alternate '{}' syntax for string offsets !
So, as the subject is controversial and very tangential to the subject of this RFC, it will be left for a future RFC.
artyfarty
13.09.2016 16:54+2Изменение работы isset() в 7.0.6 резко охладило мой пыл обновляться. То что разработчики могут без лишнего шума в минорной версии могут решить всё сломать не греет душу. Я в целом за то чтобы убирать грязь из движка, но вот так вот, в минорных версиях, без настроек, без этапа депрекации – это опасно. Я ж не могу за весь composer.json отвечать.
shandy
13.09.2016 17:12Ну проколы бывают у всех. Это хотели сломать еще в 7.0.0 — что было бы естественным.
ОбьяснениеIt is unfortunate that this change had to happen in a patch release, rather than in PHP 7.0.0. It's my fault that I did not notice during the pre-release cycle that this previously relatively harmless bug completely breaks the null-coalesce operator in PHP 7. However, that's how things went, and at this point a fix was critical.
For reference, the way to fix any issues with this change is to make sure your __isset() or offsetExists() functions behave correctly. If this change causes issues with your code, it indicates that you already have a broken __isset/offsetExists implementation, but it's brokenness did not previously surface (one bug canceling another...)baltazorbest
13.09.2016 21:38+4А можете пожалуйста сказать, как именно изменили работу?
artyfarty
14.09.2016 13:19На хабре был пост: https://habrahabr.ru/post/283302/
Из него не совсем ясны последствия, но хотя бы понятно что у себя проверять.
syouth
13.09.2016 16:58-2Кстати, это не значит что $x = someNethod(); не вернет ничего. Как и прежде в $x будет значение NULL
У авторов php шмаль явно лучше чем у меня.AndreyRubankov
13.09.2016 17:22+1Можно предположить, что это сделано для обратной совместимости.
Код, который имеет такое поведение уже написан и просто так ломать его не принесет успеха новой версии языка.
Хотя с другой стороны, было бы не плохо кидать какой-то warning для такого кода, чтобы фиксили активнее.4orever
13.09.2016 21:06+2Простите, какая обратная совместимость? Речь о функции с возвращаемым типом void, который только-только вводится. В старых проектах таких ситуаций в принципе быть не может. Логично выкидывать предупреждение, если кто-то пытается присвоить значение функции с типом void.
AntonShevchuk
13.09.2016 22:41+1Может быть, если решили обновить зависимости, которые используются в текущем «старом» коде.
4orever
14.09.2016 00:23Можете пояснить?
Punk_UnDeaD
14.09.2016 01:42Если вы пишете генератор кода, то проще считать, что все функции что-то возвращают.
Я писал, например, мне проще.
Рефлексия же не давала никакой информации.
Теперь эта информация есть, можно её использовать.
Но для обратной совместимости лучше пока оставить, потом в депрекейтед и потом выпилить.
Pakos
14.09.2016 09:33PHP-программисты пишут всё с нуля и переписывают проект только целиком? Если использовать библиотеку, которая обновится и функция станет возвращать void, то это как раз тот случай.
Source
14.09.2016 11:18Если использовать библиотеку, которая обновится и функция станет возвращать void,
то это как раз тот случай, когда лучше показать даже не ворнинг, а ошибку. Ведь если вызывающий код использовал значение, которое возвращала функция, то он никак не ожидает настолько кардинальной смены логики работы этой самой функции. Прикинуться валенком и продолжать работать — это худшее что может сделать интерпретатор в такой ситуации.Pakos
14.09.2016 12:49Так оно возвращало NULL и возвращает NULL, как понял.
SerafimArts
14.09.2016 14:22Именно так. Смысл void только в том, чтобы помечать методы (функции) у которых де-факто отсутствует возвращаемое значение. Можно было конечно не изобретать новый псевдотип, а использовать null:
function a(): null {}
Но с другой стороны такая конструкция позволяет писать:
function a(): ?int { return random_int(0, 1) ?: null; } function b(): null { return a(); }
Что говорит о том, что ошибка может возникнуть в 50% случаях. А на void можно ругаться ещё на этапе парсинга тела метода\функции (скорее всего так и будет, я пока не пробовал войд на практике, не в курсе). Там не должно быть ничего кроме
return;
или же он (ретурн) просто должен отсутствовать, что избавляет от подобных проблем.
Source
14.09.2016 21:20Зачем записывать в переменную значение возвращаемое функцией, если оно всегда было NULL? А если оно не всегда возвращало NULL, а теперь возвращает void, то логика поменялась, а вызывающий код получил мину замедленного действия в виде отсутствия явной ошибки.
VolCh
15.09.2016 23:59Функции могут динамически подставляться
Source
16.09.2016 13:45С разными типами возврата? Ну, право слово, PHP не обязательно использовать для извращений.
AndreyRubankov
16.09.2016 14:33Фреймворк роутинга, который маппит HttpMethod + PATH [+MimeType] на функцию обработки.
Функция возвращает Объект, этот объект потом превращается в контент (к примеру в json или xml).
Функция может вернуть int, string, number, Object, null.
Пока не было типов функция могла ничего не возвращать, а просто записать что-то в хедеры и/или установить статус.Source
16.09.2016 23:24И? Вы не считаете такую архитектуру извращением?
Пока весь остальной мир идёт по пути унификации обработки веб-запросов (см. WSGI, Rack, Clack, Plug, WAI, etc.), Вы предлагаете из роутера передавать управление к разнородным функциям, которые хз что возвращают?
Ну, ok… ?\_(?)_/?AndreyRubankov
17.09.2016 01:49Посмотрел на WSGI и Rack (остальных не нашел), выглядит действительно не плохо! Но, как мне показалось, это то как на пхп писали еще лет 10 назад. Обработчик запроса, который устанавливает код ответа, хедеры и контент, и все это в одном месте. Для этого в пхп и фреймворки не нужны.
Описанный мною подход — это крайне упрощенный JAX-RS.
На выходе получается крайне простой и самодокументированный код, который не требует пояснений, который не содержит смеси бизнес-логики и формирования конента.
ps: вот пример из мира PHP, чтобы не было притензий, что я с монстроузорной java лезу тут: https://laravel.com/docs/5.3/routing
sumanai
13.09.2016 17:36-2В наследнике можно делать «строже» возвращаемый тип (то есть запрещать nullable), а параметр наоборот расширять до nullable, НО не наоборот!
Вот как вот с этим работать?AndreyRubankov
13.09.2016 18:02Если работать через интерфейс, то более строгая имплементация ничего не поменяет. При этом, более строгая имплементация, сама по себе, запрещает коду сделать что-то не так.
Эта фича в простом коде не будет сильно использоваться, но дает гибкость для построения абстракции (при разработке фреймворков и библиотек).sumanai
14.09.2016 05:20Да я про то, что в одном месте можно расширять до nullable, в другом нельзя. И всё это нужно запоминать, логики лично я в этом не вижу.
AndreyRubankov
14.09.2016 08:56+1Да, это кажется странным на первый взгляд, но на самом деле все довольно просто.
Интерфейс метода описывает максимальное допустимое множество значений, которые может обработать.
Имплементация может расширить это множество, скажем обрабатывать не только int, но еще и long, — это не сломает интерфейс int это подмножество множества long. А вот вместо int принимать short мы уже не сможем, т.к. множество short меньше множества int.
— а если работать через интерфейс, то сам интерфейс не позволит передать long, даже если в имплементации аргумент типа long.
Примерно такой же подход при возврате значения, мы можем Уменьшать множество ответов, но не можем его расширять. Уменьшать мы можем только, если меньшее множество ответов является подмножеством большего.
К примеру long в интерфейсе мы сможем заменить на int в имплементации, но вот заменить на bool — не сможем, bool — не является подмножеством множества long. (под bool подразумевается множество {true, false}, а не {1,0})
ps: примеры long, int, bool приведены как ограниченные множества, чтобы проще было понять идею, в реальности такие манипуляции могут не пройти.
Andrey_Volk
13.09.2016 18:08+1У меня тут такой вопрос назрел: почему нельзя было сделать конструкцию
void Test() { ... }
как во многих языках программирования это реализовано. Зачем они сделали:
function someNethod(): void { ... }
?
Уж извините за такой вопрос, но он мне покоя не дает.MaximChistov
13.09.2016 18:47Подозреваю что это нужно чтобы не сломать старый код в которым такие функции(без типа результата) возвращают значение.
shandy
13.09.2016 18:56+3Ответ почему типы решили объявлять в конце функции, а не в начале есть тут.
В целом думаю ориентир был на Hack, где это уже было реализовано именно так (типа зачем плодить другие варианты).0x9d8e
13.09.2016 20:17+2Эх. А ведь частенько руки сами набирают что-то типа protected int getNumber() {}. Вообще не очень понятно, почему бы от function не отказаться. Парисить чуть сложнее разве что.
Dremkin
13.09.2016 19:09К примеру, чтобы легко было найти поиском «function Test» в десятке мегабайт кода.
xalkin
13.09.2016 20:20-2Потому что это PHP, он не такой как все.
SerafimArts
14.09.2016 01:311) Js + FlowType
2) TypeScript
3) Haxe
4) ActionScript
5) ...
Ну да, уникальный в своём роде в этом случае.
rdifb0
14.09.2016 11:50+1Потому что обратная совместимость.
Потому что родитель был Perl, а не Си.
Потому что типизация пока по желанию.
OnYourLips
14.09.2016 11:59+1Потому что такой вариант синтаксиса не менее популярен. Более того, он гораздо удобнее читается (при чтении слева направо).
И между «сделать, как в C» и «сделать удобно» выбрали второй вариант, и я их поддерживаю.
Rathil
14.09.2016 00:27Запрет на $this вне класса — сломается 1й Yii, там он в шаблонах использовался. Значит будем на 7.0.Х пробовать переезжать.
AlexBond
14.09.2016 11:49+2По факту в Yii все шаблоны исполняются внутри класса View, так что ничего не должно сломаться.
annenkov
14.09.2016 15:44+1с чего взяли, что шаблоны YII1 вне класса работают? они инклудятся внутри метода
Anexroid
Что-то не совсем понял, как использовать void в параметрах функции.
То есть?
Вернет 5? Или данный код некорректен?
Drim
newmindcore
Данный код некорректен.
Anexroid
А, извиняюсь, неправильно прочитал.