php

PHP 5.6

Немного времени осталось до выхода PHP 5.6. Похоже, авторы взяли в привычку добавлять в каждой версии в язык несколько нововведений. Сейчас эта версия в стадии альфы, это означает, что до беты может добавиться что-то ещё, но то что уже есть, радует глаз.

Во-первых, появилась операция возведения в степень. Ничего особо примечательного, но циклы, где используется степень можно будет сделать побыстрее — вызов функции в ПХП относительно дорогая операция.

echo 2**3, "\n", // восемь
-2**2, "\n", // минус четыре
2 ** 3 ** 2; // 512

«Крышка» (^), которая во многих языках занята под степень, в ПХП занята под «исключающее или», поэтому сделали две «звёздочки», как в Пайтоне. Из приведённых примеров мы видим, что «вес» унарного минуса ниже, чем у степени — он применяется уже к результату, об этом надо помнить, лично я бы ожидал, что в степень будет возведено отрицательное число. И, кстати, сама операция является правоассоциативной (последний пример).

Во-вторых, у модуля gmp (модуль для работы с большими числами) появились перегруженные операторы. Вся арифметика (включая новопоявившуюся степень) работает привычным порядком, как у обычных чисел:

$a = gmp_init(2);
$b = gmp_init(3);

$c = $a**2 + $b;

echo $c, "\n"; // выведет 7
var_dump($c); // покажет объект класса GMP, у которого есть свойство num=7
echo array_sum([1,2,gmp_init(-3)]); // 3, печаль

Кроме этого, переход к объектам (от ресурсов) позволит сериализовать эти числа, что является хорошим бонусом. Увы, не всё ещё гладко — последний вызов (где суммируется массив) возвращает число три, а не ноль.

В-третьих, константы (задаваемые через ключевое слово const) теперь вычисляемые. Никакого противоречия тут нет — это по-прежнему не переменные, просто их значением может теперь быть вычисляемое единожды выражение. Это позволяет выражать одни константы через другие. Особого новшевства здесь нет — через define так можно было делать и раньше:

const alf = 2;
const bolk = alf*alf . alf; // работает новый синтаксис

echo bolk, "\n"; // 42

define("TEST", alf*alf . alf); // работает старый синтаксис
echo TEST, "\n"; // 42

В-четвёртых, в ПХП появился новый синтаксис функции с переменным количеством аргументов. Они были и раньше, но не было способа определения фукнции таким образом, чтобы остальные аргументы при вызове (сколько бы их не оставалось) попали в определённую переменную (для этого мы использовали func_get_args):

// сколько бы аргументов после первого не было, они попадут в $params массивом
function query($query, ...$params) {
    $stmt = $this->pdo->prepare($query);
    $stmt->execute($params);
    return $stmt;
}

Синтаксис с тремя точками используется в Си, Джаве и новой версии Джаваскрипта. Что интересно, с новым синтакисом работает и указание типа аргумента, и передача по ссылке. Ограничения этого синтаксиса вполне понятные — такой аргумент должен быть всегда последним (и/или единственным), задавать значения по-умолчанию нельзя:

// всё это неправильно:
function fn(...$args, $arg) // такой агрумент не первый
function fn(...$args1, ...$args2) // не единственный
function fn($arg, ...$args = []) // задано значение по-умолчанию

Выбор именно такого синтаксиса основывался на следующих вещах:

// выглядит как будто мы требуем, чтобы $args был массивом, что бесполезно — $args уже
// массив, по определению; на самом деле этот синтаксис должен иметь значение «каждый переданный
// в $args элемент — массив»
function fn(array $args...)
// будет путать ребят, которым приходится программировать и на Си тоже — очень похоже
// на указатель
function fn(Char *$args)
// в Си# используется ключевое слово «params», для ПХП не подходит, так как будет путаница
// с указанием типа
function fn(params $args)

В-пятых, нам больше не нужна неуклюжая конструкция в виде call_user_func_array. При помощи предыдущего синтаксиса можно передавать массивы в функции, раскрывая их значения в набор аргументов:

$a = $b = 2;
$c = ['a', 'b'];

var_dump(call_user_func_array('compact', $c)); // прежний синтаксис
var_dump(compact(...$c)); // новый синтакисис
var_dump(compact(...$c, 'c')); // можно ещё и так, будут переданы как аргументы 'a', 'b' и 'c'

var_dump(...new ArrayIterator([1, 2, 3])); // даже так работает! утиная типизация во всей красе
var_dump(...[1 => 1, 3 => 1]); // будет переданы два параметра, без пропусков в номерации

var_dump([...$c, $a]); // а вот так, увы, нельзя
list($k, ...$d) = $c; // так тоже нельзя
echo ...$c; // даже так нельзя!

К сожалению, ПХП традиционно не радует согласованностью синтаксиса и последняя группа выражения не работает. Кстати, синтаксис в тремя точками улучшает картину производительности вызова функций с переменным числом аргументов.

Но даже с этими недостатками, нововведение хорошее. Посмотрите как чудесно выглядит каррирование в новом синтаксисе:

function bind(callable $function, ...$boundArgs) {
    return function(...$args) use($function, $boundArgs) {
        return $function(...$boundArgs, ...$args);
    }
}

$arrayToLower = bind('array_map', 'strtolower');

$arrayToLower(['Foo', 'BAR', 'baZ']); // вернёт ['foo', 'bar', 'baz']

14 комментариев

Локальные переменные

В прошлом я несколько лет программировал на Перле, очень любил этот язык и неплохо знал, до сих пор могу без особых проблем читать программы на нём. Прошло немало лет, наиболее часто я теперь использую ПХП и Джаваскрипт, на Перле не пишу вовсе (хотя в «Яндексе» был краткий эпизод — написал небольшой пример для документации к какому-то сервису).

Вообще я сейчас считаю, что будущее скорее за языками с лаконичным синтаксисом (Пайтон), чем за тем, у кого он сверхнасыщен (Перл, Руби).

Тем не менее, некоторых вещей из Перла мне до сих пор нехвает в других языках. Я научился без этого обходиться, но много лет меня не отпускает чувство, что у меня чего-то нет, что было раньше — как будто трогаешь языком ямку, где раньше был зуб.

Например, локальные переменные. В ПХП часто приходится смотреть (особенно, если достаётся код-лапша), не определяется ли выше и не используется ли ниже переменная с каким-нибудь глубокомысленным названием — «temp», «i» или что-то в подобном роде. Ну мне тоже хочется что-то временное сохранить и в цикле какой-то счётчик использовать, причём посредь чужого кода.

В Перле такой проблемы нет — там локальную переменную можно создать почти в любом месте, произвольном месте кода, просто поставив фигурные скобки — так создаётся новый лексический блок, у которого могут быть свои локальные переменные:

my @a = (1, 2, 3);
my @b = ('a', 'b', 'c');

for my $i (@a) { # первая переменная $i
     for my $i (@b) { # вторая переменная $i, перекроет первую только внутри цикла
         print $i . "\n";
    }

    {
         my $i = 'end'; # третья переменная $i, перекроет первую только в этом блоке
         print $i . "\n";
    }

    print $i . "\n"; # выводится первая переменная $i
}

