Для справки:
Tokenizer (лексер) предоставляет интерфейс для анализа кода. Таким образом, можно писать утилиты без необходимости работы с языковой спецификацией.
Tokenizer, начиная с версии php >= 4.3 включен в сборку php по-умолчанию.
Какие задачи можно решать с помощью tokenizr'а?
Да самые разные, связанные с анализом и модификацией кода.
Удаление комментариев из кода
Самый простой пример приведен на php.net — по удалению комментариев:
<?php
function strip_comments($fileName)
{
$source = file_get_contents($fileName);
// получаем все метки файла
$tokens = token_get_all($source);
$result = '';
foreach ($tokens as $token) {
if (!is_array($token)) {
// простая 1-буквенная лексема
$result .= $token;
} else {
// токен-массив
list($id, $value) = $token;
switch ($id) {
case T_COMMENT:
case T_DOC_COMMENT:
// комментарии пропускаем
break;
default:
// все остальное -> оставляем "как есть"
$result .= $value;
break;
}
}
}
return $result;
}
?>
Как видно из кода — мы получаем массив токенов и, в зависимости от их типа, оставляем или пропускаем.
Так можно решить задачки и поинтереснее — например на основе php-файлов сгенерировать карту классов проекта для автозагрузки.
Получение списка классов из файла
Для получения списка классов из файла я написал вот такую функцию:
<?php
function getClasses($fileName)
{
$result = array();
$content = file_get_contents($fileName);
$tokens = token_get_all($content);
$waitingClassName = false;
$waitingNamespace = false;
$waitingNamespaceSeparator = false;
$namespace = array();
for ($i = 0, $c = count($tokens); $i < $c; $i++) {
if (is_array($tokens[$i])) {
list($id, $value) = $tokens[$i];
switch ($id) {
case T_NAMESPACE:
$waitingNamespace = true;
$waitingNamespaceSeparator = false;
$namespace = array();
break;
case T_CLASS:
case T_INTERFACE:
$waitingClassName = true;
break;
case T_STRING:
if ($waitingNamespace) {
$namespace[] = $value;
$waitingNamespace = false;
$waitingNamespaceSeparator = true;
} elseif ($waitingClassName) {
if (!empty($namespace)) {
$value = sprintf('%s\%s', implode('\', $namespace), $value);
}
$result[] = $value;
$waitingClassName = false;
}
break;
case T_NS_SEPARATOR:
if ($waitingNamespaceSeparator && !$waitingNamespace && !empty($namespace)) {
$waitingNamespace = true;
$waitingNamespaceSeparator = false;
}
break;
}
} else {
if (($waitingNamespace || $waitingNamespaceSeparator) && ($tokens[$i] == '{' || $tokens[$i] == ';')) {
$waitingNamespace = false;
$waitingNamespaceSeparator = false;
}
}
}
return $result;
}
?>
А потом подумал и написал небольшую утилиту, которая генерирует автозагрузчик на основании файлов проекта.
Она анализирует в указанной папке все файлы с расширением "*.php" и строит карту классов (с учетом неймспейсов, конечно), на основании которой потом генерируется автозагрузчик.
Найти ее можно на github.com
Отключение и переопределение стандартных функций
На днях вспомнил, как когда-то ковырялся с расширением runkit. Из его возможностей меня особенно заинтересовало переопределение стандартных функций и песочница, в которой можно было бы запретить использование тех или иных функций.
И сейчас я подумал, а можно ли реализовать подобный функционал, без использования этого расширения. Оказалось, что tokenizer вполне может помочь в этом деле.
Так родилась библиотечка Runtime, с помощью которой, можно во время выполнения скрипта запретить использование любых стандартных функций, или переопределить их.
Приведу примеры работы:
<?php
use DmRuntime;
$code = <<<CODE
<?php
echo str_replace( 0, 1, 100 );
?>
CODE;
// Вывалит Exception, с в котором сообщается, что использование str_replace запрещено
Runtime::code($code)
->disableFunction('str_replace')
->execute();
?>
<?php
use DmRuntime;
$code = <<<CODE
<?php
echo str_replace( 0, 1, 100 );
?>
CODE;
// Выведет 000, вместо 111
Runtime::code($code)
->overrideFunction('str_replace', function ($search, $replace, $subject) {
// меняем 1ый и 2й аргументы местами
echo str_replace($replace, $search, $subject);
})
->execute();
?>
Как использовать эти возможности — дело личное. Но использовать это нужно аккуратно.
Я провел небольшое исследование и остался доволен результатом.
Касательно runtime — сложно сказать, где его можно применить, а где нет. Но сама библиотека наглядно демонстрирует работу tokenizer'а и его возможностей.
Ссылки
Автор: dmkuznetsov