Шастая по строкам кода одного из своих проектов, я наткнулся на функцию, значение которой я давно забыл. Проект хоть и имеет в качестве основы cms, но создателя этой функции я с точностью определил себя. Ее название выбивалось из общего стиля. Тем неприятнее для меня было осознавать, что я не помню, что она делает, и не знаю где она продекларирована, чтобы по ее коду определить. Проверив список из нескольких вероятных мест декларирования, я понял, что функция запрятана в нестандартном месте, мне вспомнилось несколько трогательных моментов связанных с тем далеким временем, когда я вел структуру проекта очень безответственно. Когда сеанс ностальгии прошел, проблема с нахождением места декларирования функции осталась.
К сожалению, применить великолепный метод grep-ания по файлам проекта не подходил изза отсутствия ssh. Список возможных методов решения сужался к одному очень неприятному «поочередная проверка подключаемых файлов». Но госпожа лень взяла вверх, а, как известно именно она является основным двигателем прогресса.
Я подумал, что вместо того чтобы в ручную проверять подключенные файлы, можно написать скрипт который это будет делать автоматически и выдавать файл и строку где находится декларирование функции. Немного позже я подумал, что узнать это место можно было более простым и топорным методом, сгенерировав ошибку дублированного декларирования, создавая функцию с таким же именем по ошибке можно узнать место, где эта функция уже существует. Этот метод неудобен на работающих проектах, но и тут можно исхитрится спрятав дубликат декларирования в проверку, например по ip.
Задача мне показалась довольно интересной, а в
Для начала обхода файлов используются данные от функции debug_backtrace() которая возвращает основные файлы. Следующий этап это запуск рекурсивной функции, которая просматривает все файлы подключающиеся к процессу.
<?
// Список функции которые подключают дополнительные файлы
$INC_FUNC=array("include","require","include_once","require_once");
// Список файлов которые были уже обработанны
$FILES_SEARCH=array();
function find_declaration($fname){
global $INC_FUNC,$TRACE_ROOT_PATH;
$trace=debug_backtrace();
// Находим корень запуска
$TRACE_ROOT_PATH=$trace[sizeof($trace)-1]['file'];
$TRACE_ROOT_PATH=pathinfo($TRACE_ROOT_PATH);
$TRACE_ROOT_PATH=$TRACE_ROOT_PATH['dirname'];
//для каждого уровня вложения из результатов debug_backtrace проверяем файлы
foreach($trace as $n=>$v) {
if($n==0 or array_search($v['function'],$INC_FUNC)!==false)
$srez=scan_file($fname,$v['file']); // Запуск рекурсивного поиска по файлу
if($srez!==false) break;
}
return $srez;
}
function scan_file($fname,$file){
global $INC_FUNC,$TRACE_ROOT_PATH,$FILES_SEARCH;
$cont=file_get_contents($file);
$cont_or=$cont;
// Удаляем коментарии из текста скрипта
$cont=preg_replace("#/*.**/#Uis","",$cont);
$cont=preg_replace("#//.*n#U","",$cont);
$cont=preg_replace("'#.*n'U","",$cont);
// Поиск формата декларирования
preg_match("#functions+".$fname.".*n#i",$cont,$m);
if(sizeof($m)>0){
$cont_or=substr($cont_or,0,strpos($cont_or,$m[0]));
$mline=explode("n",$cont_or);
return array("file"=>$file,"line"=>sizeof($mline));
}
// Поиск других подключенных файлов
preg_match_all("#(".implode("|",$INC_FUNC).")s+([^;$]+);#is",$cont,$minc);
foreach($minc[2] as $n=>$v ) {
eval(" $v = ".$v.";");
$inc_file="";
if(is_file($v)){
$inc_file=$v;
} else
if(strrpos($file,"/")!==false)
$inc_file=substr($file,0,strrpos($file,"/"))."/".$v;
else
$inc_file=substr($file,0,strrpos($file,"\"))."\".$v;
if(!is_file($inc_file)) {
$inc_file=$TRACE_ROOT_PATH."/".$v;
}
// Поиск по вложеным файлам
if(array_search($inc_file,$FILES_SEARCH)===false) {
$FILES_SEARCH[]=$inc_file;
$srez=scan_file($fname,$inc_file);
} else $srez=false;
if($srez!==false)
return $srez;
}
return false;
}
$funcp=find_declaration('init');
echo "Функция init() находится в файле ".$funcp['file'].", строка ".$funcp['line'];
?>
В процессе пришлось перейти от простого копирования текста после функции типа include к преобразованию через функцию eval. Так воспринимаются и пути к подключенным файлам, содержащие константы или глобальные переменные, что значительно увеличивает стабильность работы скрипта. Есть ситуации, при которых функция не будет найдена, это когда путь к файлу определен через локальные переменные или другие конструкции не доступные из внешней среды.
Конечно, функционал напрашивается быть переделанным в ООП, но это уже дополнения. Для меня на этом этапе главное алгоритм.
Скрипт можно использовать как дополнение ко всяким дебагерам написанных на PHP.
Автор: Vencendor