Конечно, я не призываю именно так писать код — во вложенных циклах с одной и той же переменной легко запутаться, просто иллюстрирую принцип.

В Джаваскрипте ситуация меняется, появилась конструкция «let» (впрочем, раньше её можно было эмулировать), а ПХП ничего похожего так и нет. Я переписывался с разработчиками много лет назад на эту тему, но воз и ныне там.

Сейчас есть возможность опубликовать предложение, описав его в виде RFC — эта новая процедура появилась недавно, если предложение понравится, то синтаксис имеет все шансы попасть в язык. Надо будет как-нибудь заняться.

Оставить комментарий

Проблема в стемере для PHP

В модуле stem для ПХП обнаружилась неожиданная проблема. Этот модуль — стеммер, позволяющий обрезать окончания слов для того, чтобы искать потом по тексту без учёта падежей и склонений.

Для русского языка в нём целых две функции — для кодировки КОИ-8 и УТФ-8. Наш код работает в КС1251, потому для стеммера мы перегоняем текст в УТФ-8.

Я обратил внимание, что из командной строки код с этим модулем работает нормально, а в рабочем проекте кодировка портится — как будто стеммер где-то внутри себя рассматривает УТФ-8 как однобайтовую русскую кодировку и приводит её к нижнему регистру.

У меня возникла мысль, что дело в выставляемой локали, мы провели эксперимент — так и есть, дело в этом. Такой код работать правильно не будет:

setlocale(LC_TIME, 'ru_RU.cp1251');
echo stem_russian_unicode(iconv('windows-1251', 'utf-8', 'Приветы'));

Кодировка получится битой. Перед вызовом функций стеммера нужно переставлять кодировку (setlocale) в УТФ-8. Ошибочное место в модуле ищется элементарно:

// файл stem.c
     php_strtolower(z->p, arglen); // ← вот искомый вызов
     stem(z);
     z->p[z->l]= '\0';

// … ищем эту функцию в исходниках PHP…
// string.c из PHP
    PHPAPI char* php_strtolower(char * s, size_t len) {
        unsigned char *c, *e;

        c = (unsigned char *)s;
        e = c+len;

        while (c < e) {
            *c = tolower(*c); // ← а это проблемное место
            c++;
        }
         return s;
    }

Согласно манам, функция tolower ориентируется в своей работе на локаль, в этом и проблема.

7 комментариев

Igbinary и PHP serialize

Если вкратце, модуль igbinary в ПХП — замена встроенным функциям сериализации. Встроеные функции хороши только в одном — сериализованное значение получается читаемым (хотя, на мой взгляд, JSON всё же получше будет).

Igbinary пренебрегает читаемостью (это вообще бинарный формат) в пользу скорости и компактности. Вчера ночью я перевёл пару мест в одном из проектов нашей компании, а утром мой друг, И. Б., кинул мне ссылку на статью, где сравнивается скорость работы встроенных функций и igbinary, причём в тестах на сохранение строк последний сливает первому.

Удивительный результат, так как, например, с числами всё иначе — там разница в несколько раз в пользу igbinary (в случае типа с плавающей запятой — более почти в 20), что же за проблема со строками?

У меня возникло подозрение, что я знаю в чём дело. Обычно, когда я изучаю новый модуль, я смотрю так же и на его настройки. Вчера я тоже их просматривал (в этом модуле их всего-то три) и хорошо запомнил одну из них — «igbinary.compact_strings».

Опция хорошая — помогает экономить память (например, при сериализации в memcached), работает она очень просто — при сериализации, если в данных попадается строка, проверяем не встречалась ли такая же строка ранее, если встречалась, повторно строку не сохраняем, сохраняем лишь ссылку на предыдущее вхождение.

По-умолчанию она включена и очевидным образом должна замедлять сериализацию строк. Я решил её выключить и посмотреть насколько быстрее станет. Результаты на скриншоте — слева опция выключена, справа — включена.

Результаты тестов (19.54КиБ)

Как видите, разница разительная. Так что если вы ставите igbinary для скорости, то эту опцию лучше бы выключить.

4 комментария

Числа из PHP в MongoDB

Не знал, что официальный драйвер Монги для ПХП передаёт числа в Монгу как строки.

Долго не понимал почему удаление строк из коллекции срабатывает почти сразу, а не через заданное время. Наконец, догадался посмотреть на получившийся индекс, оказалалось, что все числа там — строки (в том числе и период через который значения удаляются).

А чтобы получились числа их надо завернуть в специальный объект:

$mongo->ensureIndex([
    'date' => new MongoInt32(1)
], [
    'expireAfterSeconds' => new MongoInt32(7200)
]);

Вот как выглядит правильный индекс:

Правильный индекс (12.97КиБ)

В неправильном числа будут окружены апострофами.

11 комментариев

PHPFuck

JSFuck у нас уже был (это намёк на язык Брейнфак, а не ругательство), почему бы не изобрести PHPFuck?

Часа полтора потратил на скрипт, который выводит слово «Hello!»:

<?
$__=$_[$_[]++].=$_;$_[!_]+=++$_[!_];@$$_=($_[$$_][$_]&$_[$$_][$_[!_]]).$_[$$_][$_];
$_[]=&$$_;@$$_.=$_[!!_][$__]^$__[$_[!_]]|$__[!_];$___=$__[!_]|_&~$__[!!_];
@$$_.=++$___.($__[$_]^_^$__[$_[!_]]).($_[!!_][!$_]^$_[_==_][$_[!_]>>!!_]^_);
$___&=$__[$_[!_]];$___.=${$_}[!_]^${$_}[$_[!_]]|$__[!_].($$$_=$__[!_]^_^$__[!!_]).
$$$_.($_[_]=$__[$_[!_]]^_^${$_}[$_[!_]>>!!_]);$$_($___.
(~${$_}[~-$_[!_]]&$___[$_[!_]]).($_[_]&~$___[!!_]));

Это настоящий код на PHP, он работает и выполняет заявленное. За вдохновение спасибо неизвестному автору бэкдора, как только я увидел код, понял, что так написать можно что угодно.

Что вкратце происходит у меня. Я создаю массив, привожу его к строке, получается слово «Array» (это такая особенность PHP, если кто не знает), потом при помощи бинарных операций над буквами (с буквами можно использовать «~», «&», «|», «^», а так же операцию инкремента, получая другие буквы), собираю из этого слова слово «printf» (точнее «prINTf», функции в PHP регистронезависимые), которое в дальнейшем будет использовано в качестве имени функции. Потом из двух слов — «Array», «prINTf» и знака подчёркивания теми же бинарными преобразованиями получаю «Hello!» с переводом строки.

Например, буква «N» получается операцией «A» | «_» | ~«r» с последующим инкрементом результата, а «l» (латинская «эл») — «A» ^ «r» ^ «_». Подчёркивание можно использовать без кавычек — все незакавыченные идентификаторы PHP рассматривает как строки. Кое-где там используются похожим образом получаемые цифры, но это уж совсем не интересно рассказывать.

В конце просто вызываю функцию, передавая ей полученную строку.

12 комментариев

Немаловажное про генераторы в PHP

