php
Знание PHP
В рамках разговора о знании языка (ПХП, в данном случае), родился у меня такой пример:
var_dump(b"1" == 0b1);Что тут будет — true или false и почему?
«Глаголы» в PCRE
В известной библиотеке «PCRE», реализующей
Но есть и другие, на самом деле их довольно много, причём их применение очень разнообразно. Мне редко ими приходится пользоваться, вероятно по той причине, что я всё реже пользуюсь регулярными выражениями вообще. Впрочем, смотря с чем сравнивать.
Самые популярные «глаголы» в моём рационе (если исключить UCP), это — ACCEPT и FAIL. Сейчас на небольших примерах из документации расскажу что это.
«Глагол» ACCEPT даёт понять библиотеке, что дальше можно не искать — найденное нас устроило. Вот смотрите:
A((?:A|B(*ACCEPT)|C)D)Если подставить в эту регулярку «ACD», то ничего необычного не произойдёт: совпадение выглядит ожидаемым способом: A((?:A|B(*ACCEPT)|C)D), выглядит так, как будто строка для проверки всегда обязана заканчиваться на «D», по если передать сюда «AB», то совпадение тоже будет: A((?:A|B(*ACCEPT)|C)D) — «глагол» ACCEPT говорит о том, что дальше можно не продолжать, остаток нас не интересует.
«Глагол» FAIL позволяет достичь ровно противоположного и, в частности, сформировать отрицание, которое другими средствами добиться тяжелее и представляет сложность для новичков в регулярках.
A(?:BCD(*FAIL)|.*?)EЕсли передать сюда строки «ABCE», «AEEE», «AwhateverE» совпадение произойдёт по следующим правилам шаблона: A(?:BCD(*FAIL)|.*?)E, но если передать «ABCDE», то совпадения не будет, вступит в силу «глагол» FAIL: A(?:BCD(*FAIL)|.*?)E.
Эти и другие «глаголы» (кроме MARK) отлично работают в ПХП, потому что фукнции «preg_*» как раз используют библиотеку PCRE у себя внутри.
Defer в PHP
В языке «Гоу» есть превосходная конструкция «defer», которая запускает переданное ей выражение, когда заканчивает выполняться текущий блок. Вот как это может выглядеть:
profile, e := os.OpenFile(oProfile, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0777)
if e == nil {
defer profile.Close()
profile.WriteString("0: 0 0 0 0 ;\n0: 1 8 0 2 ;\n0: 9 63 0 2 ;\n0: 1 63 2 1 ;\n0: 1 63 1 0;")
}Это маленький пример из одной моей реальной программы. Как только управление покинет текущую функцию, вызовется метод Close у profile и файл автоматически закроется. Пример маленький и всех удобств не раскрывает, но надеюсь они, как на ладони: если у вас много условий при которох управление может покинуть блок, то вам не нужно уничтожать все свои ресурсы в каждой такой ситуации, достаточно их один раз описать через «defer» и всё сработает само.
Одна из причин любить «Гоу».
В «Пайтоне» есть похожая концепция: у класса могут имется «магические» методы «__enter__» и «__exit__», которые срабатывают, соответственно, на входе и выходе в блок, но блок не любой, а специальный:
with open("/etc/passwd", "r") as f:
print(f.read(10))Объекты, у которых реализованы оба этих метода, могут быть вызываны из специальной конструкции «with», при входе в которую будет вызван метод «__enter__», при выходе — «__exit__», это нагляднее, чем в «Гоу» и тут невозможно забыть освободить
В ПХП так сделать нельзя, но можно на шаг приблизиться к этому, если использовать деструктор специального объекта, но это и близко не то же самое, что в вышерассмотренных случаях. В «Пайтоне» не зря выделили специальный метод, срабатывающий на выходе, а не обошлись деструкторами, которые (в отличие от «Гоу») в этом языке есть: «__exit__» вызовется гарантированно в момент выхода, тогда как деструктор — когда придётся.
Иногда это может быть очень важным, потому при поздних использованиях того же ресурса, возможно, нам придётся долго ждать когда сборщик мусора доберётся, наконец, до нашего объекта и вызовет деструктор, где освободится повторно нужный нам, но эксклюзивно заблокированный ресурс.
В общем, хорошо, что в ПХП нет такого встроенного класса, его бы сразу начали активно использовать, что привело бы к куче сложноуловимых «багов». Вот это средоточие зла:
class Defer
{
private $funcs;
public function __construct(callable $func)
{
$this->funcs = [$func];
}
public function __destruct()
{
foreach ($this->funcs as $func) {
call_user_func($func);
}
}
public function __invoke(callable $func)
{
array_unshift($this->funcs, $func);
}
}Очень простой класс с тремя методами. Как же его можно использовать? (никак! не надо! нет!). Пример ниже, он, конечно, мегасинтетический, но что вы хотите от «совы» в семь утра, у меня ещё
function doSomePHP()
{
for ($i = 0; $i<10; $i++) {
$fp = fopen("/tmp/$i.tmp", "wb");
$zone = new Defer(function() use ($fp) {
fclose($fp);
});
// кстати, обратите внимание, а вот так можно сделать в Hack — это диалект PHP, здорово же:
// $zone = new Defer(() ==> fclose($fp));
// тут какой-то код и куча проверок, причём мы можем выйти в любой момент, а fclose вызовется
fwrite($fp, "My number is $i");
}
}Что тут происходит? У нас тут, как видно, цикл, в котором открываются на запись 10 файлов, в каждый из них выводится некая строка, а посередине торчит неиспользумая переменная с моим новым объектом. В объект передаётся функция, которая будет вызываться (посмотрите на код объекта) в деструкторе. Как только переменная zone перетрётся (следующим её значением, у нас же цикл или при выходе из функции), объект будет помечен к удалению
Важно заметить две вещи: переменная, содержащая объект, должна затираться в нужное нам время и деструктор вызовется обязательно, но когда — мы не знаем. Первое — недостаток, второе — фатальный недостаток. Оба неустранимые.
Для чего нужен третий метод в класс Defer — «__invoke__»? В языке он появился для поддержки анонимных функций и вызовется, если попытаться вызвать объект, как функцию.
Тут я хочу кивнуть на ещё один хороший язык программирования — «Раст». В нём есть такая интересная штука как «Named lifetimes», не знаю как это перевести литературно, скажем «именованный срок жизни».
Напрягитесь, сейчас будет немного тяжело.
fn select<'r, T>(shape: &'r Shape, threshold: f64,
a: &'r T, b: &'r T) -> &'r T {
if compute_area(shape) > threshold {a} else {b}
}Выше код на «Расте», взятый из руководства по языку. Тут «’r» — это именованный срок жизни. Сроки жизни всех переменных, помеченных одним именем должны пересекаться. Очень грубо говоря, это значит, что язык не должен уничтожать одну переменную с тем же именем жизни раньше других. Пример выше вызовет ошибку компиляции — видно, что возвращаемое функцией значение пытается пережить остальные переменные с тем же именем —
Это позволяет хорошо мирить в языке указатели и сборку мусора, а так же, уже на этапе компиляции ловить некоторые сложные ошибки.
Вернёмся к ПХП. Тут, конечно, всё и близко не так и цели другие, но немного похоже. Упомянутый мною метод был введён для постоения цепочек уничтожения. Например нам надо освободить ресурсы в определённом порядке (например, нужно прибрать за собой временные файлы и только потом убрать блокировку с lock-файла):
function doAnything()
{
$fp = fopen('/var/lock/do.lock', 'r');
flock($fp, LOCK_EX);
$zone = new Defer(function() use ($fp) { fclose($fp); });
// делаем что-то полезное от чего остался мусор
$zone(function() use ($tmpfiles) { array_map('unlink', $tmpfiles); });
// делаем что-то ещё
}В общем, я считаю, что языку ПХП пошла бы на пользу языковая конструкция, близкая к defer «Гоу», поскольку она очень гармонично решает проблемы процедурного программирования — эффективной работы с процедурным АПИ внешних ресурсов (а в ПХП оно почти всё такое).
PHP: хинтование возвращаемого типа
Вероятно уже в следующем году в языке можно будет указывать возвращаемый тип довольно стандартным образом:
function foo(): array {
return [];
}Такой синтаксис встречается во многих языках и понятен сразу. Указать можно будет (стандартно для ПХП): имя класса, «array» или «callable». Надеюсь,
Пара слов для чего это надо:
Конструкция «??»
В ПХП 7 появилась, наконец, конструкция, которая должна была быть в языке с первых версий:
echo $_GET['var'] ?? 'default'; // PHP 7
echo isset($_GET['var']) ? $_GET['var'] : 'default'; // PHP 5.6 и нижеОчень похоже на тернарный оператор без средней части, появившийся в версии 5.3, но с одним отличием: проверяется не ложь или истина, а существование.
Теперь в ПХП можно писать намного более эмоционально:
echo X-D ? :-D ??!! O_o; // это работает в PHP 7, я даже могу сказать что это значит.Может
git clone https://github.com/php/php-src.git
cd php-src
brew install re2c
brew info bison27
PATH=/usr/local/opt/bison27/bin/:$PATH
./buildconf
./configure --prefix=/opt/php-5.7 --disable-all
make
make installJSOND
JSOND — это ещё один «дешёвый» способ увеличить производительность вашего проекта на ПХП, наряду с igbinary. Исходный код есть на PECL, для всех систем существуют пакеты. Насколько я знаю, есть планы по замене стандартного расширения JSON в ПХП7 на JSOND.
Для интереса погонял стандартный тест пакета на одной из наших машин (мы там вчера перешли на ПХП 5.5
STR: {"i": 23, "array": [1, null, false, true, ["aha", "baba", 23, {"test": 23}]]}
JSON: time for 100000 iterations: 1.096384
JSOND: time for 100000 iterations: 0.353658
STR: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
JSON: time for 100000 iterations: 0.700904
JSOND: time for 100000 iterations: 0.202519
STR: {"a": 23.2234232}
JSON: time for 100000 iterations: 0.398129
JSOND: time for 100000 iterations: 0.083977
STR: сериализуется очень длинная строка
JSON: time for 100000 iterations: 0.392990
JSOND: time for 100000 iterations: 0.341663
STR: сериализуется переменная $_SERVER
JSON: time for 100000 iterations: 4.983888
JSOND: time for 100000 iterations: 2.321528Что приятно, АПИ полностью совместимо, так что проблем нет никаких — в вызовах надо просто добавить одну букву. Есть так же несложный способ самостоятельной замены стандартного JSON на этот модуль в интерпретаторе.
Процедурное настоящее PHP
ПХП, к сожалению, в сердце ещё глубоко процедурный язык. Наткнулся на очередную особенность. Сначала небольшая предыстория.
Многие знают, что FastCGI в ПХП не настоящий — при каждом запросе весь код интерпретируется заново, это позволяет не заботиться об утечке памяти, сбросе состояния между запросами и ещё о многих вещах, а значит упрощает вообще всё, что соответствует духу ПХП — понизить до минимума порог вхождения. Конечно, это сказывается на производительности, но в некоторых случаях ПХП научился смягчать этот недостаток.
Например, есть такая вещь как «постоянные соединения» (persistent connections) — это специальные режим, в котором на уровне процесса PHP FastCGI соединения
Конечно,
Как известно, ПХП может свалиться в фатальной ошибкой по многим причинам, одна из них — превышение времени обработки скрипта. Соответственно, скрипт может упасть и в середине транзакции, она останется незакрытой и может остаться висеть долгое время, пока соединение не получит скрипт, в котором встретится команда закрытия транзакции.
А пока этого не произошло, блокировки на базе могут привести к коллапсу.
Как показывает практика, должен, но не обязан. Ниже простой код:
class ImportantToClose {
public function __destruct()
{
// закрываем что-то важное
echo "Вызвался деструктор\n";
}
}
$itc = new ImportantToClose;
register_shutdown_function(function() {
echo "Вызвалась shutdown function\n";
});
set_time_limit(1);
for(;;);Если запустить его в интерпретаторе и подождать секунду, то мы получим следующую ошибку:
PHP Fatal error: Maximum execution time of 1 second exceeded in fatal.php on line 18
Fatal error: Maximum execution time of 1 second exceeded in fatal.php on line 18 Вызвалась shutdown function
Можно ещё попробовать записать
Кстати, не забудьте выставить внутри shutdown function нужное вам время выполнения, если предел будет превышен, то и эта функция будет прервана, в этом случае вам уже ничего не поможет закрыть ресурс корректно.
Недокументированный Memcached-2
У модуля Мемкешед для ПХП удивительно плохо с документацией, я иногда заглядываю в его исходники, с целью почитать. Недавно наткнулся на очередную опцию, описания которой в документации нет (причём в документации аналогичного модуля для Пайтона она есть).
Оказываются UDP не единственный способ ускорить работу с Мемкешед, если вам скорость важнее результата операции. Способ хорош тем, что используется протокол TCP (т. е. с гарантированной доставкой), но мы не ждём — нам главное, что
Посмотрите на пример и обратите внимание на опцию OPT_NOREPLY.
$m = new Memcached;
$m->addServer(0, 11211);
$val = str_repeat(file_get_contents('/etc/passwd'), 200);
$m->set('key', $val);
$m->setOption(Memcached::OPT_NOREPLY, true);
$m->setOption(Memcached::OPT_COMPRESSION, false);
$time = microtime(true);
$m->append('key', $val);
echo number_format(microtime(true) - $time, 4, ',', ''), "\n";Кстати, такое большое (как в примере) значение мне потом обратно прочитать не удалось, видимо превысил
Интересно, что можно делать несколько операций append подряд, очерёдность их сохраняется, корректность прочитанного — тоже. Вот только непонятно не может ли чтение получить данные между двумя append, скорее всего — может, но этот способ как раз для случаев, когда на первом месте скорость записи к кеш.
Преобразование tnsnames.ora в JSON
По работе сделал разбор файла tnsnames.ora — файла оракловой конфигурации на ПХП в массив:
function Ora2Array($str)
{
// убираю комментарии
$str = preg_replace('/^\s*#.*$/m', '', $str);
// преобразование скаляров
$str = preg_replace('/\((\w+)\s*=\s*([^()]+)\)/m', '"$1": "$2", ', $str);
// преобразование ассоциативных массивов
do {
$str = preg_replace('/\((\w+)\s*=\s*([^()]+?)[\s,]*\)/sS', '"$1": { $2 },', $str, -1, $count);
} while ($count);
// преобразование верхнеуровневых значений
$str = preg_replace('/(\w+)\s*=\s*([^()]+?)[\s,]*(?:(?=\w+\s*=)|\s*$)/s', '"$1": { $2 },', $str);
return json_decode('{' . trim($str, ' ,') . '}', true);
}Не разбираются значения в кавычках — такого в наших конфигурациях не встречается, но добавляется несложно.
Memcached и UDP
Когда я написал про поддержку модулем Мемкешед для ПХП протокола UDP, я ещё не знал многих подробностей. Например, я думал, что ребятам из Мемкешед
Оказывается ничего подобного — UDP нужен только тогда, когда вас устраивает негарантированная доставка, т. е. вы швырнули пакет, а дойдёт ли он до кеша — неважно, при чтении это будет считаться промахом кеша.
В интернете есть статья, которая кратко раскрывает что это даёт, в ней же приводится патч «multi UDP», который Мемкешеду уже не нужен — я смотрел в код, всё там нормально давно.
Кстати, обнаружились две подробности в поддержке UDP модулем Мемкешед ПХП.
Работоспособный тест выглядит так:
$m = new Memcached('SomeID');
$m->addServer('127.0.0.1', 11211, 1);
$m_udp = new Memcached();
$m_udp->setOption(Memcached::OPT_USE_UDP, true);
$m_udp->addServer('127.0.0.1', 11211, 1);
var_dump($m_udp->set('foo', "test string"));
sleep (1);
var_dump($m->get('foo'));is_numeric
Функция is_numeric одна из традиционно неверно понимаемых программистами на ПХП. Её, обычно, понимаю как «посмотреть содержится ли в переменной
Беда в том, что понятия числа у ПХП и человека различаются. Посмотрите, все эти вызовы рассматриваемой функции вернут «true»:
var_dump(is_numeric("123e123")); // true — десятичное число в экспоненциальной записи
var_dump(is_numeric("0123")); // true — восьмеричное число
var_dump(is_numeric("0XDeadBeef")); // true — шестнадцатеричное число
var_dump(is_numeric(NAN)); // true — специальное численное значение «не число»Особенно не везёт восьмеричным числам, которые во многих языках (и в ПХП) тоже начинаются с нуля: формально выглядят как обычное десятичное (только начинается с нуля), проходят все проверки, а после преобразования теряют ведущий ноль и это может быть важно.
С незапамятных времён существует хак, который позволяет убедиться, что перед нами именно десятичное число, пусть и в строковой форме:
function is_number($var) { return (string) (int) $var === (string) $var; }Принцип очень простой — переменная преобразуется в целый тип (можно преобразовывать и в тип с плавающей точкой, если это нужно), потом берётся строковое представление получившегося числа. На этом этапе восьмеричные, шестнадцатеричные и прочие виды записи чисел будут преобразованы в целочисленный тип, строковое представление которого — десятичное число. Справа же пришедшее остаётся в первозданном виде.
Далее мы сравниваем левую и правую часть и, если они совпадают, значит к нам пришло именно десятичное число, так как никаких преобразований с ним не произошло.
Добавлено позднее: в документации к функции написано, что она так же понимает двоичную форму записи введёную недавно (число должно начинаться с «0b»), к сожалению, это неправда — в коде поддержки нет, баг заведён и в нём написано, что это проблема документации, а не функции.
Интересно ещё, что is_numeric пропускает любое количество пробельных символов слева, но плохо относится к ним справа:
var_dump(is_numeric("\f\f\t0xBadBabe")); // true
var_dump(is_numeric("1\t")); //falseНедокументированные возможности модулья Memcached (для PHP)
Читая на досуге исходники модуля Memcached для ПХП, наткнулся на неожиданное: оказывается этот модуль поддерживает соединение по протоколу UDP, причём документация молчит об этом.
Делается это вот так:
$udp = new Memcached();
$udp->setOption(Memcached::OPT_USE_UDP, true);
$udp->addServer($host, $port);В частности, это позволит нам перейти на
Другая вещь, которая никак не раскрывается документацией — пользовательские флаги (UDF — user defined flag). К каждому значению можно прикрепить один или несколько флагов, которые при получении значения будут складываться в заданную переменную:
const FLAG = 1;
const ANOTHER_FLAG = 2;
$m->set($key, 'value', 0, FLAG | ANOTHER_FLAG); // установили флаг
$m->get($key, null, null, $flags); // флаги будут лежать в переменной $flagsУ каждой функции установки и получения значений (а их несколько, этих функций) в конце есть необязательный параметр для работы с флагами. Так что во всех вариантах использования значения можно
Позднее дополнение: похоже memcached больше не нуждается в multiport UDP patch, он и без него теперь с UDP работает нормально.
PHP7
Всё, PHPNG отправился в «мастер», по всей видимости, это означает, что он появится сразу после версии 5.6, которую мы увидим буквально на днях (я уже немного писал о том, что в ней нового).
PHPNG — новая версия интерпретатора, которая, по всей видимости, будет носить номер семь, чтобы избежать путаницы с PHP6 — неудавшейся попыткой перевести ПХП на Юникод, которую широко в своё время освещали, но сообщество решило от неё отказаться — большинство разработчиков англоязычны и выгоды от этого трудоёмкого процесса не видели.
Новая версия ПХП интересна переработанными внутренностями, изменение которых связано со скоростью. Установленный «ВордПресс» работает на новой версии в два раза быстрее, что впечатляет, конечно. Плохая новость — придётся переделать бо́льшую часть модулей (если не вообще все). Процесс этот сейчас движется, но только для модулей, входящих в язык, несколько модулей (в том числе важный для меня OCI8) пока ещё не поправлены.
Думаю, что миграция авторских модулей произойдёт довольно быстро.
Передача массива как аргументов в конструктор
Одна из вещей,
$result = call_user_func_array('myfunc', $args); // старый синтаксис
$result = myfunc(...$args); // PHP 5.6+Но помимо приятности, он решает две проблемы.
switch (count($params)) {
case 0:
$instance = new $class();
break;
case 1:
$instance = new $class($params[0]);
break;
case 2:
$instance = new $class($params[0], $params[1]);
break;
// и так далее…
default:
$refClass = new \ReflectionClass($class);
$instance = $refClass->newInstanceArgs($params);
break;
}В новом же синтаксисе код невероятно упрощается:
$instance = new $class(...$params);А, кроме того, становится более явным и простым. Так что очень радуюсь этому новому синтаксису.
Чудно́е в PHP
На работе сейчас с любопытным багом в ПХП разбирались. Разобрались, вот выжимка:
interface I {}
var_dump(class_exists('cI')); // false
var_dump(class_exists('C')); // true
class cI implements I {}
class C {}
var_dump(class_exists('cI')); // true
var_dump(class_exists('C')); // trueВыходит, если класс реализует интерфейс, то его не существует выше строки, где он декларируется, класс без реализации интерфейса виден в любом месте. Версия ПХП — 5.5.11. Я для интереса попробовал на 5.6а1 (есть под рукой собранный) — работает так же.
И и И (логические выражения в PHP)
В ПХП есть два набора команд булевой логики — многим более привычные «&&» и «||», но кроме них — напоминающие о Бейсике и Эскюэле «and» и «or». Даже программирующие на языке годами часто не знают чем они отличаются, между тем употребление второго набора может привести к трудноуловимым багам, потому что ведут они себя чуть иначе.
Вся разница заключается в приоритетах выполнения, большинство языков, разбирая выражение, опирается на приоритеты выполнения, например, умножение приоритетней сложения, этому нас учат в школе, в ПХП и многих других языках — так же: 2 + 2 × 2 = 6, а не 8.
Так вот. Набор «and» и «or» — чуть ли не самые низкоприоритетные из всего, что может встретиться, а «&&» и «||» находятся
// какая-то переменная, значение которой в коде,
// к этому моменту, стало true
$check = true;
// а это другая контрольная переменная, где-то в коде
// она приобрела значение false
$criteria = false;
// и вот мы подходим к главному
$ret_bool = $criteria || $check;
$ret_logic = $criteria or $check;
var_dump($ret_bool, $ret_logic);Будут ли отличаться значения переменных ret_bool и ret_logic после выполнения программы? Конечно будут, иначе я бы не писал этут статью, неправда ли? Значение первой будет true, второй — false.
Почему так? Приоритеты! ПХП, исполняя предпоследние две строчки кода видит их
$ret_bool = ($criteria || $check);
($ret_logic = $criteria) or $check;Исполнение идёт строго по приоритетам, так как во втором случае операция присваивания имеет больший приоритет, чем «or», первым выполняется именно она, а проверка переменной check осуществляется, но её результат отбрасывается.
Это отлично подходит для программировая в «хакерском» стиле:
$ret_logic = $criteria or CheckSomething();Функция CheckSomething() вызовется только в том случае, если criteria будет равна false или эквиваленту, но зато результат функции никак не повлияет на значение ret_logic.
Впрочем, я такой способ программирования крайне не рекомендую — для современных программистов он сложен.
Тип ключей в PHP
Наткнулись с коллегами на странную вещи в ПХП: тип имени свойства. Непонятно? Давайте посмотрим на код. Как вы думаете, есть ли
$first = (object) [ 1 => 1 ];
$second = new stdClass(); $second->{1} = 1;В первом случае массив получает тип «объект» (и в этом случае ПХП создаёт экземпляр класса stdClass, используя ключи и значения массива как имя и значение свойства соответственно), во втором случае сразу создаётся экземпляр класса и его свойству с именем «1» присваивается значение.
Невооружённым взглядом различий не видно, но если попытаться обратиться к этим свойства, то сразу увидим
var_dump(
$first->{1}, // выведет «Notice: Undefined property: stdClass::$1»
$second->{1} // выведет значение «1»
);При этом свойство существует: var_dump обоих объектов его покажет и после преобразования переменной в массив, мы так же получим искомое. Тем не менее у первого объекта нельзя никаким образом обратиться к этому свойству: удалить его, прочитать, перезаписать! Всё проясняет следующий код:
$first = (object) [ 1 => 1 ];
$first->{1} =2;
var_dump($first);
/* у нас получился объект, у которого два свойства с одним именем,
но разными типами(!) имени, у одного он строка, у второго — число
object(stdClass)#1 (2) {
[1]=>
int(1)
["1"]=>
int(2)
}
*/
var_dump((array) $first);
/* попытка преобразовать его в массив, приводит к появлению
неожиданного уродца — массива у которого два ключа с одним и тем
же именем, но разными типами…
array(2) {
[1]=>
int(1)
["1"]=>
int(2)
}
*/
$arr = [ 1 => 1 ];
$arr["1"] = 2;
var_dump($arr);
/* …который вряд ли можно создать каким-то ещё способом
array(1) {
[1]=>
int(2)
}
*/К свойству с числовым именем нельзя обратиться по имени, у ПХП просто нет конструкций для этого! Тем не менее, var_dump и foreach его видят! У массива всё ровно наоборот — по имени нельзя обратиться к свойству со строчным именем, если есть числовое.
Вот такие неожиданные открытия в ПХП. Я думаю, что это сделано для того, чтобы сохранить тип ключа массива в преобразованиях из объекта и обратно, но это решение теперь даёт странные сторонние эффекты. Например, перебирая такой массив через foreach можно столкнуться с ситуацией, когда удалить свойство из объекта по имени не удаётся. Или при итерации массива по известным ключам, мы будем получать одно и то же значение два раза (возвращаться всегда будет только то, которое записано по числовому имени).
В общем, масса интересных граблей насыпана в этом месте! Счастливой прогулки!
PHP 5.6alpha3
Я уже писал о том что появилось в ПХП 5.6. Четыре дня назад вышла новая альфа, а с её появлением в языке одним магическим методом стало больше. Добавился метод «__debugInfo», теперь он вызывается функцией var_dump выводится объект.
У объектов и ранее была возможность влиять на вывод этой функцией, но тогда это было доступно только для экземпляров, классы которых были написаны на Си, теперь же эта возможность есть в ПХП.
Пример использования:
class File {
// «ресурс (поток)» — не очень-то информативно…
private $fp;
// …в отличие от его метаданных
public function __debugInfo() {
return $this->fp ? stream_get_meta_data($fp) : [];
}
public function open($filename, $mode = 'r'){
$this->fp = fopen($filename, $mode);
}
}
$f = new File;
var_dump($f); // object(File)#1 { }
$f->open('http://php.net');
var_dump($f);
/*
object(File)#1 {
["wrapper_type"]=>
string(4) "http"
["stream_type"]=>
string(10) "tcp_socket"
и так далее...
*/Мне тут же стало интересно — можно ли сделать метод __debugInfo приватным? Оказывается нельзя, интерператор ругается:
Warning: The magic method __debugInfo() must have public visibility and cannot be static in /Users/bolk/example.php on line 3
object(Dumb)#1 (0) {
}И, кстати, сразу нашёл баг — стоит в этом магическом методе вернуть себя же (массив, состоящий из переменной $this), как var_dump зацикливается, а если вернуть $GLOBALS получим Segmentation fault: 11.
В принципе, это закономерно — ведь это
«Функция» include
Один из читателей в комментариях к статье про ошибочное толкование пхпешной конструкции «print» как функции, поделился более интересным случаем — таким же заблуждением относительно include.
Речь идёт об использовании include в выражениях, например:
$x = include("value.php") + 1;Хотя я ни разу не сталкивался с этой проблемой (зато видел проблемные ситуации с print), пример, готов признать, более жизненный — такое использование имеет смысл и наверняка встречается нередко.
Тут используется
Пример же иллюстрирует ровно ту же проблему, что и в случае с print: если программист не знает в чём различие конструкции и функции, он посчитает, что тут к возращаемому значению добавится единица.
На самом деле, скобки не являются часть конструкции include (она в скобках не нуждается), для неё скобки — просто часть единого аргумента «(„value.php“) + 1» и на вход include поступит значение «1», а это вряд ли то, чего программист добивался в этом примере.
В документации к ПХП написано как следует поступать в этом случае:
// неправильно, будет трактоваться как include(('vars.php') == 'OK'), т.е. include('')
if (include('vars.php') == 'OK') {
echo 'OK';
}
// а вот так — правильно
if ((include 'vars.php') == 'OK') {
echo 'OK';
}«Функция» print
Одним из распространённых заблуждений программистов на ПХП является то, что конструкция print является, якобы, функцией. На самом деле никакая она не функция — ей даже скобки не нужны, чтобы работать. Но с функцией print роднит то, что при вызове в составе выражения она всегда возвращает «1».
Весь ужас этого заблуждения как раз в использовании этой конструкции со скобками. Я программирую на ПХП уже около 15 лет, но всего пару раз за это время видел как это заблуждение рождает проблемы, но зато какие!
Я, понятное дело, напишу утрированный случай, чтобы проиллюстрировать проблему. Как думаете, что выведет следующий код?
var_dump(print("xaxa") + 1);По идее, должна выполниться «функция» print, вывести «xaxa», вернуть «1», которая сложится со второй единицей и var_dump выведет их сумму — «2».
Как бы не так! На самом деле, с точки зрения конструкции print (которой не нужны скобки) ей передали аргумент «(„xaxa“) + 1». При их складывании левая часть преобразуется к числу и получается «0», сумма нуля и единицы — «1», вот print его и напечатает, потом вернёт единицу, которую напечатает var_dump.