php
PCRE 8.10
Заодно, после перевода проекта
Для этого я собрал специальный модуль YGX, это тот же extension PCRE, только с новой версией библиотеки (кстати, модуля пришлось делать два — для PHP 5.2.xx и 5.3.xx отдельно).
Теперь, когда у меня в PHP два вида функций — preg_* (старая PCRE) и ygx_* (новая PCRE 8.10), можно сравнивать поведение обоих. Вот первый небольшой тест:
bolk-dev ~/wiki $ php -r 'var_dump(ygx_replace("/a?/u", "", "abc"));'
string(2) "bc"
bolk-dev ~/wiki $ php -r 'var_dump(preg_replace("/т?/u", "", "тест"));'
string(4) "ес"
bolk-dev ~/wiki $ php -r 'var_dump(ygx_replace("/т?/u", "", "тест"));'
NULLКак видно, PCRE 8.10 в последнем случае выдаёт NULL. Такое поведение похоже на баг. Выражение, конечно, довольно бессмысленное, но на практике оно встретилось в составе более сложного.
Чтобы в этом случае не получался NULL (хотя правильного результата всё равно не будет), можно поставить «глагол» «(*FAIL)» (это специальное значение, который ни с чем не совпадает):
bolk-dev ~/wiki $ php -r 'var_dump(ygx_replace("/(*FAIL)т?/u", "", "тест"));'
string(8) "тест"Стоит ввести в выражение хоть
Что делать в этом случае, кроме как переписать регулярное выражение полностью, я пока не придумал.
Аппаратное ускорение PHP
Обсуждается возможность аппаратного ускорения PHP, точнее использования в коде возможностей GPU, в данный момент речь идёт о библиотеке CUDA фирмы NVIDIA.
Первое что приходит в голову — это ускорение сортировок чисел и вычисление md5/sha1. Я слишком слабо разбираюсь в вопросе, чтобы понять с первого взгляда куда ещё можно приспособить графические ускорители.
PHP, UTF-8: восьмой этап, заключительный
Прошло уже немало времени, с тех пор как я описал предыдущий этап перевода
Для начала надо понять что же делать с чтением и записью файлов. Нет никакого автоматического способа решить что делать с этими операциями в вашем проекте, хорошие новости заключаются в том, что, скорее всего, ничего делать не придётся. Мне не пришлось, хотя, чтобы это понять, нужно было исследовать исходники. Побайтовое чтение файлов — редкая операция в мире Даже в том случае, если вы производите с файлом Для другой операции, нам понадобится четыре метода в классе работы с UTF (напоминаю, что константа ON этого класса определяет включена ли поддержка UTF): метод charset возвращает Замены очевидны: везде, где в коде встречается Потом нужно проверить все места, где встречаются вызовы chr и ord («egrep ’\\b(ord|chr)’ -R .») и проверить не нужно ли переписать После этого осталось только перекодировать исходный код: И базу: В процессе пуска проекта выяснилась ещё одна немаловажная подробность (спасибо Саше Покатилову): оказывается, несмотря на то, что установки локали (setlocale) никак не влияет на функции сортировки в PHP, она влияет на некоторые функции работы с файлами (dirname, basename и некоторые другие), вот цитата из описания функции dirname: dirname() is locale aware, so for it to see the correct directory name with multibyte character paths, the matching locale must be set using the setlocale() function. Поэтому не забывайте выставлять локаль, если пользуетесь этими функциями. Дополнено: внезапно нашлись неприятные грабли, будьте осторожны: функция preg_match_all (и preg_match) с ключём PREG_OFFSET_CAPTURE возвращает бинарное смещение в строке (т. е. указывающее не на байт, а на символ), даже Я поступил просто — написал простую функцию, которая корректирует выходные значения. Идея простая: отрезаем при помощи substr строку до смещения, потом измеряем её Еще позднее: оказывается и параметр offset (последний параметр в preg_match и preg_match_all) работает с бинарным смещением, И ещё позже: а вот вам ещё сюрприз: модуль Multibyte String считает, что кодировка И ещё сильно позже: strftime тоже использует локаль.Что же дальше?
# вот эти файлы перекодировать не надо, они у меня уже в UTF-8
SKIPIT='./php/Utf.php|./php/UtfReverseString.php|./php/UtfString.php'
tmp=`tempfile`
for line in $(find -name '*.php' -or -name '*.tpl' -or -name '*.inc' | egrep -v "$SKIPIT"); do
iconv -fcp1251 -tutf8 "$line" -o "$tmp"
[ -e "$tmp" ] && mv -f "$tmp" "$line"
donemysqldump -pпароль -uпользователь --add-drop-table --create-options --default-character-set=utf8 --order-by-primary -q база |
sed -e 's/cp1251;$/utf8;/mg' |
mysql -pпароль -uпользователь база
PHP, UTF-8: седьмой этап, давайте что-нибудь сделаем с регулярными выражениями
В прошлый раз я рассматривал, по сути, самый сложный этап — замену всех вхождений $var[$index] и им подобных на вызов моей функции. В процессе замены я несколько раз корректировал заметку и доводил некоторые вещи руками.
В самой первой части я рассказывал о том как можно решить проблему К сожалению, этот «глагол» появился только в PCRE 8.10, а в самом последнем PHP, на сегодняшний момент, содержится 8.02. Как я говорил ранее, новый PCRE можно вкомпилить в PHP при помощи специального ключа. Внутренние сервисы, где я работаю, используют Поскольку мы сейчас переходим постепенно на новую версию Ubuntu (с Hardy Heron на Lucid Lynx), то часть машин у нас пока на Цапле, а часть — уже на Рыси. Это значит, что пакетов нужно будет собрать шесть. Дело это непростое. Я решил попробовать «выдрать» из внутренностей PHP модуль PCRE, переименовать его функции и константы и поставить его как простой подключаемый модуль. Мне удалось сделать, модуль «свежего PCRE» (можно скачать в разделе «Храню»). Модуль называется ygx, все функции начинаются с префикса «ygx_», константы — с «YGX_». Что это за сокращение, не спрашивайте, когда я начинал работать, я это знал, а сейчас забыл. Сложнее всего было корректно переименовать только те функции, типы, константы и так далее, которые видно в PHP, не задев всё то, что импортируется из внешней библиотеки PCRE. Ставится модуль примерно следующим образом: потом нужно прописать в php.ini строку «extension=ygx.so», перезапустить Поскольку к этому моменту все вызовы функций PCRE у меня заменены на вызовы одноимённых методов статического класса UTF, то мне нужно поменять «preg_» на «ygx_» только в одном месте — внутри методов этого класса.Что же дальше?
mkdir /tmp/pcre
cd !:1
wget ftp://ftp.csx.cam.ac.uk/pub/software/programming/pcre/pcre-8.10.tar.bz2
tar xvfj pcre-8.10.tar.bz2
cd pcre-8.10
./configure --prefix=/usr/local/pcre-8.10 --enable-utf8 --enable-unicode-properties --disable-static
make && make install
cd -
wget /files/php_fresh_pcre.zip
unzip php_fresh_pcre.zip
phpize
./configure --with-ygx-regex=/usr/local/pcre-8.10
make && sudo make install
phpize --clean
cd ; rm -rf /tmp/pcre
PHP, UTF-8: шестой этап, она же «строки, часть II». Заменяем
В прошлой части я пытался упростить себе задачу, посчитав сколько различных видов операций получения символа из строки по индексу есть у нас в коде. Но для того, чтобы подсчитать эти ситуации, нужен парсер, который эти случаи выделит.
Я такой парсер начал писать несколько дней назад и очень быстро увидел, куда меня этот путь ведёт. К сожалению,
Код я выкинул и начал писать заново, основываясь на токенах (лексемах), которое выдаёт встроенное в язык API токинайзера. По ходу процесса я часто уточнял случаи и производил тестирование. Получился довольно неряшливый парсер, к тому же с некоторыми багами, которые я счёл несущественными. Например, не обрабатывается ситуация isset($var, $var[$idx]).
В PHP isset (а так же empty и unset) — конструкции, а не функции и принимают на вход только переменные. У меня получение символа по индексу заменяется на Utf::get(…), а внутри строки на другую конструкцию — $_ENV[0x5f3759df]->get(…), так что если произойдёт замена внутри isset, будет синтаксическая ошибка.
Т. е. у меня в коде, внутри файла с классом Utf есть ещё и такая строка — «$_ENV[0x5f3759df] = new Utf();». $_ENV я выбрал за то, что это переменная (а переменную внутри строки проще заменять на другую переменную, пусть и с вызовом метода, PHP это позволяется) и это, в терминах PHP, «суперглобальный» массив, то есть он доступен отовсюду.
Итак, я сказал, что парсер имеет баги, но на весь довольно большой код у меня было всего четыре случая, правда один довольно странный — пропала треть исходного кода, так что, если будете применять — сохраните оригинальный код (мой лежал в SVN).
Первая операция, которую вам нужно сделать ещё до запуска парсера — это запустить следующий скрипт. У меня парсер работает
Так что я перекодировал код туда и обратно и сравнил что получилось:
for file in $(find -name '*.php' -or -name '*.inc' -or -name '*.tpl'); do
sum_new=`iconv -fcp1251 -tutf8 "$file" | iconv -futf8 -tcp1251 | md5sum -b | cut -d' ' -f1`
sum_orig=`md5sum -b "$file" | cut -d' ' -f1`
if [ $sum_new != $sum_orig ]; then
echo $sum_new $sum_orig $file
fi
doneУ меня ничего не поломалось, так что я спокойно запустил пасер. После запуска я проверил синтаксис скриптов интерпретатором и вручную избавил четыре найденные ошибки:
find -name '*.php' -or -name '*.inc' -or -name '*.tpl' | xargs php -lПосле всех запусков проект выглядит рабочим, но в понедельник я обязательно буду тестировать весь проект.
Добавлено позднее: а вот как выглядит метод get в классе Utf:
static public function get($str, $index)
{
if (!is_scalar($str)) {
return isset($str[$index]) ? $str[$index] : null;
}
return self::substr($str, $index, 1);
}Добавлено позже: нашёл ещё два бага: неверно отрабатываются
Конструкции heredoc я нашёл при помощи «grep -FR ’<<<’ *» и просмотрел их все, их было очень мало и ни одна из них не содержала переменных.
Добавлено ещё позже: опаснее оказалась ситуация, которую просто так можно и не заметить: замена вызова func($var[$index]) на func(Utf::get($var, $index), где прототип func описан как func(&$var). Синтаксис PHP вполне допускает такие ситуации, а на деле в массив могут не попасть изменения, которые должны были переданы быть по ссылке.
Способов исправить такую ситуацию два. Первый — логгировать все вызовы Utf::get, которые применяются к массивам и править их на получение переменной по индексу. Второй — найти и просмотреть все такие ситуации. Я выбрал второй способ:
find \( -name '*.php' -or -name '*.tpl' -or -name '*.inc' \) |
xargs egrep -oh 'ion[\t ]+\w+[\t ]*([^{]+' | awk -F'[( ]' '/&\$/{print tolower($2)}' |
sort -u | egrep -v '^preg_' | xargs -I{} egrep -iR --exclude-dir=.svn {}'[\t ]*(.*?Utf::get' *Этот скрипт (который очень долго работает) показывает все подозрительные строки (у меня их 7 оказалось), их надо просмотреть, выявить потенциально проблемные и поправить, если проблема действительно есть.
И ещё позже: так же есть набор встроенных функций, которые используют передачу массива по ссылке, я их нашёл вот таким способом:
(cat<<FUNCS
'\b(?:array_multisort|array_pop|array_push|\
array_replace_recursive|array_replace|\
array_shift|array_splice|\
array_unshift|array_walk_recursive|\
array_walk|arsort|asort|current|each|\
end|key|krsort|ksort|natcasesort|natsort|\
next|pos|prev|reset|rsort|shuffle|\
sort|uasort|uksort|usort)[ \t]*\([\t ]*[^$)]'
FUNCS
) | xargs -I[] find \( -name '*.tpl' -or -name '*.php' -or -name '*.inc' \) -exec \
egrep [] {} \; -print
PHP, UTF-8, рассуждения по поводу строк

Работаю с низким приоритетом над автоматической заменой операций получения символа в строке по индексу на такую же операцию, но которая умела бы работать
В общем, задача сложная, я над ней думаю. Пока в сторону парсеров синтаксиса (colorer, Text_Highlighter и PHP tokinizer). Из исследования выяснилось, что все они могут мне помочь примерно в равной степени, так что я думаю, что возьму PHP tokinizer. К сожалению, ни один из парсеров не парсит PHP настолько удобно, чтобы избавить меня от всех проблем, так что я думаю над их решением. Когда у меня будет понимание как бороться со всеми проблемами, буду программировать.Что дальше?
Огрызки от собеседований
От собеседований, которые я недавно проводил, у меня осталась масса листочков с заковыристыми вопросами. Например, если человек говорил, что знает PHP на 10 из 10, то я ему давал, например, такой тест:
var_dump((bool) print_r(${strrev('SLABOLG')} = 1, $GLOBALS[1]{1} == (unset) b"1") == "00");Сказать есть ли тут ошибки, если нет, то что получим и что вообще тут происходит.
P.S. Я в Самаре, «Holiday Inn» отличный отель.
PHP и UTF-8: пятый с половиной этап
После последнего, на данный момент, пятого этапа, меня опять потянуло на нецелые числа. Что поделать — я работаю над другим проектом, поэтому на полноценный этап я ничего не наскрёб, но работу потихоньку продолжаю. В прошлый раз я занимался оптимизацией — сделал итератор
Использовать так же просто, как и предыдущий, «прямой» итератор: Второй параметр указывает с какого символа (с конца) нужно начинать итерироваться.Так, и?
<?
class UtfReverseString implements Iterator
{
protected $str; // строка
protected $idx; // бинарный индекс строки
protected $pos; // UTF-8-индекс
protected $curend; // бинарный указатель на конец текущего символа
protected $idx_start; // бинарный индекс откуда стартуем
protected $pos_start; // UTF-8-индекс откуда стартуем
// проверка — байт не является началом символа
protected function isNotBegin($ch)
{
return (ord($ch) & 192) == 128;
}
public function __construct($str, $pos_start = 0)
{
$len = strlen($str);
$this->str = $str;
$this->idx = strlen($str); // этот указатель будет уточнён тут же, ниже
$this->pos = strlen(utf8_decode($str)) - 1 - $pos_start; // лучше использовать mb_strlen, если есть возможность
while ($pos_start-- >= 0) {
$this->curend = --$this->idx;
while ($this->idx > 0 && $this->isNotBegin($this->str[$this->idx])) {
$this->idx--;
}
}
$this->idx_start = $this->idx;
$this->pos_start = $this->pos;
}
public function current()
{
return substr($this->str, $this->idx, $this->curend - $this->idx + 1);
}
public function key()
{
return $this->pos;
}
public function next()
{
$this->pos--;
$this->curend = $this->idx - 1;
for ($this->idx--; $this->isNotBegin($this->str[$this->idx]); $this->idx--);
}
public function rewind()
{
$this->idx = $this->idx_start;
$this->pos = $this->pos_start;
}
public function valid()
{
return $this->idx >= 0;
}
public function __toString()
{
return $this->str;
}
}$str = new UtfReverseString('Привет', 1);
foreach ($str as $ch) {
echo $ch, ' ';
}
PHP4 без XML-парсера, плюс XPath
Я недавно написал простенький парсер XML для PHP 4.3+, для случаев, когда никакого встроенного парсера XML на хостинге не оказалось.
Весь XPath я, конечно, реализовывать не буду, да мне столько и не надо. В общем, написал класс, который используется в паре VerySimple_XMLParser. Реализованы получение потомков тега, «*», «node()» и «text()». Это всё, но мне хватит.
// Simple XPath for VerySimple_XMLParser
// /
class VerySimple_XMLParser_Tree
{
var $path;
var $counter;
var $xpath;
function VerySimple_XMLParser_Tree()
{
$this->path = array('/');
$this->counter = 0;
$this->xpath = array();
}
function collect($type, $args)
{
$path = implode($this->path);
switch ($type) {
case 'TEXT':
$this->xpath[$path . $this->counter . '/text()/'] = $args;
$this->counter++;
break;
case 'TAGOPEN':
list($name, $attributes) = $args;
$tag = $this->counter . '/' . $name . '/';
$this->xpath[$path . $tag] = $attributes;
$this->counter = 0;
$this->path[] = $tag;
break;
case 'TAGCLOSE':
$this->counter = 1 + (int) array_pop($this->path);
break;
}
}
function halfxpath($path)
{
$keys = array_keys($this->xpath);
$trans = array(
'\/' => '\/\d+\/',
'\*' => '[^\/()]+',
'node\(\)' => '[^\/]+',
);
$filter = strtr(preg_quote($path, '/'), $trans);
$filtered = preg_grep("/^$filter\/$/su", $keys);
$nodes = array();
foreach ($filtered as $key) {
$nodes[$key] = $this->xpath[$key];
}
return $nodes;
}
}
// а вот так это можно использовать:
$xml =<<<XML
<root>
<a>
<b><c>Xa!</c></b>
<b>Hello</b>
</a>
<a name="value"/>
</root>
XML;
$tree = &new VerySimple_XMLParser_Tree();
$sax = &new VerySimple_XMLParser(array(&$tree, 'collect'));
$sax->parse($xml);
echo '<plaintext>';
var_dump($tree->halfxpath('/*/a/b/text()'));
var_dump($tree->halfxpath('/*/a/b/node()'));
var_dump($tree->halfxpath('/*/a/b/*'));
var_dump($tree->halfxpath('/root/a'));Метод для запросов я назвал «halfxpath», намекая, что это не настоящий XPath, а лишь неполноценная эмуляция.
Основная идея, как видно из кода — не парсить XML в дерево, а помещать каждый элемент по специального вида пути, который потом будет проверен на совпадение по регулярному выражению, приготовленному из запроса.
Тонкости heredoc в PHP
В PHP, как и в некоторых других языках (Bash, Perl) есть конструкция heredoc — это способ задать многострочную строку. Посмотрите как удобно, не нужно экранировать кавычки:
// это PHP
$str = <<<STR
Привет, я строка "в кавычках",
'в апострофах'.
STR;Но меня всегда раздражало, что завершающий ограничитель приходится прижимать к левому краю, пробел и табуляция дают ошибку (хотя в Perl относительно недавно разрешили делать отступы для этой конструкции).
Сегодня, в очередной раз раздражаясь по этому поводу, я решил попробовать использовать неразрывные пробелы (Alt Right+Space в раскладке Бирмана), получилось, работает даже в PHP 4.4.4. Подходит, конечно, для исходников в кодировке
Правда, строки внутри, конечно, менять нельзя, но эта конструкция часто используется вместе с HTML, а ему отступы неинтересны.
$str = <<< HTML
<table>
<tr class="row"><td>'Привет'</td></tr>
</table>
HTML;Только не забудьте, что отступы надо делать и в начальной конструкции, и в конечной.
PHP4 без XML-парсера
Мне тут по работе пришлось решить странную, по нынешним временам задачу — принимать XML в программе на PHP4.3+, где никакого парсера XML может не оказаться. Таковы реалии продукта и среды, где он будет использоваться (это код внешнего проекта). Поэтому мне пришлось написать небольшой парсер, который, правда, расcчитывает, что XML будет
В качестве иллюстрации я использую в коде крайне простую функцию, которая не строит дерево, а просто собирает вызовы в массив, но понятно, что собрать дерево тоже ничего не стоит.Дальше
<?
// Very Simple XML SAX Parser by Evgeny Stepanischev
// /
class VerySimple_XMLParser
{
var $saxCallback;
function VerySimple_XMLParser($saxCallback)
{
$this->saxCallback = $saxCallback;
}
function parse($xml)
{
if (preg_match_all('#<![CDATA[.*?]]>|</?[^>]*>|[^<]+#us', $xml, $matches)) {
foreach ($matches[0] as $match) {
$this->parseThis($match);
}
}
}
function parseThis($match)
{
$calls = array(
'<!--' => 'COMMENT',
'<?' => 'PI',
'<![CDATA[' => 'CDATA',
'<!' => 'DTD',
'</' => 'TAGCLOSE',
'<' => 'TAGOPEN',
);
foreach ($calls as $need => $type) {
if (strpos($match, $need) === 0) {
return $this->parseByType($type, $match);
}
}
return $this->parseByType('OTHER', $match);
}
function parseByType($type, $text)
{
if ($type == 'COMMENT') {
return $this->addChunk($type, substr($text, 4, -3));
}
if ($type == 'CDATA') {
return $this->addChunk('TEXT', substr($text, 9, -3));
}
if ($type == 'OTHER') {
return $this->addChunk('TEXT', $text);
}
if ($type == 'TAGCLOSE') {
$end = strcspn($text, ' >');
return $this->addChunk('TAGCLOSE', substr($text, 2, $end - 2));
}
if ($type == 'TAGOPEN') {
$this->addChunk('TAGOPEN', $this->parseTag($text));
if (substr($text, -2) == '/>') {
$end = strcspn($text, ' >');
return $this->addChunk('TAGCLOSE', substr($text, 1, $end - 2));
}
}
return '';
}
function parseTag($text)
{
$end = strcspn($text, ' />');
$name = substr($text, 1, $end - 1);
return array($name, $this->parseTagAttributes(substr($text, $end)));
}
function parseTagAttributes($text)
{
$nameStart = '(?:[A-Za-z_:]|[^\x00-\x7F])';
$nameChar = '(?:[A-Za-z0-9_:\.-]|[^\x00-\x7F])';
$attrValue = '(?:\'[^\']*\'|"[^"]*")';
if (preg_match_all("/{$nameStart}{$nameChar}*=$attrValue/us", $text, $matches)) {
return $this->parseAttributes($matches[0]);
}
return array();
}
function parseAttributes($rawAttributes)
{
$attributes = array();
foreach ($rawAttributes as $rawAttr) {
list($name, $value) = explode('=', $rawAttr, 2);
if ($value[0] == '"' || $value[0] == "'") {
$value = substr($value, 1, -1);
}
$attributes[$name] = $value;
}
return $attributes;
}
function addChunk($type, $arg)
{
call_user_func($this->saxCallback, $type, $arg);
}
}
// Пример использования
$xml =<<<XML
<!-- комментарий -->
<?xml version="1.0"?>
<root attribute="a.a" v="a">
<v><![CDATA[data]]></v>
<v>data</v>
<v/>
</root>
XML;
// Понятно, что можно написать класс и передать в качестве
// callback в парсер метод объекта, но я для простоты, как пример,
// написал очень простую функцию
function VerySimple_XMLParser_Collector()
{
static $tree = array();
if (func_num_args() == 2) {
$tree[] = func_get_args();
} else {
return $tree;
}
}
$sax = &new VerySimple_XMLParser('VerySimple_XMLParser_Collector');
$sax->parse($xml);
echo '<plaintext>';
var_dump(VerySimple_XMLParser_Collector());
PHP и UTF-8: пятый этап
Итак, пятый этап. Точнее на этап, он, пожалуй, не тянет, но после этапов 4,5 и 4,6 мне хочется целых чисел.
В самом деле, если простое взятие символа по индексу в строке с фиксированным размером символа имеет сложность O(1), то Я написал небольшие тесты. Замерял три прохода по тексту объёмом 187,5КБ. Проход по строке с однобайтной кодировкой — 0,2 секунды, по строке Так вот, одна из вещей, которой мало кто пользуется — возможность создать в PHP свой итератор. У меня тут не руководство по итераторам, поэтому я ограничусь лишь кодом и примером использования. Основная идея — если мы двигаемся по строке последовательно, Использовать итератор достаточно просто: Так как в итерации сначала вызывается «current», а потом «next» мне показалось странным терять информацию о положении следующего символа, которую мы в «current» уже знаем, так что я передаю её в «next» через защищённое свойство. Список функций (из моего списка используемых функций), которые я перепишу с итератором (или скорость которых увеличится): trim, ltrim, rtrim, wordwrap, strcspn и strspn, а так же некоторые места в коде проекта.Итак, пятый этап
class UtfString implements Iterator
{
protected $str; // строка
protected $idx; // бинарный индекс строки
protected $pos; // UTF-8-индекс
protected $len; // бинарная длина
protected $next; // бинарный указатель на следующий
protected $idx_start; // бинарный индекс откуда стартуем
protected $pos_start; // UTF-8-индекс откуда стартуем
// проверка — байт не является началом символа
protected function isNotBegin($ch)
{
return (ord($ch) & 192) == 128;
}
public function __construct($str, $pos_start = 0)
{
$this->str = $str;
$this->pos = $this->pos_start = $pos_start;
$this->len = strlen($str);
$this->next = null;
if ($pos_start) {
for ($i = 0; $i<$pos_start; $i++) {
$this->idx_start++;
while ($this->isNotBegin($this->str[$this->idx_start])) {
$this->idx_start++;
}
}
$this->idx = $this->idx_start;
} else {
$this->idx = $this->idx_start = 0;
}
}
public function current()
{
$idx = $this->idx;
$out = $this->str[$idx++];
while ($this->isNotBegin($ch = $this->str[$idx])) {
$out .= $ch; // быстрее, чем массив!
$idx++;
}
$this->next = $idx;
return $out;
}
public function key()
{
return $this->pos;
}
public function next()
{
$this->pos++;
if ($this->next) {
$this->idx = $this->next;
$this->next = null;
} else {
for ($this->idx++; $this->isNotBegin($this->str[$this->idx]); $this->idx++);
}
}
public function rewind()
{
$this->idx = $this->idx_start;
$this->pos = $this->pos_start;
}
public function valid()
{
return $this->idx < $this->len;
}
public function __toString()
{
return $this->str;
}
}$str = str_repeat('Привет', 64000);
foreach (new UtfString($str) as $position => $char) {
// …
}
PHP и UTF-8: четыре и шесть десятых или ещё проблемные функции
С прошлого этапа я, в данном проекте, был занят тем, что писал
Более простая, но тоже не самая очевидная функция — addcslashes. Она экранирует указанные символы слешем, представляя все символы с кодом большим 128 как восьмеричное число, например, буква «ф» (в кодировке cp1251), если указать её во втором параметре, будет представлена «\364». Вопрос в следующем — символы Ответ в парной функции — stripcslashes. Если последовательно заэкранировать все байты Моя задача — эмулировать поведение функции в точности, в этой связи str_pad содержит две загадки: если задана строка дополнения из двух символов и более, то как аргумент дополняется слева и какая сторона дополняется первой, когда дополнять надо обе стороны? Как видим, сначала дополняется правая сторона, а при дополнении слева символы дополняют в указанном порядке (когда я программировал эту функцию, у меня был соблазн сделать дополнение с конца последовательности). Вообще, в PHP функций с тонкостями поведения масса. Функция wordwrap Дальше
static public function ord($character) {
if (self::ON) {
$codes = unpack('N', mb_convert_encoding($character, 'UCS-4BE', 'UTF-8'));
return reset($codes);
}
return ord($character);
}
static public function chr($ascii) {
if (self::ON) {
return mb_convert_encoding(pack('N', $ascii), 'UTF-8', 'UCS-4BE');
}
return chr($ascii);
}bolk@dev:~/daproject$ php -r 'var_dump(str_pad("..", 3, "12", STR_PAD_BOTH));'
string(3) "..1"
bolk@dev:~/daproject$ php -r 'var_dump(str_pad("..", 5, "12", STR_PAD_LEFT));'
string(3) "121.."
PHP и UTF-8: четыре с половиной или некоторые функции
Я пока приостановил эпопею
public static function strcmp($str1, $str2) {
// используется модуль intl
return collator_compare(collator_create(''), $str1, $str2);
}
public static function strncmp($str1, $str2, $len) {
return self::strcmp(self::substr($str1, 0, $len), self::substr($str2, 0, $len));
}
public static function strcasecmp($str1, $str2) {
return self::strcmp(self::strtolower($str1), self::strtolower($str2));
}
static public function ctype_space($text) {
return preg_match('/^\p{Zs}+$/us', $text);
}Привожу в качестве иллюстрации того, что не всё так сложно как кажется. Стоит только сесть и начать.
Некоторые функции в PHP уже есть (mb_substr),
И, кстати, в ходе родился замечательный совет: если функцию трудно реализовать (например, str_ireplace), посмотрите, может проще переписать место, где она используется. У меня две функции были использованы ровно по одному разу.
Добавлено позднее: тщательно изучил вопрос сравнения Unicode, я выяснил, что производить сравнение так, как это сделал я нельзя, поэтому функцию strcmp я удалил, буду переписывать. Например, буква «ё» в Unicode стоит дальше «л», а должно быть наоборот.
Добавлено ещё позже: в PHP 5.3 появилось расширение Intl, основанное на известной библиотеке UCI, оказывается это расширение собирается и с PHP 5.2.4+, так что имеет смысл использовать именно его.
PHP и UTF-8: четвёртый этап
В прошлой части мы
Начнём с eval, его проще всего найти: К счастью, почти всё что я нашёл — это Теперь я попробую найти вообще все Не все Вот что у меня получилось: readline_callback_handler_install, readline_callback_read_char, ob_start, session_set_save_handler, CURLOPT_HEADERFUNCTION, CURLOPT_PASSWDFUNCTION, CURLOPT_PROGRESSFUNCTION, CURLOPT_READFUNCTION, CURLOPT_WRITEFUNCTION, FILTER_CALLBACK, iterator_apply, set_exception_handler, pcntl_signal, array_map, array_filter, array_walk, array_walk_recursive, array_udiff_uassoc, array_diff_uassoc, array_udiff_assoc, array_diff_ukey, array_udiff , array_intersect_ukey, array_uintersect_uassoc, array_uintersect_assoc, array_intersect_uassoc, array_intersect_assoc , array_uintersect, array_reduce, uksort, usort, uasort. Или для командной строки (регулярное выражение я тут подсократил): В выводе нужно высматривать только вызовы строковых функций и статических методов, которые стоят на месте Всё, что осталось найти — вызовы preg_replace с модификатором «e». Я, было, подумал Думаю, в следующей части надо Какие у нас есть варианты?
bolk@dev:~/daproject$ find -type f ( -name '*.php' -or -name '*.tpl' -or -name '*.inc' ) | xargs -n1 egrep -li 'bevals*('
./php/lib/magpierss/extlib/Snoopy.class.inc
./php/lib/PEAR.php
./php/dir1/file1.php
./php/dir1/file2.php
./php/dir1/file3.phpbolk@dev:~/daproject$ find -type f ( -name '*.php' -or -name '*.tpl' -or -name '*.inc' ) |
> xargs -n1 egrep -H 'array_map|array_filter|array_walk|…|uasort' | less
PHP и UTF-8: третий этап (давайте уже что-нибудь заменим)
Ну что ж, всё готово для того, чтобы
Запустить её можно, например, так (напоминаю, что у меня PHP находится в файлах с расширениями php, tpl и inc): Программа напечатает имена файлов, где произошла замена. Своеобразная проверка — запустите её ещё раз, на втором проходе никаких имён файлов выведено быть не должно! Обязательно проследите, чтобы запуск не затронул ваш класс Utf! Итак, мы заменили функции на вызовы класса Utf. Всё работает? У меня не заработало. Причины две — я ошибся в имени одной из функций в классе Utf и появились проблемы с preg_replace_callback. О последней проблеме поподробнее. Пока preg_replace_callback была встроенной функцией всё было замечательно, как только она стала статическим методом класса, проблемы появились. Всё дело в указании в качестве callback метода статического класса. Такие вещи придётся править вручную. Если нет возможности провести тестирование, то можно воспользоваться подсказками IDE — во многих случаях она укажет на такие ситуации, но, в любом случае, лучше все такие места просмотреть глазами. Сегодня ночью я улетаю в Екатеринбург, так что, думаю, это последняя часть на этой неделе. Кстати, остаются ещё строковые функции, вызываемые как callback, внутри eval Что же дальше?
<?
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";
}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
…
PHP и UTF-8: третий этап (создание прототипов)
Этот сериал хорош тем, что я пишу его в реальном времени. Единственно, первая фаза — когда я собирал первоначальные данные и обдумывал как мне подступиться к этой задаче, заняла куда больше времени, чем может показаться. Итак, в предыдущей части мы выяснили какие модули используются в нашем проекте и узнали, что в нём
В этой части мы подготовимся к тому, чтобы заменить в проекте все упоминания небезопасных (с точки зрения работы
Как я уже сказал, расширение Reflection работает в PHP с багами, именно поэтому я максимально подробно стараюсь обработать ошибочные ситуации. После запуска программы, можно подсчитать, какое количество функций (у меня их 10) не отобразилось правильно, ничего не поделаешь, придётся их править руками. Файл Я удаляюсь править прототипы руками, а в следующей части уже перейдём к непосредственной замене. P.S. В коде есть странное место — «blah» . ’\$var’, увы, у меня Что же дальше?
<?
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 ( https://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 "}";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
PHP и UTF-8: второй этап (продолжение: ничего не забыли?)
Итак, в прошлый раз мы выяснили какие файлы у нас содержат программы на PHP и какие расширения есть в нашем интерпретаторе. Кстати, не забывайте про внепроектные файлы, те, что лежат в других папках, например, уровнем ниже. Их тоже надо использовать в нашем исследовании.
Выяснить какие же расширения реально используются в проекте нам поможет следующая программа:
<?
$extensions = array_merge(
get_loaded_extensions(true),
get_loaded_extensions()
);
$funcs = array();
foreach ($extensions as $extension) {
if ($extension == 'standard' || $extension == 'session') {
continue;
}
$extfuncs = get_extension_funcs($extension);
if ($extfuncs) {
$funcs = array_merge(
$funcs,
array_combine(
array_map('strtolower', $extfuncs),
array_fill(0, sizeof($extfuncs), $extension)
)
);
};
}
$classes = get_declared_classes();
set_time_limit(0);
$classes_regexp = '/new\s+(?i)(' . implode('|', $classes) . ')/s';
$funcs_regexp = '/(' . implode('|', array_keys($funcs)) . ')/si';
$it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator('.'));
for (;$it->valid(); $it->next()) {
$ext = pathinfo($it->getBaseName(), PATHINFO_EXTENSION);
if ($ext == 'php' || $ext == 'tpl' || $ext == 'inc') {
$content = file_get_contents($it->key());
if (preg_match_all($classes_regexp, $content, $matches)) {
echo $it->key(), "\nClass: ",
implode("\nClass: ", array_map('strtolower', $matches[1])), "\n\n";
}
if (preg_match_all($funcs_regexp, $content, $matches)) {
echo $it->key(), "\n";
foreach ($matches[1] as $func) {
$func = strtolower($func);
if (isset($funcs[$func])) {
echo 'Module: ', $funcs[$func], "\n";
} else {
echo 'Something wrong: ', $func, "\n";
}
}
echo "\n\n";
}
}
}У меня она отработала достаточно неспешно, за несколько минут. Каковы же результаты?
bolk@dev:~/daproject$ awk -F: '/^Module/{print $2}' tool2.log | sort -u
ctype
curl
date
gettext
hash
iconv
…
bolk@dev:~/daproject$ awk -F: '/^Class/{print $2}' tool2.log | sort -u
exception
simplexmlelementНачнём с конца — Exception это класс для работы с исключениями,
А вот остальные 19 модулей, используемых в проекте, просмотреть сложнее. Их тех, что у меня есть я сразу могу исключить SQLITE3, XML,
Как я узнаю какие модули работают
На что ещё надо обратить внимание? На две вещи — нет ли в файлах
С первым просто, я перевожу проект
bolk@dev:~/wiki$ find -type f -name '?*.*' | grep -v '.svn' | xargs -r -n1 egrep -iH 'cp1251|cp\-1251|windows\-1251'
./php/dir1/file1.php: if (strtolower($rss->encoding) != 'windows-1251') {
./php/dir1/file2.php: $c = @iconv($rss->encoding, 'cp1251', $c);
./php/dir2/file3.php: $title = iconv('UTF-8', 'CP1251', $employers[$i]->first_name .' '. $employers[$i]->last_name);
…У меня таких строк — вагон и маленькая тележка, что с ними делать, будем решать дальше, пока же отметим, что они есть, причём в количестве 158 штук.
А что с PEAR? Точнее, с теми файлами PEAR, которые подключаются не из папки проекта, а из папки PEAR, которая скрыта
У меня таких импортов, кстати, не оказалось.
Кажется, информации накоплено достаточно, пора
(И тут я предлагаю сделать перерыв до завтра)
PHP и UTF-8: второй этап (ничего не забыли?)
Итак, на первом этапе перевода нашего
Могли.
Итак, как их найти? Мне кажется, хороший путь — посмотреть где встречается сигнатура „<?“:
bolk@dev:~/daproject$ grep --binary-files=without-match -R '<?' * | cut -d':' -f1 |
> grep -v '.svn' | sed -re 's/.*?/([^/]+)/1/' | awk -F. '/./{print $NF}' | sort -u
conf
csv
example
inc
js
package
php
pm
png
py
svg
tpl
xml
xslОчевидно, что файлы формата XML (у меня это расширения xml, xsl и svg) так же могут содержать сигнатуру „<?“ (там это часть языка), текстовые файлы (conf, csv) так же её могут содержать (они вообще могут содержать что угодно).
Как же отфильтровать такой мусор? У интерпретатора PHP есть специальная опция, для того, чтобы проверить является ли подаваемый на вход файл синтаксически верным. Мы ей можем воспользоваться:
bolk@dev:~/daproject$ grep --binary-files=without-match -R '<?' * | cut -d':' -f1 | egrep -v '.svn|.php' |
> xargs -rn1 -I{} php -r 'echo `php -l {}`;' | grep -F 'No syntax errors detected in' | sort -u
No syntax errors detected in htdocs/_dir/file1.csv
No syntax errors detected in htdocs/_dir/file2.png
No syntax errors detected in htdocs/_dir/file3.csv
No syntax errors detected in htdocs/_dir/file4.png
No syntax errors detected in htdocs/_dir/file5.csv
No syntax errors detected in templates/file1.tpl
…Оказывается, PHP у меня есть даже внутри файлов с расширением png!
К несчастью, у меня таких файлов достаточно прилично — 39, поэтому я собираюсь некоторые из них отбросить сразу:
sed -e 's/^No syntax errors detected in //' tool2.log | xargs -n1 head --verbose | lessОказалось, что рассматривать как файлы PHP нужно файлы с ещё двумя расширениями — inc и tpl, остальные так же содержат
Поэтому я добавлю в программу из предыдущего этапа ещё два расширения и запущу её снова. У меня количество функций не изменилось — 38, а вот файлов добавилось — стало 176 против 173. Ура, теперь файлы
А что по поводу используемых расширений PHP? Их много и с каждым месяцем становится всё больше (я сам написал 3 или 4), вдруг
Для начала неплохо было бы узнать сколько у нас вообще расширений. Я знаю, что расширения в PHP подключаются двумя способами: в конфигурационном файле и при помощи функции dl. Для начала посмотрим, используется ли у нас функция dl:
bolk@dev:~/daproject$ find \( -name '*.php' -or -name '*.inc' -or -name '*.tpl' \) -exec egrep -i 'dl\s*\(' {} \; -print
* @return bool Success or not on the dl() call
// if either returns true dl() will produce a FATAL error, stop that
return @dl('php_'.$ext.$suffix) || @dl($ext.$suffix);
./php/lib/PEAR.php
if(!extension_loaded("mysql")) dl("mysql.so");
./php/db/mysql.phpУ меня dl используется. Берём на заметку, что через dl подгружается модуль MySQL, а заглянув в PEAR, обнаружим, что там есть метод loadExtension, который является мультиплатформенной обёрткой для dl.
Для очистки совести я поискал случаи использования loadExtension в проекте (не забудьте, что регистр нужно игнорировать), но не нашёл.
Итак, мы выяснили какие модули у нас подгружаются через dl — только один (mysql), что же вкомпилированно в сам PHP или подключено из файлов конфигурации? Нет способа лучше, чем узнать что сам PHP думает об этом:
bolk@dev:~/daproject$ php -m
[PHP Modules]
zip
xmlwriter
libxml
xml
wddx
tokenizer
…У меня это 54 модуля. Это очень много. Действительно ли их столько используется в проекте? Вот уж вряд ли. Надо
Отсюда мысль — узнать какие функции/классы содержатся в модуле и поискать их в файлах.
Давайте я это сделаю в следующей части, а то мне сейчас собеседование проводить.
PHP и UTF-8: первый этап
Я решил описать переход проекта
Основная идея — я добавлю в проект статический класс UTF. В этом классе будет переключатель (я назвал его «ON»), если он в состоянии «false», то все статические методы класса вызывают функции, работающие с однобайтовыми кодировками, иначе —
Выглядит он примерно так:
class UTF
{
const ON = false;
static public function strpos($haystack, $needle, $offset = 0)
{
if (self::ON) {
return mb_strpos($haystack, $needle, $offset, 'UTF-8');
}
return strpos($haystack, $needle, $offset);
}
// … и так далее
}Для чего нужен переключатель ON? Он позволяет переключить разом весь проект в состояние
Итак, нам надо посмотреть что же нужно переписывать и заменять в проекте. Я поступил просто — открыл в руководстве по PHP разделы строковых функций, функций PCRE и регулярных выражений в формате POSIX, скопировал их себе в программу и оформил как регулярное выражение:
<?
$funclist = <<<LIST
addcslashes — Quote string with slashes in a C style
chop — Alias of rtrim
chr — Return a specific character
chunk_split — Split a string into smaller chunks
convert_cyr_string — Convert from one Cyrillic character set to another
count_chars — Return information about characters used in a string
hebrev — Convert logical Hebrew text to visual text
hebrevc — Convert logical Hebrew text to visual text with newline conversion
html_entity_decode — Convert all HTML entities to their applicable characters
htmlentities — Convert all applicable characters to HTML entities
htmlspecialchars_decode — Convert special HTML entities back to characters
htmlspecialchars — Convert special characters to HTML entities
lcfirst — Make a string`s first character lowercase
levenshtein — Calculate Levenshtein distance between two strings
localeconv — Get numeric formatting information
ltrim — Strip whitespace (or other characters) from the beginning of a string
metaphone — Calculate the metaphone key of a string
money_format — Formats a number as a currency string
nl_langinfo — Query language and locale information
ord — Return ASCII value of character
rtrim — Strip whitespace (or other characters) from the end of a string
setlocale — Set locale information
similar_text — Calculate the similarity between two strings
soundex — Calculate the soundex key of a string
sscanf — Parses input from a string according to a format
str_getcsv — Parse a CSV string into an array
str_ireplace — Case-insensitive version of str_replace.
str_pad — Pad a string to a certain length with another string
str_rot13 — Perform the rot13 transform on a string
str_shuffle — Randomly shuffles a string
str_split — Convert a string to an array
str_word_count — Return information about words used in a string
strcasecmp — Binary safe case-insensitive string comparison
strcmp — Binary safe string comparison
strcoll — Locale based string comparison
strcspn — Find length of initial segment not matching mask
stripcslashes — Un-quote string quoted with addcslashes
stripos — Find position of first occurrence of a case-insensitive string
stristr — Case-insensitive strstr
strlen — Get string length
strnatcasecmp — Case insensitive string comparisons using a "natural order" algorithm
strnatcmp — String comparisons using a "natural order" algorithm
strncasecmp — Binary safe case-insensitive string comparison of the first n characters
strncmp — Binary safe string comparison of the first n characters
strpbrk — Search a string for any of a set of characters
strpos — Find position of first occurrence of a string
strrchr — Find the last occurrence of a character in a string
strrev — Reverse a string
strripos — Find position of last occurrence of a case-insensitive string in a string
strrpos — Find position of last occurrence of a char in a string
strspn — Finds the length of the first segment of a string consisting entirely of characters contained within a given mask.
strtolower — Make a string lowercase
strtoupper — Make a string uppercase
strtr — Translate certain characters
substr_compare — Binary safe comparison of two strings from an offset, up to length characters
substr_count — Count the number of substring occurrences
substr_replace — Replace text within a portion of a string
substr — Return part of a string
trim — Strip whitespace (or other characters) from the beginning and end of a string
ucfirst — Make a string`s first character uppercase
ucwords — Uppercase the first character of each word in a string
wordwrap — Wraps a string to a given number of characters
preg_filter — Perform a regular expression search and replace
preg_grep — Return array entries that match the pattern
preg_last_error — Returns the error code of the last PCRE regex execution
preg_match_all — Perform a global regular expression match
preg_match — Perform a regular expression match
preg_quote — Quote regular expression characters
preg_replace_callback — Perform a regular expression search and replace using a callback
preg_replace — Perform a regular expression search and replace
preg_split — Split string by a regular expression
ereg_replace — Replace regular expression
ereg — Regular expression match
eregi_replace — Replace regular expression case insensitive
eregi — Case insensitive regular expression match
split — Split string into array by regular expression
spliti — Split string into array by regular expression case insensitive
sql_regcase — Make regular expression for case insensitive match
ctype_alnum — Check for alphanumeric character(s)
ctype_alpha — Check for alphabetic character(s)
ctype_cntrl — Check for control character(s)
ctype_digit — Check for numeric character(s)
ctype_graph — Check for any printable character(s) except space
ctype_lower — Check for lowercase character(s)
ctype_print — Check for printable character(s)
ctype_punct — Check for any printable character which is not whitespace or an alphanumeric character
ctype_space — Check for whitespace character(s)
ctype_upper — Check for uppercase character(s)
array_change_key_case — Changes all keys in an array
array_multisort — Sort multiple or multi-dimensional arrays
arsort — Sort an array in reverse order and maintain index association
asort — Sort an array and maintain index association
krsort — Sort an array by key in reverse order
ksort — Sort an array by key
natcasesort — Sort an array using a case insensitive "natural order" algorithm
natsort — Sort an array using a "natural order" algorithm
rsort — Sort an array in reverse order
sort — Sort an array
LIST;
$funcs = array();
foreach (explode("\n", $funclist) as $line) {
list($line) = explode(" ", $line, 2);
$funcs[] = $line;
}
$regexp = '/(?<!UTF::)\b(' . implode('|', $funcs) . ')\s*\(/si';
set_time_limit(0);
$it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator('.'));
for (;$it->valid(); $it->next()) {
$ext = pathinfo($it->getBaseName(), PATHINFO_EXTENSION);
if ($ext == 'php') {
if (preg_match_all($regexp, file_get_contents($it->key()), $matches)) {
echo $it->key(), "\n";
echo ' ', implode("\n ", array_map('strtolower', $matches[1])), "\n\n";
}
}
}Список функций я проредил — удалил оттуда те, у которых проблем
bolk@dev:~/daproject$ grep ' ' tool.log | sort | uniq -c | sort -rn
225 substr
195 preg_replace
174 trim
132 preg_match
132 htmlspecialchars
127 strpos
113 strtolower
88 strlen
50 preg_replace_callback
40 rtrim
…
bolk@dev:~/daproject$ grep './' tool.log
./tool.php
./htdocs/_file.php
./htdocs/file1.php
./htdocs/dir/file1.php
./htdocs/dir/file2.php
./htdocs/dir/file3.php
./htdocs/dir/file4.php
./htdocs/dir/file5.php
./htdocs/class.php
./htdocs/_ajax.php
…На следующем этапе я заменю эти функции на их аналоги. Кстати, если у вас используются оба алиаса одной функции (например, chop и trim), то подумайте над тем, чтобы привести всё к единому виду.
Отдельно надо будет
Так же очень полезно получить список всех используемых модулей, чтобы решить какие из них могут быть проблемными.