Это сайт — моя персональная записная книжка. Интересна мне, по большей части, история, своя жизнь и немного программирование.

PHP и UTF-8: третий этап (давайте уже что-нибудь заменим)

Ну что ж, всё готово для того, чтобы наконец-то что-то заменить в нашем проекте. С прошлого этапа у нас должен появиться поправленный руками класс Utf, который нужно подключить так, чтобы он был доступен из любого файла нашего проекта. Например, для этой цели можно воспользоваться настройкой PHP «auto_prepend_file».

Следующая программа поможет нам заменить наши ущербные функции (их имена и статистика всё так же лежат в файле tool-func.log) на пока не менее ущербные обёртки.

<?
    if ($_SERVER['argc'] <> 3) die;
    if (realpath($_SERVER['argv'][2]) == realpath(__FILE__)) die; 

    $file = file_get_contents($_SERVER['argv'][2]);
    if (strpos($file, '<?') === false) die;

    $funcs = file($_SERVER['argv'][1], FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
    foreach ($funcs as &$func) {
        list(,$func) = preg_split('/\s+/s', $func, 2, PREG_SPLIT_NO_EMPTY);
    }

    $skip   = false;
    $skipIf = array(T_PAAMAYIM_NEKUDOTAYIM, T_DOUBLE_COLON, T_OBJECT_OPERATOR);
    $fixed  = false;

    ob_start();

    foreach (token_get_all($file) as $token) {
        if (is_scalar($token)) {
            echo $token;
        } else {
            if (!$skip && $token[0] == T_STRING && in_array(strtolower($token[1]), $funcs)) {
                $fixed = print 'Utf::' . $token[1];
            } else {
                echo $token[1];
            }

            $skip = in_array($token[0], $skipIf); 
        }
    }

    file_put_contents($_SERVER['argv'][2], ob_get_clean());

    if ($fixed) {
        echo $_SERVER['argv'][2], "\n";
    }

Запустить её можно, например, так (напоминаю, что у меня PHP находится в файлах с расширениями php, tpl и inc):

bolk@dev:~/daproject$ find -type f \( -name '*.php' -or -name '*.tpl' -or -name '*.inc' \) | grep -vF '.svn' |
> xargs -n1 php tool4.php tool-func.log
./php/file.php
./php/dir1/file1.php
./php/dir1/file2.php
./php/dir1/dir2/file3.php
…

Программа напечатает имена файлов, где произошла замена. Своеобразная проверка — запустите её ещё раз, на втором проходе никаких имён файлов выведено быть не должно! Обязательно проследите, чтобы запуск не затронул ваш класс Utf!

Итак, мы заменили функции на вызовы класса Utf. Всё работает? У меня не заработало. Причины две — я ошибся в имени одной из функций в классе Utf и появились проблемы с preg_replace_callback. О последней проблеме поподробнее.

Пока preg_replace_callback была встроенной функцией всё было замечательно, как только она стала статическим методом класса, проблемы появились. Всё дело в указании в качестве callback метода статического класса. Во-первых, метод может оказаться private или protected, что мешает ему быть вызванным из другого, неродного класса, во-вторых, в качестве имени класса может быть указано «self», а «self» внутри класса Utf равно «Utf», что совсем не то, что ожидается.

Такие вещи придётся править вручную. Если нет возможности провести тестирование, то можно воспользоваться подсказками IDE — во многих случаях она укажет на такие ситуации, но, в любом случае, лучше все такие места просмотреть глазами.

Сегодня ночью я улетаю в Екатеринбург, так что, думаю, это последняя часть на этой неделе.

Кстати, остаются ещё строковые функции, вызываемые как callback, внутри eval или в eval-выражениях preg_replace. Что-то с ними надо делать.

5 комментариев
diiina 2010

О! Кат! Ура, я считаю :)

livejournal.com 2010

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

Евгений Степанищев (bolknote.ru) 2010

Комментарий для livejournal.com:

Это, увы, ещё не конец :) Прилечу из Екатеринбурга, продолжу.

indeec17 2010

Комментарий для Евгения Степанищева:

Я нашёл универсальное решение проблемы со строковыми функциями с UTF-8, -16 и даже с китайским языком, это в т.ч. решает проблемы с eval и все вышенаписанные. Достаточно тебя внедрить в команду разработчиков php! Дальше произойдёт всё само собой =)

Евгений Степанищев (bolknote.ru) 2010

Комментарий для indeec17:

Это очень дорогое решение :) Мой бывший начальник иногда меня спрашивает «где нам найти ещё трёх Болков» :)