Наткнулся на важный пост о генераторах в ПХП, я их попробовал использовать, ещё когда они были в альфе. Хорошая штука, мне после Пайтона их в языке не хватало.

Суть же рассматриваемой проблемы в следующем. Смотрите, вот функция, которая возвращает построчно файл и пример её вызова:

function getLines($file) {
    $f = fopen($file, 'r');
    while ($line = fgets($f)) {
        yield $line;
    }
    fclose($f);
}

foreach (getLines("file.txt") as $line) {
     echo $line;
}

Пока всё чудесно, ничего странного и необычного, а теперь представьте, что нам нужно прочитать только четыре первые строки:

foreach (getLines("file.txt") as $n => $line) {
    if ($n > 5) break;
    echo $line;
}

Если в файле их было больше, то в функции getLines вызов fclose не выполнится никогда — до него программа просто не дойдёт, файл останется открытым, память занятой и так далее. Плохо. Что делать?

Оказывается, документация обходит этот вопрос стороной, а вот автор введения в ПХП этой конструкции позаботился о том, чтобы эту тему раскрыть — если прочитать соответствующий RFC, становится ясно как быть.

Оказывается, при высвобождении ссылки на генератор, внутри его обязаны выполнится все блоки finally, т. е. вышеприведённый код нужно переписать следующим образом:

function getLines($file) {
    $f = fopen($file, 'r');
    try {
        while ($line = fgets($f)) {
            yield $line;
        }
    } finally {
        fclose($f);
    }
}

15 комментариев

Пятничная загадка для PHPшников

В ЖЖ увидел прекрасное — загадку про ПХП.

Те, кто знает этот язык, скажите, почему этот код не выдаёт ошибок или предупреждений (все ошибки, конечно, включены):

<?php
//bolknote.ru
?>

16 комментариев

Оптимизирующий транслятор Brainfuck → PHP

Грех было не запустить свою же программу из предыдущего поста на собственном стареньком (2005 года) оптимизирующем трансляторе Брейнфака в ПХП. Оказалось, оттранслировал его неправильно.

Нашёл баг (неверно транслировалась последовательность «[<]» в некоторых случаях), исправил, почистил, убрал наследие ПХП 4, ввёл небольшую новую оптимизацию и выхожил на ГитХаб.

История этого интерпретатора тянется ещё с 2001-го года и сайта bf.kzn.ru, который ныне не существует. На нём у меня работал jBF, транслятор Брейнфака на Джаваскрипт и это был первый в мире интерпретатор Брейнфака, который использовал оптимизирующие техники. Вышло аж тринадцать версий интерпретатора и он довольно неплохо уделывал аналоги.

В 2005 году я переделал его на ПХП и попробовал предложить на ПЕАР (к тому времени у меня там уже было несколько модулей), но там он не прошёл предворительный осмотр, сказали, что такой модуль не нужен.

Так он у меня и валялся в разделе «Храню», пока я не запустил сегодня на нём свою программу. Должен, кстати, сказать, что интерпретатор держит марку, например, вот вычисление числа «Пи» на моём трансляторе и интерпретаторе на Джаве:  

BFi vs PHP BF (15.14КиБ)

Хотя, может быть, Джава просто медленно стартует.

Заодно, кстати, на своей же программе нашёл баг в расцветке Брейфака, которую я делал для библиотеки highlight.js.

2 комментария

Чудны́ дела твои, PHP!

Может быть кто-то заметил, что у меня аватары в комментариях отваливались, я довольно долго не мог найти время и посмотреть что происходит, а вчера, вот, поправил. Дело оказалось в том, что мой хостер выставил директиву open_basedir и копирование аватара с интернета на файловую систему отвалилось.

Я делал это функцией copy, первом аргументом передавал УРЛ, вторым — путь на файловой системе. В логах, после изменения конфигурации, ПХП ругался, что «http:» — это неправильное начало для разрешённого пути. Удивительно, но стоило заменить copy// на её, фактически, эквивалент, как всё заработало:

if ($fin = fopen($url, 'rb') and $fout = fopen($file, 'wb')) {
    stream_copy_to_stream($fin, $fout);

    fclose($fout);
    fclose($fin);

    return true;
} else {
    return false;
}

Такое поведение больше похоже на баг, ПХП у меня не последний (5.3.21 сейчас), может поправили уже.

2 комментария

О стоимости доступа к объектам в PHP

Удивительное — рядом: недавно искали с нашими админами место в коде где больше всего тормозит вывод одной страницы одного из сайтов, сделанных в недрах нашей компании в незапамятные времена.

Код был довольно запутанный, попросили меня помочь. Место я вычислил. Если его закоментировать, страница начинала выдаваться не за десятки секунд, а за единицы.

Никогда такого не видел. Как вы думаете, что тормозило? Запросы к базе? Какая-то суперматематика? Сортировка, может быть? Цикл в цикле в цикле, а там внутри что-то сложное?

Вовсе нет.

После выборки из базы, данные выкладывались в массив сложных объектов, в данном месте это было массив из почти что 500 объектов, внутри объектов лежали другие объекты, а них — ещё. Заполнение этой структуры занимало немало времени, но тормозило другое — цикл перебора массива объектов с выводом свойств объектов третьей вложенности.

В цикле не было вызвано ни одной функции (кроме htmlspecialchars), а вывод всего этого хозяйства занимал порядка десятков секунд.

13 комментариев

Неразборчиво в ОБД «Мемориал»

Я думал, что в ОБД «Мемориал» записаны данные только тех карточек, данные которых удалось разобрать достоверно. Случайно выяснил, что очень много данных с пропусками.

Результат запуска (24.33КиБ)

Решил написать сканер неполных данных, чтобы не перебирать их руками. Никого из родственников, правда, не нашёл, но мало ли, пригодится ещё.

Оставить комментарий

Финализированные конструкторы под разными именами

Вчера вечером подумалось, что если в PHP сделать следующее, будет ошибка или нет:

class A {
    final public function a() {
        echo 1;
    }
}

class B extends A {
    public function b() {
        echo 2;
    }
}

$o = new B();

Интересно тут то, что не сразу видно в чём вообще загвоздка. Ну или я бы увидел не сразу, думаю, если бы сам этот пример не придумал. Кажется, все уже отвыкли в PHP от «старых» конструкторов, где конструктором считается метод, имя которого совпадает с именем класса (если нет метода «__construct»).

От осознания этого факта дополнительно отвлекают ничего незначащие имена классов и методов.

Проблема же в том, что в первом классе конструктор финализирован, его дальше переопределять нельзя. Но у второго класса имя конструктора другое. Вот и вопрос — что будет? От PHP можно чего угодно ожидать.

Тем не менее, тут язык выдаст логичную ошибку, которая, правда, способна вызвать лёгкое недоумение своей формулировкой:

Fatal error: Cannot override final A::a() with B::b() in test.php on line 12

3 комментария

Наведение порядка в PHP (версия 5.5.0)

Нужно ещё похвалить авторов PHP за то, что они действительно стараются навести порядок в языке. С каждой версией он становится немного логичнее.

Например, теперь уже можно сделать вот так:

