Что нового в PHP 7.3. Обзор нововведений
Синтаксис
- Смягчение требований к синтаксису Heredoc и Nowdoc
- Поддержка конечных запятых в вызовах функций и методов
- Ссылки в list()
Устаревшие возможности (deprecated)
- Функция image2wbmp() объявлена устаревшей
- Флаги FILTER_FLAG_SCHEME_REQUIRED и FILTER_FLAG_HOST_REQUIRED при использовании FILTER_VALIDATE_URL объявлены устаревшими
- Регистро-независимые константы объявлены устаревшими
Новые возможности
- Опциональный выброс исключений при ошибках в функциях json_encode и json_decode
- Добавление функции is_countable()
- Добавление функций array_key_first() и array_key_last()
Изменения
- Миграция с PCRE на PCRE2
Смягчение требований к синтаксису Heredoc и Nowdoc
Heredoc и Nowdoc требовали ставить закрывающий идентификатор первым в новой строке.
Пример:
$foo = <<<IDENTIFIER
the crazy dog jumps over the lazy fox
"foo" bar;
IDENTIFIER
Здесь закрывающий IDENTIFIER должен быть первым символом на новой линии чтобы это работало. Кроме того, не должно было быть никаких других символов после закрывающего идентификатора (кроме ;, который является необязательным).
RFC для PHP 7.3 предлагает убрать подобные требования для улучшения читабельности кода. Прежде всего чтобы добавить отступы при использовании heredoc/nowdoc идентификаторов.
Полный список изменений в heredoc/nowdoc синтаксисе:
- Закрывающий идентификатор необязательно должен быть первым символом в строке.
- Закрывающий идентификатор имеет отсуп пробелами или табами.
- Отступ (пробелы или табы) не должен быть смешанным. Если вы это сделаете, то получите Parse error: Invalid indentation - tabs and spaces cannot be mixed in .. on line ...
- Точное количество пробелов/табов, используемых перед закрывающим идентефикатором будут удалены из каждой строки heredoc/nowdoc выражения.
- Если число отступающих символов, используемых перед закрывающим идентефикатором, больше чем в любой из строк выражения, вы получите Parse error: Invalid body indentation level (expecting an indentation level of at least ..) in .. on line ..
- несколько выражений после закрывающего идентификатора будут работать без ошибок
Вот сниппет, который использует новые возможности без нарушения новых правил:
$foo = ['foo', 'bar', <<<EOT
baz
- hello world! --
ahoy
EOT, 'qux', 'quux'
];
var_dump($foo);
На выводе будет:
array(5) {
[0]=>
string(3) "foo"
[1]=>
string(3) "bar"
[2]=>
string(29) "baz
- hello world! --
ahoy"
[3]=>
string(3) "qux"
[4]=>
string(4) "quux"
} `
Обратите внимание, что отступы используемые в декларации при помощи heredoc не отображаются в выводе var_dump(), и мы продолжили перечисление элементов массива после EOT идентификатора.
RFC, обсуждение на Externals.io, реализация
Влияние на обратную совместимость
Пока вы не используете набор идентичных heredox/nowdoc идентификаторам символов в качестве начала строки, вы на коне.
$foo = <<<HELLO
HELLO_WORLD <-- это не будет приниматься за окончание строкового литерала
HELLOWORLD <-- и это не будет
HELLO WORLD <-- а это будет
HELLO;
Если у вас имеется heredoc/nowdoc синтаксис подобный вышеописанному, отмечу, что с PHP 7.3, PHP воспримет первый HELLO литерал и выдаст ошибку на следующей строке. В более ранних версиях HELLO WORLD не воспринимался как закрывающий идентификатор для heredoc. Спасибо /u/ImSuperObjective2 с reddit за указание на это.
Поддержка конечных запятых в вызовах функций и методов
Это простое изменение, которое разрешает использование конечных запятых в вызовах функций и методах. Это не влияет на декларирование.
Например, следующий синтаксис станет возможным:
// функция
foo('bar', 'baz',); // Обратите внимание на последнюю запятую после после 'baz'
В до-PHP-7.3 версиях фрагмент выше выбрасывает ошибку PHP Parse error: syntax error, unexpected ')' in .. on line ...
Вы не можете использовать более одной запятой в конце или использовать запятые для пропуска аргументов. В основном это изменение для функция с вариативными параметрами. Также с новыми правками синтаксис массива будет выглядеть более последовательным.
Обратите внимание, что Вы не можете использовать эту возможность в объявлениях функций/методов; это неправильно:
function foo($bar, $baz, ) { // nah, you can't do this.
}
RFC, обсуждение на Externals.io, реализация
Влияние на обратную совместимость
Никакого. Существующий код будет и дальше работать. Если у вас есть вызовы функций принимающие вариативные параметры, добавьте конечные запятые в эти места для удобства. Но использовать их везде — это явно перебор.
Ссылки в list()
Функция list() полезна для быстрого присвоения значений переременным из массива. До версии PHP 7.3 не было возможным указать переменную по ссылке. До PHP 7.3 следующий фрагмент приводил к фатальной ошибке:
$arr = ['apple', 'orange'];
list($a, &$b) = $arr;
$b = 'banana';
echo $arr[1];
// Fatal error: [] and list() assignments cannot be by reference in .. on line ..
Ссылкаться на non-referencable переменные нельзя: list($a, &$b) = [12, 14]; выдаст Fatal error: Cannot assign reference to non referencable value in .. on line ...
RFC, обсуждение на Externals.io, реализация
Влияние на обратную совместимость
Нет. Вместо того чтобы использовать list() для заполнения нескольких переменных, я бы предложил вам воспользоваться value-объектами, чтобы сделать все проще. Они все равно будут передаваться по ссылке, но сделают ваш код намного чище.
Функция image2wbmp() объявлена устаревшей
image2wbmp() функция из расширения GD используется для вывода изображения в формате WBMP (Wireless Bitmap). В PHP 7.3 она объявлена устаревшей в пользу функции imagewbmp().
Если вы используете image2wbmp(), то просто замените название функции на imagewbmp и все будет хорошо! Более 5,500 упоминаний image2wbmp() на github против более 39,300 упоминаний imagewbmp(). Похоже, команда разработчиков PHP убирает редкоиспользуемые функции, чтобы минимизировать воздействие.
RFC, обсуждение на Externals.io, реализация
Влияние на обратную совместимость
Если вы используете функцию image2wbmp(), замените вызов на imagewbmp. Воспользуйтесь автоматизацией, которая сможет изменить это за вас.
Флаги FILTER_FLAG_SCHEME_REQUIRED и FILTER_FLAG_HOST_REQUIRED при использовании FILTER_VALIDATE_URL объявлены устаревшими
Это движение вперед. Когда вы используете filter_var($var, FILTER_VALIDATE_URL), есть два дополнительных флага, которые можно поставить для обеспечения строгой проверки URL-адреса: FILTER_FLAG_SCHEME_REQUIRED и FILTER_FLAG_HOST_REQUIRED.
Начиная с версии PHP 5.2.1 оба этих флага применяются неявно вне зависимости установлены они или нет.
Если ваш код использует эти флаги, просто удалите их и будет хорошо. На данный момент существует более 5000 результатов поиска на github с их использованием.
RFC, обсуждение на Externals.io, реализация
Влияние на обратную совместимость
Так как оба этих флага объявлены устаревшими, вы увидите уведомление вроде:
Deprecated: filter_var(): explicit use of FILTER_FLAG_SCHEME_REQUIRED and FILTER_FLAG_HOST_REQUIRED is deprecated in ...
Все, что вам нужно сделать — просто удалить два флага, т.к. они и так подразумеваются при использовании FILTER_VALIDATE_URL.
Регистро-независимые константы объявлены устаревшими
Функция define() позволяет объявить константу в регистро-независимом режиме. Вы должны явно объявить константу с учетом регистра, передав третьим параметром функции true. Это не поведение по-умолчанию и наверняка не согласуется с возможностью объявлять константы через ключевое слово const.
define('Foo', 'Bar', true);
Приведенный выше код будет выбрасывать уведомление об устаревании: Deprecated: define(): Declaration of case-insensitive constants is deprecated in ...
Кроме того, при попытке получить доступ к константам, которые были объявлены в режиме без учета регистра (FOO), вы увидете довольно полезное предупреждение: Deprecated: Case-insensitive constants are deprecated. The correct casing for this constant is "Foo"
RFC, обсуждение на Externals.io, реализация
Влияние на обратную совместимость
Вам придется идти в базовый код, туда где объявляются регистро-независимые константы и последовательно исправлять. Крайне маловероятно, что не будет никаких проблем с этим, потому что отловить все варианты использования довольно трудозатратно, но в результате код станет понятней.
Я не нашел на github примеры подобного использования, но как минимум Drupal и WordPress (два довольно старых и зрелых проекта в PHP) имеют регистро-независимые константы.
Опциональный выброс исключений при ошибках в функциях json_encode и json_decode
Одно из моих любимых. Все эти годы json_encode() и json_decode() молчали об ошибках в PHP-переменных или json-строках, что приводило к забагованному коду. Этот случай даже был в знаменитой критике PHP: Фрактал плохого дизайна.
json_decode возвращает null для невалидного ввода, при том, что null — абсолютно верный объект для декодируемого JSON'а. Эта функция абсолютно ненадёжна, если вы конечно не вызываете json_last_error каждый раз при её использовании.
Потребовалось 6 лет после того поста в блоге и у нас появилась возможность получить ошибку о сбоях работы с json:
try {
json_decode("{", false, 512, JSON_THROW_ON_ERROR);
}
catch (\JsonException $exception) {
echo $exception->getMessage(); // выводит "Syntax error"
}
Новый \JsonException является наследником от \Exception, а также константа JSON_THROW_ON_ERROR и сам JsonException находятся в глобальном пространстве имен.
Я настоятельно рекомендую вам начать использовать эту функцию. Есть сторонние библиотеки, такие как daverandom/exceptional-json, реализующие аналогичную функциональность для версий PHP 7.2 и ниже. С появлением этой функции в ядре PHP, вы можете удалить этот пакет и тонны некрасивого шаблонного кода с вызовом json_last_error() в каждом месте где вы работаете с json.
RFC, обсуждение на Externals.io, реализация
Влияние на обратную совместимость
Никакого, если вы не используете собственное исключение и/или константу с такими же именами.
Добавление функции is_countable()
В PHP 7.2 достаточно много устаревших и забагованных функций. Если вы в PHP 7.2. вызываете count() с использованием не-countable переменной, то PHP выведет предупреждение об этом. В общих правках было предложение проверять получаемую перменную на countable до ее использования в count().
countable-переменной является массив или объект реализующий \Countable интерфейс. Так как при проверке будет использоваться много шаблонного кода, в PHP 7.3 появилась новая функция is_countable(), проверяющая переменную на… ну… возможность использования с count().
Я написал полифил для is__countable(), если вы хотите начать использовать эту возможность уже сейчас.
RFC, обсуждение на Externals.io, реализация
Влияние на обратную совместимость
Пока не объявлена своя собственная функция is_countable(), проблем не будет.
Добавление функций array_key_first() и array_key_last()
В PHP существует 75 различных функций для работы с массивами, но до сих пор не было простого способа получить первый и последний ключи массива без изменения указателя массива или перебора всех ключей (через array_keys()) и затем получения первого/последнего значения.
Появились две новые функции, array_key_first() и array_key_last() позволяющие это делать.
В RFC также предлагалось добавить array_value_first() и array_value_last(), но эта часть не прошла голосование.
RFC, обсуждение на Externals.io, реализация
Влияние на обратную совместимость
Если вы не объявляли свои собственные array_key_first() и array_key_last() функции, то проблем не возникнет.
Миграция с PCRE на PCRE2
PHP использует Perl Compatible Regular Expressions или коротко PCRE в библитеке для работы с регулярными выражениями. С версии PHP 7.2 используется 8.x версия легаси-библиотеки PCRE, а в PHP 7.3 уже будет использоваться PCRE2. Обратите внимание, что PCRE2 считается новой библитекой, хотя в значительной степени совместима с PCRE (8.x).
Новая библитека более агрессивна в валидации паттернов и может привести к ошибкам в существующем коде. Следующий фрагмент будет невалидным с PHP 7.3:
preg_match('/[\w-.]+/', '');
PHP выбросит предупреждение Warning: preg_match(): Compilation failed: invalid range in character class at offset 3.
Проблема с шаблоном: чтобы это работало дефис должен быть перемещен в конец или экранирован.
preg_match('/[\w\-.]+/', '');
Приведенный выше код отлично отработает не только с PHP 7.3, а также и с более старыми версиями. В новом паттерне дефис экранирован - в \-. Это самая распространенная проблема из всех с которыми можно столкнуться при решении вопросов совместимости.
Это довольно незначительное изменение, но есть шанс, что все пойдет не так. Сообщение об ошибке указывает на точное положение символа в регулярном выражении. Убедитесь, что тщательно проверили свой код. Проверьте ваши регулярные выражения на совместимость с PCRE2 синтаксисом через Regex Buddy или другой подобный софт. Дополнительные сведения смотрите в описании PCRE2 синтаксиса и устаревшего PCRE синтаксиса.
RFC, обсуждение на Externals.io, реализация
Влияние на обратную совместимость
Так как PCRE2 более придирчив и строг к шаблонам, некоторые из ваших preg_match() и подобных вызовов могут не работать. Исправление варьируется от простого обновления шаблона (например, заэкранировать дефисы) до переписывания шаблонов. Убедитесь, что все ваши тесты проходят нормально.