PHP и UTF-8: третий этап (давайте уже что-нибудь заменим)
Ну что ж, всё готово для того, чтобы наконец-то что-то заменить в нашем проекте. С прошлого этапа у нас должен появиться поправленный руками класс Utf, который нужно подключить так, чтобы он был доступен из любого файла нашего проекта. Например, для этой цели можно воспользоваться настройкой PHP «auto_prepend_file».
<?
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. Что-то с ними надо делать.
О! Кат! Ура, я считаю :)
вот это труд, спасибо. раньше я считал решением таких проблем только переписывание всего подчистую. не в последнюю очерередь из-за проблем с кодировками я перешел с пхп на лисп.
Комментарий для livejournal.com:
Это, увы, ещё не конец :) Прилечу из Екатеринбурга, продолжу.
Комментарий для Евгения Степанищева:
Я нашёл универсальное решение проблемы со строковыми функциями с UTF-8, -16 и даже с китайским языком, это в т.ч. решает проблемы с eval и все вышенаписанные. Достаточно тебя внедрить в команду разработчиков php! Дальше произойдёт всё само собой =)
Комментарий для indeec17:
Это очень дорогое решение :) Мой бывший начальник иногда меня спрашивает «где нам найти ещё трёх Болков» :)