[
    POW => function($x) { return $x*$x; },
    SIN   => function($x) { return sin($x); },
][$selector]

Правда, всё ещё нельзя вызвать функцию сразу. В 5.5.0 будет работать вот такая вещь:

if (empty(somefunc())) { /* …  */}

Но то же самое нельзя сделать с isset, так как сложно сказать что это будет значить — в языке нет отдельного неопределённого значения переменной, она бывает только недекларированной; функция, конечно, может её вернуть, но на самом деле вернётся NULL, а isset от значения NULL — «ложь».

Появилась возможность использовать list внутри цикла следующим образом:

foreach ($somehash as list($key, $value)) { /* … */ }

И сделано ещё несколько мелочей, но работы впереди ещё много.

8 комментариев

Генераторы в PHP 5.5

Самым большим нововведением (и очень важным для языка) в PHP 5.5.0 будут генераторы. Я установил себе альфа-версию 5.5.0, хотел посмотреть поближе что да как, генераторы мне нравятся в Пайтоне и я рад, что PHP ими обзаведётся.

Посмотрите на код, который я написал, пока мы ждали всех участников встречи, на которую я был приглашён:

<?
function enumerate($arr)
{
    $i = 0;

    foreach ($arr as $value) {
        yield [$i++, $value];
    }
}

function ifilter(callable $predicate = null, $arr)
{
    if ($predicate === null) {
        $predicate = 'boolval';
    }

    foreach ($arr as $value) {
        if ($predicate($value)) {
            yield $value;
        }
    }
}

function islice($arr, $start, $stop)
{
    if (is_array($arr)) {
        reset($arr);

        for ($i = $start; $i > 0 && each($arr); $i--);

        for ($i = $stop - $start + 1; $i > 0 && list(,$value) = each($arr); $i--) {
            yield $value;
        }
    } else {
        for ($i = $start; $i > 0 && $arr->valid(); $i--) {
            $arr->next();
        }

        for ($i = $stop - $start; $i > 0 && $arr->valid(); $i--) {
            yield $arr->current();
            $arr->next();
        }
    }
}

$array = [10, 20, 30, 33, 40, 50, 60];

$gen = islice(
    enumerate(
        ifilter(
            function($x) { return ($x % 10) === 0; },
            $array
        )),
        1, 4
    );

foreach ($gen as $value) {
    print_r($value);
}

Три генератора, пропускающие через себя данные, обрабатывают массив, всё работает. Названия взяты из Пайтона. Первый нумерует входящий поток данных (возвращает пары номер и значение), второй фильтрует значения через функцию, третий отрезает заданную часть от входных данных.

Концепция генераторов проста, как герань. PHP поддерживает два способа их создания (как и Пайтон), но мне милее оператор yield. Работает он примерно как return, но при повторном входе в функцию её выполнение происходит не сначала, а с того места, откуда управление было возвращено при помощи yield. Можно ещё передавать значения внутрь генератора, но я про это сейчас не буду. Вцелом, получаются настоящие ленивые вычисления — всё это вызывается ровно тогда, когда это реально нужно.

Генераторы, обычно, работают с итерируемыми объектами — возращают их и/или принимают. Итерируемый объект, проще говоря, это то, по чему можно пробежаться циклом. Генераторы, кстати, тоже итерируемые.

И вот тут опять проявляются недостатоки PHP: отсутствие общей логики, непродуманность.

Массив тут до сих пор примитивный тип и он требует иной способ обработки. Посмотрите на генератор islice, тут проблема видна во всей красе. Начинается она уже с хинтинга, я не могу потребовать от $arr быть типа array, потому что на вход может прийти другой генератор, и указать класс генератора тоже не могу — массив ему не принадлежит. Думаю, следует ожидать появления нового типа «iterable», как это уже случилось с callable.

Вторая проблема ещё более серьёзная. У массива нет методов (он же примитивный тип), а генераторы, почему-то, не работают в процедурном стиле. Поэтому мне приходится делить логику на две части: отдельно писать её для массива и для генераторов. Если я задумаю поддержать ещё и строки (в Пайтоне они отлично итерируются), то нужно будет добавить ещё и третью ветку.

Мне кажется, PHP следует развивать класс ArrayObject, который уже присуствует в языке и делать прозрачное преобразование примитивных типов в объекты (как это происходит в JavaScript), это решило бы некоторые проблемы.

6 комментариев

WEBP в PHP

WEBP (31.41КиБ)

Когда я делал биндинг GD для языка «Гугл Гоу», заметил, что в ПХП появилась поддержка формата WEBP. О самом формате как-нибудь в другой раз (пока можно почитать «Википедию»), скажу только, что многообещающий формат картинок для веба, который в настоящий момент поддерживается «Хромом» и «Оперой» (а в ФФ можно дёшево добавить поддержку через Джаваскрипт).

Чтобы поддержка формата (пока довольно куцая) появилась, нужно установить библиотеку libvpx и пересобрать ПХП с ключом —with-vpx-dir. У меня на «Маке» это выглядит так:

./configure --with-vpx-dir=/usr/local/Cellar/libvpx/1.1.0 \
… остальные опции
make
sudo make install

Правда в phpinfo информация как у меня на картинке не появится (это я сам немного пропатчил исходники), не пугайтесь — так должно быть, проверить наличие поддержки WEBP можно следующим образом:

var_dump(function_exists('imagewebp'));

Собственно, в языке появятся две новые функции: imagecreatefromwebp и imagewebp. У первой только одни аргумент — имя файла из которого надо создать ресурс GD (по действию ничем не отличается от любой другой подобной функции, например imagecreatefromjpeg), вторая принимает два параметра — ресурс и необязательные имя файла и качество, описание функции полностью совпадает с imagejpeg, с той только разницей, что сохраняет она формат WEBP.

Параметр качества изменяется от 0 до 100 и соотвествует параметру VP8 QP кодека в диапазоне от 1 до 63.

15 комментариев

Код для защиты GIF, PNG и JPEG (к предыдущей статье)

В коментариях к предыдущей заметке о моём предложении по защите графических форматов от внедрённого кода один из читателей попросил привести код, который показывал бы как моё предложение работает.

Для форматов GIF, JPEG и PNG я за полчаса написал следующее (язык — PHP 5.4 и выше):

<?
// //bolknote.ru август 2012
// Защищаем изображения, возращает false в случае неудачи
function ProtectPicture($inname, $outname)
{
    // типы изображения, которые мы можем защитить и функции защиты
    $enabled = [
        IMAGETYPE_JPEG => 'Jpeg',
        IMAGETYPE_PNG  => 'Png',
        IMAGETYPE_GIF  => 'Gif',
    ];

    if (list(,, $type) = @getimagesize($inname)) {
        if (isset($enabled[$type])) {
            if ($fi = @fopen($inname, 'rb')) {
                flock($fi, LOCK_SH);

                if ($fo = @fopen($outname, 'wb')) {
                    flock($fo, LOCK_EX);
                    // если все файлы удалось открыть, то вызываем функцию защиты
                    $ret = call_user_func('ProtectPicture'.$enabled[$type], $fi, $fo, filesize($inname));

                    fclose($fo);
                    fclose($fi);

                    if (!$ret) {
                        unlink($outname);
                    }

                    return $ret;
                } else {
                    fclose($fi);
                }
            }
        }
    }
    
    return false;
}

