php7
Cookie в PHP 7.4
Обнаружилась очень странная проблема у меня на сайте — некоторые комментаторы стали подписываться с плюсом между именем и фамилией. В первый раз я решил, что у человека это вышло случайно или, может, это
Выяснилось, что плюс появился сам собой, люди его и не думали ставить. Так я понял, что столкнулся
В чём примерно дело, понятно стало почти сразу — в вебе пробел иногда кодируется как «+», видимо ошибка
Проблема воспроизводится у тех комментаторов, которые оставили свой первый комментарий до того, как мой сайт перешёл на ПХП 7.4. В этой версии сменился способ кодирования пробела в «куках» (см. на скриншоте) — был +, стало %20.
В принципе, можно вернуть
if (strpos($_SERVER['HTTP_COOKIE'], '+') !== false) {
parse_str(strtr($_SERVER['HTTP_COOKIE'], ['; ' => '&']), $_COOKIE);
}Но хорошо бы внедрить в движок блога код, который будет перекодировать такие куки у пользователей.
FFI: баг не будет исправлен?
В пхпешном FFI есть баг — можно, вызывав сишную функцию случайно изменить значение совершенно других переменных
Очень не хочется это признавать, но похоже тот баг, который я обнаружил в реализации FFI (интерфейса к языку Си) в ПХП, исправлен не будет. Собственно, на это мне давно указал один из читателей в комментариях к прошлой заметке на эту тему, но мне всё не верилось.
В тикете, который я завёл по проблеме, один из разработчиков языка написал, что придерживается мысли, что это преднамеренное проектное решение. Не думаю, что это действительно так, наверняка получилось случайно, но как всё поправить, не снизив существенно производительность модуля FFI, — неясно.
Видимо, придётся жить с тем, что есть.
В прошлый раз, в качестве временной меры, я предлагал использовать str_repeat (или эквивалент) для того, чтобы разорвать запутанность двух переменных и таким образом предотвратить неожиданное их изменение. Это плохой приём, но я его задумывал в качестве временной меры.
Но раз это навсегда, давайте избегать сторонние эффекты правильно. Тот же читатель предлагает более корректный способ:
Первый параметр memcpy должен быть указателем на отдельно выделенную память достаточного размера. И в FFI есть метод для выделения памяти: FFI::new. Его и надо использовать вместо str_repeat.
Таким образом правильная работа с указателями должна выглядеть примерно вот так:
function memcpy(?string &$dst, string $src):void
{
$len = strlen($src);
// изготавливаем тип, в котором уместится копируемая строка
$type = FFI::arrayType(FFI::type('char'), [$len]);
// выделяем место под изготовленный тип
$destination = FFI::new($type);
FFI::cdef('char *memcpy(char *dst, const char *src, size_t len);')
->memcpy($destination, $src, $len);
// преобразуем область памяти в строку
$dst = FFI::string($destination, $len);
}
$rock = "ROCK";
var_dump($rock); // «ROCK»
memcpy($rock, "SOCK");
var_dump($rock); // «SOCK»Что тут происходит?
Мы хотим вызвать сишную функцию для копирования одной строки в другую. В месте, куда мы собираемся копировать, должно быть выделено достаточное количество памяти, мы её выделяем методом FFI::new, передавая ему на вход специально изготовленный тип — указатель на массив однобайтовых символов длиной c копируемую строку.
После копирования преобразуем область памяти в пхпешную строку. Память, выделенная при помощи FFI::new, освободится автоматически.
PHP 7.4 vs. PHP 8.0a
Резюме — в моём частном случае стало ощутимо быстрее, PHP 7.4 работал 63,9 секунд, 8.0a — 42,6.
Как многие знают, основной фишкой восьмой версии интерпретатора ПХП будет JIT. Обещают ускорение вычислительных задач, что немного не про веб, где в основном используется этот язык, но всё равно выглядит интересно.
В официальном репозитории альфа восьмой версии лежит уже давно, стало интересно сравнить её с «семёркой» — много ли даёт JIT приросту. Для подобных тестов я давно использую свой оптимизирующий транслятор «Брейнфака», с загруженной в него задачей вычисления числа Пи.
Восьмой ПХП собрался на моём «Макбуке» следующей последовательностью команд:
PATH=/usr/local/Cellar/bison/3.5.3/bin/:$PATH ./buildconf
./configure --prefix=/opt/php-bin/ --without-iconv --disable-xml \
--without-libxml --disable-simplexml --disable-xmlwriter --disable-dom --disable-xmlreader
make -j4
make installВерсию 7.4 ставил через «брю», сборочную конфигурацию оставил как есть, ничего не менял.
После небольшой коррекции php.ini в информации об интерпретаторе появились OPcache и JIT:
Вообще свой брейфаковский транслятор
Каждую версию интерпретатора я тестировал со включенным и выключенным OPcache десять раз, а результаты усреднил. Как видите, JIT существенно ускорил выполнение программы в моём случае (там огромное количество циклов и ветвлений) — 63,9 секунды против 42,6.
Что интересно — PHP 8.a без OPcache обогнал версию 7.4 со включенным opcache, это примечательный результат— значит сам «голый» интерпретатор тоже оптимизируют.
Забавный баг в FFI (PHP 7.4.2)
В последнее время очень заинтересовался FFI в ПХП — интерфейсом к языку Си, появившимся в версии 7.4. Очень полезное, как по мне, нововведение, позволяющее расширять язык, обходясь минимальными знаниями о других языках программирования.
Очевидно, потребность
К несчастью, модуль FFI в ПХП
Небольшой код для иллюстрации проблемы:
const IRRELEVANT = "Hello ";
echo IRRELEVANT; // Выведет «Hello»
FFI::cdef('char *memcpy(char *dst, const char *src, size_t len);')
->memcpy("Hello ", "world!", 6);
echo IRRELEVANT; // Выведет «world!»В чём суть? Мы объявляем константу IRRELEVANT, убеждаемся, что она содержит присвоенное ей значение, потом вызываем через FFI копирование одной строки в другую, и константа внезапно меняет своё значение. Как это произошло? Чтобы понять, немного модифицируем программу.
function memcpy(string $dst, string $src):void
{
FFI::cdef('char *memcpy(char *dst, const char *src, size_t len);')
->memcpy($dst, $src, strlen($src));
}
$rock1 = str_repeat('ROCK', 1);
$rock2 = str_repeat('ROCK', 1);
var_dump($rock1, $rock2); // Выведет «ROCK» два раза
memcpy($rock1, "SOCK");
memcpy($rock2, "LOCK");
var_dump($rock1); // выведет «SOCK»
var_dump($rock2); // выведет «LOCK»Что у нас тут? Вызов memcpy обёрнут в функцию, которая ничего не возвращает и ничего не должна модифицировать — параметры передаются по значению, а не ссылке. Тем не менее, если вызвать объявленную функцию и передать в неё переменные, их значение будет изменено.
В ПХП для обеспечения производительности используется механизм копирования при записи — если
В силу этой особенности, переменные остаются ссылочными, попадая в таком виде в сишный код, где модифицируются — вызов memcpy меняет первую переданную в него переменную, копируя в неё значение второго параметра.
Кстати, если изменить строку инициализации переменных на вот такую:
$rock1 = $rock2 = str_repeat('ROCK', 1);то после вызовов memcpy обе переменные получат значение «LOCK» — работает тот же самый механизм.
Почему же в первом листинге изменилось значение константы? Сейчас разберёмся, осталось совсем немного.
Ещё одной распространённой техникой увеличения производительности (и снижения потребления памяти заодно) является интернирование, её используют многие интерпретаторы и компиляторы. В общем случае она заключается в том, что
Таким образом константа IRRELEVANT и строка «Hello » в первом параметре — суть одна и та же область памяти. Когда функция memcpy модифицирует эту область, меняются сразу все значения всего, что, для оптимальности, ссылается на это же место. Естественно, константа изменяется тоже.
Поскольку интернирование не работает во время выполнения, результат str_repeat('ROCK', 1); не интернируется и во втором листинге такого эффекта не создаётся.
Как же разорвать эту мистическую связь и заставить работать код так как задумывалось? Для этого нужно, чтобы интерпретатор создал новую область памяти, которую будет портить FFI, не затрагивая нашу переменную. Это можно сделать, например, при помощи уже упомянутой функции str_repeat:
function memcpy(string $dst, string $src):void
{
// Создаём новое место, которое испортит memcpy
$dst = str_repeat($dst, 1);
FFI::cdef('char *memcpy(char *dst, const char *src, size_t len);')
->memcpy($dst, $src, strlen($src));
}
$rock = 'ROCK';
var_dump($rock); // Выведет «ROCK»
memcpy($rock, "SOCK");
var_dump($rock); // Так же выведет «ROCK»Внимание! Это некорректный способ, корректный описан в более поздней заметке.
Естественно, подойдут и substr, и sprintf, и вообще любые функции, в общем случае возвращающие модифицированную строку. Интересно, что implode([$dst]) от бага не защищает, видимо для этого случая внутри работает
Всё описанное верно для версии актуальной на текущий момент — 7.4.2. Надеюсь, в скором времени этот довольно забавный баг устранят и необходимость в таких ухищрениях исчезнет.
PHP: FFI vs. exec
Нередко отсутствующую в ПХП функциональность наскоро заменяют вызовом утилит командной строки. И мне стало интересно взглянуть на досуге — может ли вызов через ffi (интерфейс к Си, который появился в ПХП 7.4) быть хорошей альтернативой.
Говорят, вызов через ffi работает очень медленно, такова природа этого способа (и мы это видим в других языках), это, конечно, очевидно, но медленно насколько?
Для сравнения была взята функция, создающая временный директорий по маске, которую я использовал в одной из заметок, и её аналог, использующий внешнюю утилиту:
$mkdtemp = function (string $template): string {
$cmd = '/usr/bin/mktemp -d '.escapeshellarg($template);
$dir = exec($cmd, $out, $ret);
if ($ret === 0) {
return $dir;
} else {
['message' => $message, 'type' => $type] = error_get_last();
throw new RuntimeException($message, $type);
}
};Весь код был обёрнут в цикл на 10000 итераций и запущен. Я провёл по десять запусков, а результат усреднил.
Создание директория через ffi заняло примерно 0,77 секунд на 10 тысяч итераций, а через утилиту командной строки — примерно 72,62 секунды на те же 10 тысяч итераций.
Конечно
PHP FFI
В ПХП 7.4 предпринята очередная попытка обзавестись интерфейсом к языку Си. Кажется наконец за это расширение взялись
Для интереса посмотрел что за АПИ получилось. В принципе, всё очень просто. Вот как выглядит
/**
* @method object mkdtemp(string $template)
* @method object strerror(int $errnum)
* @property-read int $errno
*/
$ffi = FFI::cdef('
// Импортируем функцию создания временного директория
char *mkdtemp(char *template);
// Импортируем функцию перевода кодов ошибок в строку
char *strerror(int errnum);
// Импортируем переменную, хранящую коды ошибок
int errno;
');
/**
* Фунция для создания временных директориев по маске
* @param string $template Шаблон создания (см. man mkdtemp)
* @return string Путь до временного директория
*/
$mkdtemp = function (string $template) use ($ffi): string {
$result = $ffi->mkdtemp($template);
if ($result === null) {
$errno = $ffi->errno;
$errstr = $ffi->strerror($errno);
throw new RuntimeException(FFI::string($errstr), $errno);
}
return FFI::string($result);
};Как можно понять из кода, создаётся объект, в который импортируются сущности, перечисленные в сишной нотации в первом параметре. В общем случае вторым параметром требуется указать библиотеку для импорта, но в данном случае работает и так.
Не знаю заработает ли этот код без изменений под Линуксом и Виндоузом, у кого есть под рукой, посмотрите, пожалуйста.
В этом примере параметры в импортированные функции передаются как обычные типы ПХП, а некоторые возвращаемые значения требуют конвертации. Так char * возвращается как специальный объект, который нужно преобразовывать в строку вызовом метода FFI::string.
Вот тривиальный текстовый пример использования созданной выше функции:
$template = implode(DIRECTORY_SEPARATOR, [sys_get_temp_dir(), 'ffiphp.XXXXXX']);
try {
$tmp = $mkdtemp($template);
rmdir($tmp);
echo $tmp, "\n";
} catch (RuntimeException $e) {
echo $e->getMessage(), "\n";
}Суть происходящего проста: если директорий удалось создать, то он будет уничтожен, а его имя — выведено на экран, иначе появится текстовое сообщение об ошибке.
Переход на PHP 7.4
Потихоньку тестируем на работе свой продукт под интерпретатором версии 7.4. Пока релиз не производит впечатления стабильного — в свежих версиях правятся очень неприятные баги. Мы при тестировании нашли два сюрприза, которые не описаны в списке изменений.
$arr = new \ArrayObject(['key' => 'value']);
// теперь выдаёт NULL, а не «key»
var_dump(key($arr));Маловероятно, что
Добавлено позднее: однако я неправ — про прекращение поддержки объектом класса ArrayObject функций работы с массивами вскользь всё же упомянуто.
Шило на мыло
Несколько лет назад, один из программистов написал в нашем продукте интересный код, который я сильно упростил, чтобы показать суть:
<?php
$array = '';
$array[PHP_INT_MAX] = 1;Главное тут в том, что массив
Несмотря на эту странность, всё работало — ПХП на втором присваивании преобразовывал строку в массив.
Оказалось, что в ПХП 7.1 и выше (мы недавно перешли с 7.0 на 7.2), преобразование в массив тут больше не происходит.
Heredoc и nowdoc в PHP
В Перле
$values = [<<<END
a
b
c
END];О чудо, хвостовую конструкцию можно сдвинуть как надо и не нужно обособлять переводом строки справа! При этом внутри лишние пробелы слева урежутся по величине сдвига закрывающей конструкции.
Баг в PHP 7.2 с output_add_rewrite_var
Для того, чтобы не подставлять в каждую ссылку и форму глобальные для проекта параметры запроса (идентификатор сессии, например), в ПХП используется техника, называемая Url Rewriting. У нас в проекте так передаётся токен против XSRF — благо есть возможность указать собственные параметры, которые надо передать и даже задать список тегов с атрибутами в которых это будет работать.
И при переходе на ПХП 7.2 словили неприятный баг, который уже поправлен в 7.1.9, но ещё
<?php
output_add_rewrite_var('foo', 'bar');
?>
<a href="index.php">This is link</a>
<a href="#place">This is anchor</a>В первую ссылку «foo=bar» добавится нормально, а во второй это будет не к месту, якорь будет испорчен:
<a href="index.php?foo=bar">This is link</a>
<a href="#place/?foo=bar">This is anchor</a>Наверняка со стандартными сессиями ПХП, передаваемыми в урле, будет тот же результат, но я не пробовал.
Либо надо ждать обновления, либо поставлять вместо якоря текущий полный урл.
Переезд на PHP7
В почте попросили описать переезд на ПХП7, с какими сложностями встретились, как они решались. Опишу, пока не забыл — я этим занимался почти в одиночку, так как задача не была приоритетной. Я считал, что мы сможем получить хороший прирост производительности, поэтому сам её и делал в свободное время.
Если кому интересно, загруженность серверов, где крутится ПХП, действительно упала, примерно в 1,5—1,7 раза.
Вообще говоря, у ПХП есть довольно детальные описания того, что может пойти не так при смене версий, достаточно заглянуть в раздел «Изменения, ломающие обратную совместимость» нужной версии. Там можно сразу прикинуть что придётся поменять в коде, если вы хорошо его знаете.
Ниже мой собственный опыт.
Сначала я составил список всех модулей ПХП, которые у нас используются — натурально выписал из зависимостей пакета нашего продукта в табличку в местной «вики». На тот момент не все модули поддерживали «семёрку», поэтому к табличке я иногда возвращался и вносил изменения, если они случались.
Когда в табличке без поддержки остался единственный модуль ctwig от шаблонизатора «Твиг», решил, что без него мы можем обойтись — к этому времени в интернете появились замеры скорости и «Твиг» без модуля под «семёркой» демонстрировал похожие результаты с «пятёркой» и модулем.
Дальше я проверил не используются ли у нас
find . -type f \( -name '*.php' -o -name '*.inc' \) -exec php -l {} \; |
fgrep -v 'No syntax errors'У нас, к слову, было несколько таких мест — например, был объявлен класс с разными полезными штуками для обработки строк, который назывался String. Так больше нельзя — зарезервированное слово.
Посложнее с обработкой исключений — класс Exception перестал быть базовым и если требуется ловить все исключения, то в «семёрке» лучше всего перехватывать всё, что реализует интерфейс Throwable.
У нас необработанные кодом исключения ловятся и пишутся в логи, для этого в нескольких местах кода есть перехват всего подряд. Выглядело это примерно так:
try {
// … какой-то код
} catch (Exception $e) {
DI::log->get('exception')->error($e);
throw $e;
}Несложными регулярками поискал различные непрямые выражения, там теперь тоже есть разница, нашёл всего несколько штук и поправил.
Потом я
Далее в дело вступили тестеры и программисты — в основном были исправления, связанные с тем, что в «семёрки» некоторые вещи устарели, ничего сверх этого я не припомню.
В целом мне кажется, переход прошёл безболезненнее, чем можно было бы ожидать —
Особенность PHP 7.2 (и 7.1)
Потихоньку смотрю как наш продукт запускается на ПХП версии 7.2 — у нас сейчас используется 7.0, но очень хочется двинуться дальше. В хитросплетениях кода нашёл очень странный баг интерпретатора, который был разбросан по разным строчкам кода, а в сконцентрированном виде он выглядит так:
$arr = [[1]];
array_walk($arr, function(){});
array_map('array_shift', $arr);
var_dump($arr);В ПХП 7.0 массив выведется в неизменном виде, а в версии 7.2 (и 7.1, как оказалось) единица исчезнет. Очевидно, что array_walk создаёт
ПХП и строгая типизация
В ПХП много странностей, ещё одна дала о себе знать в неожиданном месте. Сначала немного теории.
Во многих языках дозволяется определять функции, методы или их аналоги с необязательными параметрами, их при вызове можно не указывать способом, предусмотренным языком. По моим ощущениям наиболее распространён способ с присвоением таким параметрам значений
ПХП использует эту схему везде, кроме расширений и встроенных функций. Понятие «необязательный параметр» там есть, но обрабатывается иначе — у параметра указывается тип (например «строка»), необязательность и «нулабельность» (можно ли в этом параметре принимать null в качестве значения).
Последнее очень полезно для числовых и булевых типов — если «нулабельность» не указана, то null будет преобразован по правилам языка в значение указанного типа.
У многих функций ПХП в документации указаны значения, которые будут подставлены, если параметр не указан. В тех случаях, когда такое значение не указано, можно было попытаться подставить null, многие расширения это проглатывают.
Например, у нас преспокойно работал примерно такой код:
public function put(Serialized $object, $eventName, $extraEventData, $uniqueId = null)
{
return DI::gearman_client()->doBackground(
$this->queueName,
igbinary_serialize(
[
'object' => $object,
'event_data' => $extraEventData,
'event_name' => $eventName,
]
),
$uniqueId
);
}Всё работало корректно, пока не пришёл ПХП7 и мы не стали потихоньку переползать на строгую типизацию. Вечером я закоммитил изменения в этом файле, которые позволили включить строгую типизацию, а за завтраком поймал в логах странную ошибку, которая сообщала мне, что в метод doBackground время от времени получает в качестве последнего параметра null, а так нельзя.
Сначала я недоумевал, а потом догадался, что случилось — у doBackground последний, необязательный парамер имеет тип «строка» и он не «нулабельный». То есть в строгой типизации я его должен либо не передавать вовсе, либо передавать туда исключительно строку. А null, который передавался туда до перехода на строгую типизацию более не подходит, ибо он не строка.
Пришлось переписать более уродливо:
public function put(Serialized $object, string $eventName, $extraEventData, string $uniqueId = null)
{
$args = [
$this->queueName,
igbinary_serialize(
[
'object' => $object,
'event_data' => $extraEventData,
'event_name' => $eventName,
]
),
];
if ($uniqueId !== null) {
$args[] = $uniqueId;
}
return DI::gearman_client()->doBackground(...$args);
}Странно то, что у необязательного параметра нет никакого значения
Не смог найти, но я помню, что было
💢 OCI8: проблемы с переходом на PHP7
Ещё в копилку проблем с ПХП7: если при привязке переменных в Оракле (bind) поля типа LONG/LOB не находятся в конце списка, то вы получите ошибку
💢 Проблема с переходом на PHP7: Memcached, часть вторая
В прошлый раз я писал о проблемном Мемкешд в ПХП7 и оказалось, что я не совсем прав. Проблема есть, но её корень я понимал неверно.
Черновым кодом это выглядит примерно так (должно работать, но я его не запускал):
if (version_compare(phpversion('memcached'), '3.0.0-dev', '<')) {
// работаем по-старому
return $memcached;
} else {
// возвращаем обёртку
return new class($memcached) {
use \Core\ProxyTrait;
public function __construct($mc)
{
$this->setObject($mc);
}
public function get($key, callable $cache_cb = null, &$cas_token = null)
{
$result = $this->obj->get($key, $cache_cb, Memcached::GET_EXTENDED);
if ($result === Memcached::GET_ERROR_RETURN_VALUE) {
return false;
}
if ($result) {
$cas_token = $result['cas'];
return $result['value'];
}
return $result;
}
public function getMulti(array $keys, array &$cas_tokens = null, int $flag = null)
{
$result = $this->obj->getMulti($keys, Memcached::GET_EXTENDED | $flag);
if ($result === Memcached::GET_ERROR_RETURN_VALUE) {
return false;
}
if ($result) {
$values = [];
$cas_tokens = [];
foreach ($result as $key => $d) {
$values[$key] = $d['value'];
$cas_tokens[$key] = $d['cas'];
}
return $values;
}
return $result;
}
}
}Вся мякотка в последнем параметре Memcached::GET_EXTENDED, он заставляет возвращать соответствующие методы не искомое значение, а массив, содержащий в том числе и cas.
Неприятно, что в этой версии ПХП модуль для работы с Мемкешд помечен как «разработческий». В этом свете мне
💢 Проблема с переходом на PHP7: Memcached
ПХП7 — огромный шаг для интерпретатора ПХП в плане производительности и потребления памяти, поэтому есть большой соблазн начать переводить на него свои продукты. К сожалению, возросшие показатели дались не бесплатно, а путём сломанной в некоторых местах обратной совместимости, самая яркая проблема, которая из этого вытекает — все модули без исключения надо модифицировать.
Поэтому этот шаг, к ПХП7 очень труден для больших проектов, вроде тех, который разрабатываем мы. Тем не менее, мы медленно, но верно движемся в финишу. Вчера столкнулись с неожиданной сложностью, на которую мне пришлось потратить вечер.
Оказалось, что в модуле Мемкешд для ПХП7 нет реализации получения токена cas в методах get и getMulti (наверняка нет ещё
Method [ <internal:memcached> public method get ] {
- Parameters [2] {
Parameter #0 [ <required> $key ]
Parameter #1 [ <optional> $cache_cb ]
}
}Как видите, параметра cas нет вообще (он должен быть последним). Это печальное обстоятельство подтолкнуло меня к исследованию и к ночи я сделал решение. Возможно
class MemcachedPHP7
{
use \Core\ProxyTrait;
public function __construct($mc)
{
$this->setObject($mc);
}
public function get($key, callable $cache_cb = null, &$cas_token = null)
{
switch (func_num_args()) {
case 1:
return $this->obj->get($key);
case 2:
return $this->obj->get($key, $cache_cb);
default:
if ($this->obj->getDelayed([$key], true) === false) {
return false;
}
$res = $this->obj->fetchAll();
if ($res === false || !$res) {
if ($cache_cb !== null) {
if ($cache_cb($this->obj, $key, $value)) {
$this->obj->set($key, $value);
}
} else {
$value = false;
}
} else {
$cas_token = $res[0]['cas'];
$value = $res[0]['value'];
}
return $value;
}
}
public function getMulti(array $keys, array &$cas_tokens = null)
{
if (func_num_args() === 1) {
return $this->obj->getMulti($key);
} else {
if ($this->obj->getDelayed($keys, true) === false) {
return false;
}
$res = $this->obj->fetchAll();
if ($res === false) {
return false;
}
$cas_tokens = [];
$values = [];
$results = array_column($res, null, 'key');
foreach ($keys as $key) {
$cas_tokens[$key] = $results[$key]['cas'];
$values[$key] = $results[$key]['value'];
}
return $values;
}
}
}Трейт ProxyTrait я тут не привожу, там идея простая — он тупо проксирует всё, что получает через магические методы __get, __set, __call и прочие, setObject — метод этого трейта. Очень удобно, если надо оставить всё как есть, за исключением
В остальном всё основано на том, что в методе getDelayed реализация токена cas есть, его я и использую, чтобы заткнуть эту дыру в функциональности. Работает всё так же как в ПХП 5.6, за исключением того, что в методе getMulti нет реализации последнего параметра — флага, вместо этого всё работает так, как будто он установлен, это ничему не мешает.
Void в PHP
ПХП7
Я сначала не понял зачем вводить в язык новое ключевое слово, можно было бы использовать для такого указания уже существующее ключевое слово null, но оказалось, что авторы под void имели ввиду, что функция не может вернуть никакого значения:
function returns_null(): void {
return null; // Fatal error: A void function must not return a value
}включая null, потому что это определённо значение, хоть и со специальным смыслом. При этом вот такое работает:
function returns_nothing(): void {
return; // valid
}Таким образом в языке произошло неявное изменение: раньше ситуации с пустым return и с return null не различались, сейчас они будут иметь разный смысл.
Отрицательное количество элементов (PHP)
Сегодня утром с разработчиками обсуждали фрагмент кода, где автор очень уж параноидально подошёл к проверке:
if (count($this->to) <= 0) {
$this->validation_errors[] = "Неверный отправитель";
}Проверять возвращает ли функция count значение меньшее нуля действительно странно, но я вдруг подумал — а можно ли в ПХП в припципе заставить эту функцию вернуть такое значение?
Дело в том, что функцию count можно использовать не только с примитивными типами (чаще всего её используют с массивами), но и с объектами, которые реализуют интерфейс Countable.
Оказалось вполне нормально работает:
$var = new class implements \Countable {
public function count()
{
return -1;
}
}
var_dump(count($var)); // выведет int(-1)Выше код написан в синтаксисе ПХП7, который выйдет сегодня, но его можно переписать и на «пятёрку» — надо только дать имя классу.
Моя маленькая лепта в PHP7

Читал исходники ПХП7, заметил небольшую ошибку в функции копирования потоков (stream), сегодня мой коммит перенесли в код.
PHP7: scalar hinting
Продолжаю потихоньку наблюдать за развитием ПХП 7. Сегодня с утра выписывал из репозитория и компилировал новую версию, заметил, что в языке появилось указание типов для скаляров — поддерживаются int, float, string и bool.
Выглядит всё довольно естественно:
<?php
class Test
{
public function __toString()
{
return "test";
}
}
function len(string $a): int {
return strlen($a);
}
echo len(new Test);
echo len(new stdClass); // Fatal error: Argument 1 passed to len() must be of the type string, object givenВ данном случае первый вызов ошибку не вызовет (в отличие от второго), так как у объекта есть магический метод __toString, который вызывается, когда объект требуется привести к строке, так что всё логично. Возможные преобразования описаны в документации и ничего нового там нет, всё в соответствии с текущей логикой языка.
Есть и второй режим работы — строгий. Его можно включить при помощи конструкции declare, которая должна быть первой конструкцией языка (теперь у неё приоритет даже над namespace) в файле:
<?php
declare(strict_types=1);
namespace Test;
class Test
{
public function __toString()
{
return "test";
}
}
function len(string $a): int {
return strlen($a);
}
echo len(new Test); // Fatal error: Argument 1 passed to Test\len() must be of the type string, object givenВ этом случае все проверки на тип становятся строгими и никаких неявных преобразований не допускается.
По правде говоря, мне больше импонирует второй режим — несколько лет программирования на «Гоу» и «Пайтоне» позволили оценить все прелести строгой типизации, но существующий код большого продукта привести за приемлемое к строгому режиму будет часто невозможно, на мой взгляд. Видимо поэтому создатели языка ограничили действие строгого режима рамками файла — новые части можно писать, не затрагивая поведение старых, это удобно.