PHP и UTF-8: третий этап (создание прототипов)
Этот сериал хорош тем, что я пишу его в реальном времени. Единственно, первая фаза — когда я собирал первоначальные данные и обдумывал как мне подступиться к этой задаче, заняла куда больше времени, чем может показаться. Итак, в предыдущей части мы выяснили какие модули используются в нашем проекте и узнали, что в нём кое-где указана кодировка прямым текстом. Так же держим в голове, что нам надо что-то будет делать с файловыми функциями и обращением к символу в строке по индексу ($str[$index]).
В этой части мы подготовимся к тому, чтобы заменить в проекте все упоминания небезопасных (с точки зрения работы с UTF-8) функций на статические методы класса Utf, где мы будем менять их на UTF-8-аналоги.
<?
function U_makeproto($funcname)
{
$func = new ReflectionFunction($funcname);
$out = ' static public function ' . $funcname . '(';
$counter = 0;
$hasRefOptional = $hasOptional = false;
$names = array();
foreach ($func->getParameters() as $param) {
$name = $param->getName();
if ($name !== null && $name != '...') {
$names[] = $name = '$' . $name;
} else {
$names[] = $name = '$param' . $counter++;
}
if ($param->isPassedByReference()) {
$name = '&' . $name;
}
if ($param->isArray()) {
$name = 'array ' . $name;
}
if ($param->isOptional()) {
$name .= '=';
$name .= $param->isDefaultValueAvailable() ? $param->getDefaultValue() : 'null';
if ($param->isPassedByReference()) {
$hasRefOptional = true;
}
$hasOptional = true;
}
$out .= $name . ', ';
}
$out = rtrim($out, ', ') . ') {';
if ($counter || !$names) {
$out .= "\n // FIXME: Invalid proto ( http://www.php.net/$funcname )\n";
}
$out .= "\n if (self::ON) {".
"\n // TODO: UTF version".
"\n }\n";
if (!$hasRefOptional) {
if ($hasOptional || !$counter && !$names) {
$out .= "\n " . '$params = func_get_args();'.
"\n return call_user_func_array('$funcname', \$params);";
} else {
$hereNames = implode(', ', $names);
$out .= "\n return $funcname($hereNames);";
}
} else {
$reqCounter = $func->getNumberOfRequiredParameters();
$out .= "\n switch (func_num_args()) {".
"\n case $reqCounter:".
"\n " . '$params = func_get_args();'.
"\n return call_user_func_array('$funcname', \$params);";
for ($i = $reqCounter+1, $len = $func->getNumberOfParameters(); $i<=$len; $i++) {
$hereNames = implode(', ', array_slice($names, 0, $i));
$out .= "\n case $i:".
"\n return $funcname($hereNames);";
}
$out .= "\n default:".
"\n trigger_error('Wrong parameter count for $funcname()', E_USER_WARNING);".
"\n return null;";
$out .= "\n }";
}
return $out . "\n }";
}
echo "<?php\nclass UTF\n{\n const ON = false;\n\n";
foreach (file('php://stdin', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) as $funcname) {
echo U_makeproto($funcname), "\n\n";
}
echo "}";
Как я уже сказал, расширение Reflection работает в PHP с багами, именно поэтому я максимально подробно стараюсь обработать ошибочные ситуации. После запуска программы, можно подсчитать, какое количество функций (у меня их 10) не отобразилось правильно, ничего не поделаешь, придётся их править руками. Файл tool-func.log содержит частоты появления функций с их именами из первого этапа эпопеи.
bolk@dev:~/daproject$ sed -re 's/\s+\S+\s+//' tool-func.log | php tool-func-proto.php > utf.php
bolk@dev:~/daproject$ grep -c FIXME utf.php
10
Я удаляюсь править прототипы руками, а в следующей части уже перейдём к непосредственной замене.
P.S. В коде есть странное место — «blah» . ’\$var’, увы, у меня какие-то глюки с форматированием на сайте, пришлось сделать так.