/* Private functions */

function ProtectPictureJpeg($fi, $fo, $controllen)
{
    // заголовок JPEG
    if (!@fwrite($fo, fread($fi, 2))) return false;

    $protect = '<?php __halt_compiler();';
    $length  = pack('n', strlen($protect) + 2);

    // дописываю секцию COM (comment)
    if (!@fwrite($fo, "\xFF\xFE$length$protect")) return false;

    // остаток файла
    if (@stream_copy_to_stream($fi, $fo) != $controllen - 2) return false;

    return true;
}

function ProtectPicturePng($fi, $fo, $controllen)
{
    // заголовок PNG
    if (!@fwrite($fo, fread($fi, 8))) return false;

    // пропускаем кусок IHDR (он должен быть первым))
    $len  = unpack('N', $lenbin = fread($fi, 4))[1] + 8;
    $ihdr = $lenbin . fread($fi, $len);

    // Смотрим, не написал ли в этот кусок хакер свой код
    if (($pos = strpos($ihdr, '<?')) !== false) {
        // в стандарте перечислены поля, которые есть в этом куске — там должно быть 13 байт (+4 — длина)
        if ($len != 17) return false;

        // эта комбинация может встретиться только в первых восьми байтах заголовка — дальше поля не
        // могут принимать такие большие значения
        if ($pos > 11) return false;

        // если разрешены короткие теги, нужно присмотреться к этому коду
        // в столь ограниченном пространстве вред можно натворить только через запуск
        // чего-то внешнего
        if (ini_get('short_open_tag') && strpos($ihdr, '`', $pos + 2) !== false) {
            return false;
        }
    } 

    if (!@fwrite($fo, $ihdr)) return false;

    // кусок iTXt (text) с нашим кодом
    $protect = "<?php __halt_compiler();";
    $length  = pack('N', strlen($protect));
    $sum     = pack('N', sprintf('%u', crc32($protect)));

    if (!@fwrite($fo, "{$length}iTXt{$protect}{$sum}")) return false;

    // остаток файла
    if (@stream_copy_to_stream($fi, $fo) != $controllen - 8 - 4 - $len) return false;

    return true;
}

function ProtectPictureGif($fi, $fo, $controllen)
{
    $protect = "<?php __halt_compiler();";

    // заголовок GIF
    if (!@fwrite($fo, fread($fi, 6))) return false;

    // поле Logical Screen Descriptor
    $lsd  = fread($fi, 7);
    $flag = ord($lsd[4]);

    // указана ли Global Color Table?
    if ($flag & 128) {
        $pixel = 1 + ($flag & 0b111);
        $colors = pow(2, $pixel);
        $gctlen = $colors * 3;

        // читаем Global Color Table
        $gct = fread($fi, $gctlen);

        // не вставил ли уже туда злоумышленник „<?“?
        // меняем у такой комбинации последний бит — это малозаметно,
        // безопасность важнее!
        if (strpos($gct, '<?') !== false) {
            $gct = str_replace('<', '=', $gct);
        }

        if (!@fwrite($fo, $lsd . $gct)) return false;
    } else {
        $gctlen = 0;

        if (!@fwrite($fo, $lsd)) return false;   
    }

    // пишем комментарий с куском защиты
    fwrite($fo, "!\xfe" . chr(strlen($protect)) . $protect . "\0");

    if (@stream_copy_to_stream($fi, $fo) != $controllen - 6 - 7 - $gctlen) return false;

    return true;
}

В коде есть два места где я ищу не встретилась ли комбинация <? раньше, чем я вставляю защиту — это в заголовочном куске (IHDR) в ПНГ и в глобальной таблице цветов у ГИФа.

В случае ПНГ я анализирую заголовок, чтобы понять как там оказалась эта комбинация, она валидна только в первых восьми байтах (на зло остаётся всего 6 байт), кроме того, в этом случае я контролирую размер заголовка, в стандарте там указано 13 байт, хотя, кажется, размер не ограничивается. В общем, в шести байтах особо не разгуляешься, всё что я придумал туда засунуть — `rm *`, т. е. очистка текущего каталога. На этот случай, я контролирую появление символа `. На мой взгляд, вероятность встретить в нормальном ПНГ в заголовке <? после которого идёт ` ничтожно мала.

С ГИФом проще — меняю младший бит у цвета, это вряд ли будет заметно, а безопасность важнее.

10 комментариев

Защита от внедрения PHP-кода в картинки (JPEG, GIF, PNG)

На «Хабре» всплыла тема атаки, старой как мир — заливаем на сайт картинку, у которой внутри (в поле комментариев) есть ПХП-код, потом всякими трюками этот код запускаем. Валидатор картинок на сайте такую картинку пропускает — это вполне валидная картинка.

Во всех форматах принятых в вебе, есть поле, позволяющее вставить такие данные. В JPEG есть специальные секции APPn и COM, в PNG — iTXt, tEXt и можно создавать произвольные секции, в GIF — блоки расширений, в SVG можно использовать обычный XML-комментарий.

В качестве абсолютного метода защиты на «Хабре» предлагается пропустить картинку через какую-либо графическую программу, чтобы убедиться, что все дополнительные данные исчезли. Мне кажется, плохой способ — медленный, а в случае JPEG это ещё и скажется на качестве.

Можно ещё попробовать вырезать все секции файла, куда можно запихнуть текстовую информацию, но это не спасёт от файлов, где ПХП вставлен прямо в графическую часть, так, чтобы детектор валидности картинки (в ПХП для этого обычно используют getimagesize) не спотыкался на файле.

Мне кажется, я придумал более удачный метод защиты.

В загруженный файл можно добавить первой секцией (сразу после заголовка) свой собственный кусок ПХП-кода с вызовом __halt_compiler эта специальная конструкция останавливает интерпретатор в этой точке и всё остальное содержимое файла дальше не интерпретируется:

# смотрим что внутри файла
bolk@Bolk ~ $ hexdump -Cn128 sample.jpg 
00000000  ff d8 ff e0 00 10 4a 46  49 46 00 01 01 00 00 01  |......JFIF......|
00000010  00 01 00 00 ff fe 00 1a  3c 3f 70 68 70 20 5f 5f  |........<?php __|
00000020  68 61 6c 74 5f 63 6f 6d  70 69 6c 65 72 28 29 3b  |halt_compiler();|
00000030  ff db 00 43 00 02 02 02  02 02 01 02 02 02 02 03  |...C............|
00000040  02 02 03 03 06 04 03 03  03 03 07 05 05 04 06 08  |................|
00000050  07 09 08 08 07 08 08 09  0a 0d 0b 09 0a 0c 0a 08  |................|
00000060  08 0b 0f 0b 0c 0d 0e 0e  0f 0e 09 0b 10 11 10 0e  |................|
00000070  11 0d 0e 0e 0e ff db 00  43 01 02 03 03 03 03 03  |........C.......|

# запускаем
bolk@Bolk ~ $ php sample.jpg
????JFIF??

