Построение списка классов приложения на PHP
Задача:
Проиндексировать файлы с php сырцами и построить список классов, описанных в этих файлах. На выходе должен получиться массив вида:
‘имяКласса’ => ‘имяФайлаВКоторомОписанКласс’
Зачем:
Нужно сделать autoload класса при создании объекта в проекте, где никакой структуры директорий и именования классов не наблюдаеться без каких либо перспектив на ее появление.
Решение:
Для начала нужно проиндексировать файлы в проекте, для этой задачи я использовал простенькую самописную рекурсивную функцию, результат выполнения которой - массив сериализировался в файл:
<?php /** * getRecursiveFileList * * рекурсивно получаем список всех .php и .inc файлов в директории и ее поддиректориях * * @param string $path * @param array $filelist * @return array */ function getRecursiveFileList($path, $filelist = array()) { $pathFileList = array_diff(scandir($path), array('.','..')); foreach ($pathFileList as $key=>$fsElementName) { if (is_dir($path."/".$fsElementName)) { $filelist = getRecursiveFileList($path."/".$fsElementName, $filelist); } else { //берем только .inc и .php файлы. Обратите внимание, на регулярку, возможно //вам нужно будет подправить ее под свои нужды. if (preg_match('/^[a-zA-Z\_0-9\.\-]+\.(php|inc)$/',$fsElementName, $matches)) { $filelist[] = $path."/".$fsElementName; } } } return $filelist; } //в данном примере я индексировал свежий AdoDB, результат сериализировал $serializedContent = serialize(getRecursiveFileList("../adodb5")); //дампим в файл $tempFilelistCacheFileName = "./cache/_fsDump_Adodb"; $handle = fopen($tempFilelistCacheFileName, "w"); fwrite($handle, $serializedContent); fclose($handle);
Далее на основе этого дампа нам нужно построить уже массив для автолоада. Вот тут начались интересности: многие сразу начинают кричать про get_declared_classes(); , но данное решение нам не подойдет, так как если даже в данном файле нет никаких классов, а есть только пару небрежно оставленных require/include, то индекснем мы его как носителя классов из проиклюдженных файликов. Остаеться только решение на основе синтаксического разбора исходника файла. Для этой задачи мне оптимально подошла функция token_get_all();:
<?php <?php //получаем сериализировнный массив списка файлов $serializedContent = file_get_contents("./cache/_fsDump_Adodb"); $filesArray = unserialize($serializedContent); $autoloadClassFiles = array(); //парсим на токен class и достаем имена классов foreach ($filesArray as $key=>$fileName) { $fileTokens = token_get_all(file_get_contents($fileName)); foreach ($fileTokens as $key=>$tokenInfo) { if ($tokenInfo[0] == T_CLASS) { //небольшой хак $autoloadClassFiles[$fileTokens[$key+2][1]] = $fileName; } } } //я задампил все это в файлик для примера $serializedContent = serialize($autoloadClassFiles); $tempFilelistCacheFileName = "./cache/_classesAutoloadFiles_Adodb5"; $handle = fopen($tempFilelistCacheFileName, "w"); fwrite($handle, $serializedContent); fclose($handle);
Обяснение такого хака, как $autoloadClassFiles[$fileTokens[$key+2][1]] = $fileName; - довольно простое, после разбора на токены, после T_CLASS идет T_WHITESPACE, а только потом имя класса, имя класса не воспринимаеться как конструкция языка, а идет как T_STRING, тоесть мы банально берем токен через токен :-).
Напоследок хочу сказать, что я не хотел рассказать про готовое к продакшн решение, а хотел рассказать про пути решения данной проблемы, а мегафункциональный класс или консольное приложение напишете сами я думаю.
Скачать этот полностью рабочий пример с adodb5 в комплекте(для жиру :-)) можно скачать тут: http://yomoyo.feodosian.com/downloads/classIndexerExample.zip
Просто пошариться по сырцам можно здесь:
http://yomoyo.feodosian.com/srcFiles/classIndexerExample/
Tags: php, проекты, разработка
Последние комменты