Т. е. на экран выводится бинарный заголовок JPEG, первая секция, а потом интерпретатор дальше просто не идёт. Будет дальше ход хакера или нет, он не проинтерпретируется.

17 комментариев

Патч модуля bcompiler для PHP 5.4

Мы тут по работе пытаемся закешировать байт-код некоторых мест кода, которые будут интенсивно вызываться и редактироваться пользователем из настроек. В ПХП для этого есть модуль bcompiler, но вот печаль — он не компилируется под версию 5.4.

Я посидел над сообщениями при компиляции, посмотрел что другие ребята делали в модуле xcache для того, чтобы он заработал в 5.4, посмотрел внутренние структуры ПХП и родил патч, который позволяет успешно скомпилировать модуль. Модуль мои простенькие тесты проходит, но это правда не значит, что всё действительно работает.

Добавлено позднее: на более сложных тестах модуль всё-таки падает.

Патч и немного кода.

diff -wrdu bcompiler-1.0.2/bcompiler_zend.c bcompiler-1.0.2.new/bcompiler_zend.c
--- bcompiler-1.0.2/bcompiler_zend.c	2011-06-09 15:58:42.000000000 +0400
+++ bcompiler-1.0.2.new/bcompiler_zend.c	2012-07-24 21:59:30.000000000 +0400
@@ -471,12 +471,6 @@
 		apc_serialize_zend_class_entry(zv->obj.ce, NULL, 0, NULL, 0 TSRMLS_CC);
 #endif
 		break;
-#ifdef ZEND_ENGINE_2_4
-	case IS_CLASS:
-	case IS_SCALAR:
-	case IS_NUMERIC:
-		break;
-#endif
 	default:
 		/* The above list enumerates all types.  If we get here,
 		 * something very very bad has happened. */
@@ -551,12 +545,6 @@
 		apc_create_hashtable(&zv->obj.properties, apc_create_zval, NULL, sizeof(zval *) TSRMLS_CC);
 #endif
 		break;
-#ifdef ZEND_ENGINE_2_4
-	case IS_CLASS:
-	case IS_SCALAR:
-	case IS_NUMERIC:
-		break;
-#endif
 	default:
 		/* The above list enumerates all types.  If we get here,
 		 * something very very bad has happened. */
@@ -636,8 +624,10 @@
 	}
 	SERIALIZE_SCALAR(arg_info->allow_null       , zend_bool);
 	SERIALIZE_SCALAR(arg_info->pass_by_reference, zend_bool);
+#ifndef ZEND_ENGINE_2_4
 	SERIALIZE_SCALAR(arg_info->return_reference , zend_bool);
 	SERIALIZE_SCALAR(arg_info->required_num_args, int);
+#endif
 #ifdef ZEND_ENGINE_2_1
 	if (BCOMPILERG(current_write) > 20) {
 # ifdef ZEND_ENGINE_2_4
@@ -667,8 +657,10 @@
 	else ZS2SR(arg_info->class_name) = NULL;
 	DESERIALIZE_SCALAR(&arg_info->allow_null, zend_bool);
 	DESERIALIZE_SCALAR(&arg_info->pass_by_reference, zend_bool);
+#ifndef ZEND_ENGINE_2_4
 	DESERIALIZE_SCALAR(&arg_info->return_reference, zend_bool);
 	DESERIALIZE_SCALAR(&arg_info->required_num_args, int);
+#endif
 #ifdef ZEND_ENGINE_2_1
 	if (BCOMPILERG(current_version) > 20) {
 # ifdef ZEND_ENGINE_2_4
@@ -883,7 +875,9 @@
 #else
 	SERIALIZE_SCALAR(*(zce->refcount)  	     , int);
 #endif
+#ifndef ZEND_ENGINE_2_4
 	SERIALIZE_SCALAR(zce->constants_updated, zend_bool);
+#endif
 #ifdef ZEND_ENGINE_2
 	SERIALIZE_SCALAR(zce->ce_flags, 	 zend_uint);
 #endif
@@ -930,15 +924,15 @@
 	 * and all the functions. */
 	BCOMPILER_DEBUG(("-----------------------------\nBUILTIN:\n"));
 	count = 0;
-	if (zce->type == ZEND_INTERNAL_CLASS && zce->builtin_functions) { 
-		for (zfe = (zend_function_entry*)zce->builtin_functions; zfe->fname != NULL; zfe++) {
+	if (zce->type == ZEND_INTERNAL_CLASS && zce->info.internal.builtin_functions) { 
+		for (zfe = (zend_function_entry*)zce->info.internal.builtin_functions; zfe->fname != NULL; zfe++) {
 			count++;
 		}
 	}
 	
 	SERIALIZE_SCALAR(count, int);
 	for (i = 0; i < count; i++) {
-		apc_serialize_zend_function_entry((zend_function_entry *)&zce->builtin_functions[i] TSRMLS_CC);
+		apc_serialize_zend_function_entry((zend_function_entry *)&zce->info.internal.builtin_functions[i] TSRMLS_CC);
 	}
 #ifndef ZEND_ENGINE_2
 	/** old code: SERIALIZE_SCALAR(zce->handle_function_call, void*); */
@@ -974,12 +968,12 @@
 	/* do reflection info (file/start/end) !! TODO: check for PHP6 !! */
 #ifdef ZEND_ENGINE_2
 	if (BCOMPILERG(current_write) >= 25) {
-		char* fname = bcompiler_handle_filename(zce->filename TSRMLS_CC);
+		char* fname = bcompiler_handle_filename(zce->info.user.filename TSRMLS_CC);
 		apc_serialize_string(fname TSRMLS_CC);
-		SERIALIZE_SCALAR(zce->line_start, int);
-		SERIALIZE_SCALAR(zce->line_end, int);
-		apc_serialize_zstring(zce->doc_comment, zce->doc_comment_len TSRMLS_CC);
-		SERIALIZE_SCALAR(zce->doc_comment_len, int);
+		SERIALIZE_SCALAR(zce->info.user.line_start, int);
+		SERIALIZE_SCALAR(zce->info.user.line_end, int);
+		apc_serialize_zstring(zce->info.user.doc_comment, zce->info.user.doc_comment_len TSRMLS_CC);
+		SERIALIZE_SCALAR(zce->info.user.doc_comment_len, int);
 	}
 #endif
 
@@ -1089,7 +1083,9 @@
 		*zce->refcount = 1; /* val: to avoid memort leaks */
 	}
 #endif
+#ifndef ZEND_ENGINE_2_4
 	DESERIALIZE_SCALAR(&zce->constants_updated, zend_bool);
+#endif
 #ifdef ZEND_ENGINE_2
 	DESERIALIZE_SCALAR(&zce->ce_flags, zend_uint);
 #endif
@@ -1184,11 +1180,11 @@
 	   zend_class_entry.builtin_functions member */
 
 	DESERIALIZE_SCALAR(&count, int);
-	zce->builtin_functions = NULL;
+	zce->info.internal.builtin_functions = NULL;
 	if (count > 0) {
-		zce->builtin_functions = (zend_function_entry*) ecalloc(count+1, sizeof(zend_function_entry));
+		zce->info.internal.builtin_functions = (zend_function_entry*) ecalloc(count+1, sizeof(zend_function_entry));
 		for (i = 0; i < count; i++) {
-			apc_deserialize_zend_function_entry((zend_function_entry*)&zce->builtin_functions[i] TSRMLS_CC);
+			apc_deserialize_zend_function_entry((zend_function_entry*)&zce->info.internal.builtin_functions[i] TSRMLS_CC);
 		}
 	}
 #ifndef ZEND_ENGINE_2
@@ -1237,11 +1233,11 @@
 	if (BCOMPILERG(current_version) >= 25) {
 		char* fname;
 		apc_create_string_u(&fname, -1 TSRMLS_CC);
-		zce->filename = fname ? fname : estrdup(BCOMPILERG(current_filename));
-		DESERIALIZE_SCALAR(&zce->line_start, int);
-		DESERIALIZE_SCALAR(&zce->line_end, int);
-		apc_create_string_u(ZS2SP(zce->doc_comment), -1 TSRMLS_CC);
-		DESERIALIZE_SCALAR(&zce->doc_comment_len, int);
+		zce->info.user.filename = fname ? fname : estrdup(BCOMPILERG(current_filename));
+		DESERIALIZE_SCALAR(&zce->info.user.line_start, int);
+		DESERIALIZE_SCALAR(&zce->info.user.line_end, int);
+		apc_create_string_u(ZS2SP(zce->info.user.doc_comment), -1 TSRMLS_CC);
+		DESERIALIZE_SCALAR(&zce->info.user.doc_comment_len, int);
 	}
 #endif
 
@@ -1564,7 +1560,7 @@
 
 #ifdef ZEND_ENGINE_2_4
 	SERIALIZE_SCALAR(zoa->last_literal, zend_uint);
-	SERIALIZE_SCALAR(zoa->size_literal, zend_uint);
+	//SERIALIZE_SCALAR(zoa->size_literal, zend_uint);
 	for (i = 0; i < (int) zoa->last_literal; i++) {
 		bc_serialize_zend_literal(&zoa->literals[i] TSRMLS_CC);
 	}
@@ -1572,7 +1568,7 @@
 
 	SERIALIZE_SCALAR(zoa->refcount[0], zend_uint);
 	SERIALIZE_SCALAR(zoa->last, zend_uint);
-	SERIALIZE_SCALAR(zoa->size, zend_uint);
+	//SERIALIZE_SCALAR(zoa->size, zend_uint);
 
 	/* If a file 'A' is included twice in a single request, the following 
 	 * situation can occur: A is deserialized and its functions added to
@@ -1595,7 +1591,7 @@
 
 	SERIALIZE_SCALAR(zoa->T, zend_uint);
 	SERIALIZE_SCALAR(zoa->last_brk_cont, zend_uint);
-	SERIALIZE_SCALAR(zoa->current_brk_cont, zend_uint);
+	//SERIALIZE_SCALAR(zoa->current_brk_cont, zend_uint);
 #ifndef ZEND_ENGINE_2
 	SERIALIZE_SCALAR(zoa->uses_globals, zend_bool);
 #endif
@@ -1608,8 +1604,8 @@
 	}
 	apc_serialize_hashtable(zoa->static_variables, apc_serialize_zval_ptr TSRMLS_CC);
 	assert(zoa->start_op == NULL);
-	SERIALIZE_SCALAR(zoa->return_reference, zend_bool);
-	SERIALIZE_SCALAR(zoa->done_pass_two, zend_bool);
+	//SERIALIZE_SCALAR(zoa->return_reference, zend_bool);
+	//SERIALIZE_SCALAR(zoa->done_pass_two, zend_bool);
 	fname = bcompiler_handle_filename(zoa->filename TSRMLS_CC);
 	apc_serialize_string(fname TSRMLS_CC);
 	if (fname) efree(fname);
@@ -1628,9 +1624,9 @@
 #endif
 		SERIALIZE_SCALAR(zoa->fn_flags, zend_uint);
 		SERIALIZE_SCALAR(zoa->required_num_args, zend_uint);
-		SERIALIZE_SCALAR(zoa->pass_rest_by_reference, zend_bool);
+		//SERIALIZE_SCALAR(zoa->pass_rest_by_reference, zend_bool);
 
-		SERIALIZE_SCALAR(zoa->backpatch_count, int);
+		//SERIALIZE_SCALAR(zoa->backpatch_count, int);
 #ifdef ZEND_ENGINE_2_3
 		SERIALIZE_SCALAR(zoa->this_var, zend_uint);
 #else
@@ -1642,7 +1638,7 @@
 #ifdef ZEND_ENGINE_2_1
 	if (BCOMPILERG(current_write) >= 0x0007) {
 		SERIALIZE_SCALAR(zoa->last_var, int);
-		SERIALIZE_SCALAR(zoa->size_var, int);
+		//SERIALIZE_SCALAR(zoa->size_var, int);
 		for (i = 0; i < zoa->last_var; i++) {
 			SERIALIZE_SCALAR(zoa->vars[i].name_len, int);
 			apc_serialize_zstring(ZS2S(zoa->vars[i].name), ZLEN(zoa->vars[i].name_len) TSRMLS_CC);
@@ -1706,7 +1702,7 @@
 
 #ifdef ZEND_ENGINE_2_4
 	DESERIALIZE_SCALAR(&zoa->last_literal, zend_uint);
-	DESERIALIZE_SCALAR(&zoa->size_literal, zend_uint);
+	//DESERIALIZE_SCALAR(&zoa->size_literal, zend_uint);
 	zoa->literals = NULL;
 	if (zoa->last_literal) {
 		zoa->literals = (zend_literal*) ecalloc(zoa->last_literal, sizeof(zend_literal));
@@ -1722,7 +1718,7 @@
 		*zoa->refcount = 1; /* val: to avoid memort leaks */
 	}
 	DESERIALIZE_SCALAR(&zoa->last, zend_uint);
-	DESERIALIZE_SCALAR(&zoa->size, zend_uint);
+	//DESERIALIZE_SCALAR(&zoa->size, zend_uint);
 
 	zoa->opcodes = NULL;
 
@@ -1736,7 +1732,7 @@
 
 	DESERIALIZE_SCALAR(&zoa->T, zend_uint);
 	DESERIALIZE_SCALAR(&zoa->last_brk_cont, zend_uint);
-	DESERIALIZE_SCALAR(&zoa->current_brk_cont, zend_uint);
+	//DESERIALIZE_SCALAR(&zoa->current_brk_cont, zend_uint);
 #ifndef ZEND_ENGINE_2
 	DESERIALIZE_SCALAR(&zoa->uses_globals, zend_bool);
 #endif
@@ -1749,10 +1745,10 @@
 	}
 	apc_create_hashtable(&zoa->static_variables, apc_create_zval, NULL, sizeof(zval *) TSRMLS_CC);
  
-	zoa->start_op = NULL;
+	//zoa->start_op = NULL;
  
-	DESERIALIZE_SCALAR(&zoa->return_reference, zend_bool);
-	DESERIALIZE_SCALAR(&zoa->done_pass_two, zend_bool);
+	//DESERIALIZE_SCALAR(&zoa->return_reference, zend_bool);
+	//DESERIALIZE_SCALAR(&zoa->done_pass_two, zend_bool);
 	apc_create_string_u(&fname, -1 TSRMLS_CC);
 	if (fname) {
 		zoa->filename = zend_set_compiled_filename(fname TSRMLS_CC);
@@ -1798,8 +1794,8 @@
 		zoa->fn_flags &= ~ZEND_ACC_IMPLEMENTED_ABSTRACT; /* this is set later by zend_do_inheritance() */
 #endif
 		DESERIALIZE_SCALAR(&zoa->required_num_args, zend_uint);
-		DESERIALIZE_SCALAR(&zoa->pass_rest_by_reference, zend_bool);
-		DESERIALIZE_SCALAR(&zoa->backpatch_count, int);
+		//DESERIALIZE_SCALAR(&zoa->pass_rest_by_reference, zend_bool);
+		//DESERIALIZE_SCALAR(&zoa->backpatch_count, int);
 #ifdef ZEND_ENGINE_2_3
 		DESERIALIZE_SCALAR(&zoa->this_var, zend_uint);
 #else
@@ -1811,15 +1807,15 @@
 #ifdef ZEND_ENGINE_2_1
 	if (BCOMPILERG(current_version) >= 0x0007) {
 		DESERIALIZE_SCALAR(&zoa->last_var, int);
-		DESERIALIZE_SCALAR(&zoa->size_var, int);
-		if (zoa->size_var > 0) {
+		//DESERIALIZE_SCALAR(&zoa->size_var, int);
+		/*if (zoa->size_var > 0) {
 			zoa->vars = ecalloc(zoa->size_var, sizeof(zoa->vars[0]));
 			for (i = 0; i < zoa->last_var; i++) {
 				DESERIALIZE_SCALAR(&(zoa->vars[i].name_len), int);
 				apc_create_string_u(ZS2SP(zoa->vars[i].name), -1 TSRMLS_CC);
 				DESERIALIZE_SCALAR(&(zoa->vars[i].hash_value), ulong);
 			}
-		}
+		}*/
 	}
 #endif
 
diff -wrdu bcompiler-1.0.2/php_bcompiler.c bcompiler-1.0.2.new/php_bcompiler.c
--- bcompiler-1.0.2/php_bcompiler.c	2011-06-09 15:58:42.000000000 +0400
+++ bcompiler-1.0.2.new/php_bcompiler.c	2012-07-24 21:27:31.000000000 +0400
@@ -571,7 +571,7 @@
 #ifndef ZEND_ENGINE_2
 			if (i > n_old_class)
 #else
-			if (zc->filename && strcmp(zc->filename, real_path) == 0)
+			if (zc->info.user.filename && strcmp(zc->info.user.filename, real_path) == 0)
 #endif
 			{
 				SERIALIZE_SCALAR(BCOMPILER_CLASS_ENTRY, char)
@@ -632,7 +632,7 @@
 #ifndef ZEND_ENGINE_2
 			if (i > n_old_class)
 #else
-			if (zc->filename && strcmp(zc->filename, real_path) == 0)
+			if (zc->info.user.filename && strcmp(zc->info.user.filename, real_path) == 0)
 #endif
 			{
 #if PHP_MAJOR_VERSION >= 6
diff -wrdu bcompiler-1.0.2/php_bcompiler.h bcompiler-1.0.2.new/php_bcompiler.h
--- bcompiler-1.0.2/php_bcompiler.h	2011-06-09 15:58:42.000000000 +0400
+++ bcompiler-1.0.2.new/php_bcompiler.h	2012-07-24 22:13:31.000000000 +0400
@@ -22,7 +22,7 @@
 #ifndef PHP_BCOMPILER_H
 #define PHP_BCOMPILER_H
 
-#define PHP_BCOMPILER_VERSION "1.0.2"
+#define PHP_BCOMPILER_VERSION "1.0.2 (bolk) "
 
 #ifdef HAVE_CONFIG_H
 #include "config.h"

Неудобно только то, что модуль не позволяет работать с объектами и анонимными функциями из ПХП 5.3 (которые тоже объекты), а так же компилирует всё в файл. Но второй недостаток легко обходится:

$fp = fopen('php://memory', 'rw');
bcompiler_write_header($fp);
bcompiler_write_function($fp, 'тут имя вашей функции, которую надо скомпилировать');
bcompiler_write_footer($fp);

fseek($fp, 0);
$compiled = stream_get_contents($fp); // байт-код теперь в переменной $compiled
fclose($fp);

17 комментариев

ПХП: красавица и чудовище

Одна из превосходнейших вещей в ПХП — обработчики протоколов. Они позволяют работать с разнообразными источниками данных как с файлами. Встроенные протоколы умеют понимать ftp, http, data URL, ssh2, rar, zlib и ещё несколько менее известных штук. Т. е. традиционный путь до файла в ПХП на самом деле является УРЛом, по виду которого интерпретатор решает какой обработчик за него ответственен. Если протокол (в терминах УРЛ) не указан, считается, что идёт обращение к локальному файлу. Язык так же позволяет создавать свои обработчики.

ПХП единственный из известных мне языков, где я могу с лёгкостью сделать такие вещи:

copy('ftp://user:pass@example.org/pub/somefile.txt', '/tmp/somefile.txt');
$var = file_get_contents('https://example.org/index.html');
# и при этом
copy('/tmp/somefile', '/tmp/anotherfile.txt');
$var = file_get_contents('/etc/passwd');

Немного более сложный синтаксис позволяет сделать и более сложные вещи:

$post = http_build_query([
    'login' => $login,
    'pass' => $password,
]);

$options = [
    'http' => [
        'method' => 'POST',
        'header' => 'Content-type: application/x-www-form-urlencoded\r\n'.
                           'Content-length: ' . strlen($post),
        'content' => $post,
        'proxy'=>"tcp://192.168.1.1:3128"
    ]
];

$result = file_get_contents('http://example.org/action.php', false, stream_context_create($options));

От полного изящества далеко, но по-прежнему проще, чем во многих других языках (особенно, в Пайтоне); можно даже с прокси работать!

Что сделано из рук вон плохо — это получение заголовков ответа для протоколов HTTP и HTTPS: ответ просто помещается в локальную переменную http_response_header, это печальное и достойное сожаления решение. Вообще-то в языке есть и альтернативы, но эта переменная всё равно будет создана.

Более неявное поведение трудно и выдумать. Оно настолько неявное, что многие о нём даже не подозревают. Ещё отвратительно, что эта переменная содержит заголовки не в виде ключ/значение, а просто набор строк:

file_get_contents('//bolknote.ru');
var_dump($http_response_header);

вернёт:

array(14) {
  [0]=>
  string(15) "HTTP/1.1 200 OK"
  [1]=>
  string(13) "Server: nginx"
  [2]=>
  string(35) "Date: Wed, 11 Jul 2012 19:50:10 GMT"
… и так далее

Как такой непродуманный функционал может являться продолжением такой прекрасной штуки мне непонятно.

25 комментариев

← 1 4 5 6 7 8 12 →