5
Отладки приложения PHP или надоело писать print_r
Рубрика: PHP, ОтладкаВсе web программисты используют для отладки конструкции типа print ‘<pre>’.print_r($var,1).’</pre>’ в разных модификациях, и после сотого (у некоторых первого) проекта возникает закономерное желание «Если я создаю инструменты для других, почему бы не создать инструмент для себя?». В итоге, я не исключение, появляются функции:
<?
function p($var) {
print '<pre>'. htmlspecialchars(print_r($var, true)).'</pre>';
}
?>
У меня функция стала называться «p», это короткое имя и легко запоминающееся т.к. происходит от оператора print и тега pre. Я видел десяток вариантов (vardump, vd, pre и т.д.), но мой вариант наиболее симпатизирует, своей лаконичностью. Такая конструкция, как правило, используется еще при отладке нескольких проектов и возникает желание сделать что-то более привлекательное и более функциональное.Я попробую продемонстрировать путь создания удобного и гибкого инструмента, с одной стороны, а с другой простого и надежного. Небольшое замечание, инструмент я буду описывать для языка PHP версии не ниже 5, но при небольшой адаптации все можно заставить работать и в PHP версии 4. Показанный инструмент максимально независимым от Фреймворков и CMS систем.
Создадим файл debub.php, куда поместим указанную функцию «p», но слегка ее модернизируем:
<?
// +--------------------------------------------------------+---------------+
// | Проект: Отладчик | PHP Version 5 |
// +--------------------------------------------------------+---------------+
// Объявление отладочных функций и подключение библиотек
/**
* Функция вывода отладочной информации
*
* @param mixed $arg Иследуемая переменная
* @param boolean $return Возврат результата
* @param boolean $stop Прекращение исполнения
*/
function p($arg=null, $return=false, $stop=false)
{
if($return) {
return Debug::dump($arg);
} else {
if ($stop) {
die (Debug::dump($arg));
} else {
print Debug::dump($arg);
}
}
}
В данном коде в принципе просматривается вся та же функциональность, но это не расширенная обертка встроенной функции «print_r» а обертка статического метода «dump» класса «Debug», но выполняющая все те же функции что и «print_r». Что это за зверь и нужны ли еще какие-то дополнительные обертки данной функции?
Идем дальше. Создаем файл class.debug.php, кстати, имена файлов в вашем варианте могут отличаться от моих в зависимости от требований которые диктует ваша платформа. В файле debub.php обязательно нужно подключить навый файл. include_once(’class.debug.php’); А в созданный файл помещаем следующий код.
<?
// +--------------------------------------------------------+---------------+
// | Проект: Отладчик | PHP Version 5 |
// +--------------------------------------------------------+---------------+
// Класс отладки
/**
* Инструменты для отладки
*/
class cmsDebug
{
static protected $style_block = 'border: 1px dashed #ddd; padding: 5px; margin: 2px; text-align: left; background-color: #f9f9f9;';
static public $style_boolean = 'color: #FF0000;';
static public $style_integer = 'color: #0000FF;';
static public $style_float = 'color: #660099;';
static public $style_callable = 'color: #999900;';
static public $style_string = 'color: #009900;';
static public $style_array = 'color: #660000;';
static public $style_object = 'color: #003399;';
static public $style_resource = 'color: #DD66FF;';
static public $style_null = 'color: #000000;';
static public $style_unknown = 'color: #000000;';
/**
* Форматирует полученные данные Вывод в одну строку
* Используестся для вывода простейших типов данных
* или для показа аргументов функций
*
* @param mixed $arg
*/
static protected function varDump ($arg)
{
switch (true)
{
case is_bool($arg):
$param = $arg == true ? "true":"false";
$type = '<span'.self::getStyle(self::$style_boolean).'>boolean</span>';
break;
case is_int($arg):
$param = strval($arg);
$type = '<span'.self::getStyle(self::$style_integer).'>integer</span>';
break;
case is_float($arg):
$param = strval($arg);
$type = '<span'.self::getStyle(self::$style_float).'>float</span>';
break;
case is_callable($arg):
if (is_string($arg)) {
$functions = get_defined_functions();
$type = array_search($arg, $functions['internal'])? ‘internal’:'user’;
$param = $type.’ function ‘.htmlspecialchars($arg).’()’;
} elseif (is_array($arg)) {
reset($arg);
$class = current($arg);
$method = next($arg);
if (is_string($class)) {
$param = ’static class ‘.$class.’::’.$method.’()’;
} elseif (is_object($class)){
$param = ‘object of class ‘.get_class($class).’->’.$method.’()’;
} else {
$param = ‘unknown “‘.strval($arg).’”‘;
}
} else {
$param = ‘unknown “‘.strval($arg).’”‘;
}
$type = ‘<span’.self::getStyle(self::$style_callable).’>callable</span>’;
break;
case is_string($arg):
$param = ‘”‘.htmlspecialchars($arg).’”‘;
$param = preg_replace (”/rn/”, “\r\n”, $param);
$param = preg_replace (”/n/”, “\n”, $param);
$param = preg_replace (”/r/”, “\r”, $param);
$param = preg_replace (”/t/”, “\t”, $param);
$type = ‘<span’.self::getStyle(self::$style_string).’>string['.strlen($arg).']</span>’;
break;
case is_array($arg):
$param = print_r($arg, true);
$param = preg_replace (”/\r\n/”, ” “, $param);
$param = preg_replace (”/\n/”, ” “, $param);
$param = preg_replace (”/\t/”, ” “, $param);
$param = preg_replace (”’s+’i”, ” “, $param);
$param = htmlspecialchars($param);
$type = ‘<span’.self::getStyle(self::$style_array).’>array['.count($arg).']</span>’;
break;
case is_object($arg):
$param = print_r($arg,1);
$param = preg_replace (”/\r\n/”, ” “, $param);
$param = preg_replace (”/\n/”, ” “, $param);
$param = preg_replace (”/\t/”, ” “, $param);
$param = preg_replace (”’s+’i”, ” “, $param);
$type = ‘<span’.self::getStyle(self::$style_object).’>object['.get_class($arg).']</span>’;
break;
case is_resource($arg):
$param = ‘”‘.get_resource_type($arg).’”‘;
$type = ‘<span’.self::getStyle(self::$style_resource).’>resource</span>’;
break;
case is_null($arg):
$param = ”;
$type = ‘<span’.self::getStyle(self::$style_null).’>null</span>’;
break;
default:
$param = ”;
$type = ‘<span’.self::getStyle(self::$style_boolean).’>unknown</span>’;
break;
}
return $type.’ ‘.$param.”;
}
static public function dump($arg) {
return ‘<pre’.self::getStyle(self::$style_block).’>’.self::varDump($arg).’</pre>’;
}
Протестируйте данный код, красота вроде стала появляться, все сейчас раскрашено, но вот с гибкостью остались проблемы, точнее одна: ее отсутствие, данная конструкция в принципе по информативности и возможностям не отличается от банального print_r. Но на самом деле это только первый шаг и ждать от него много нельзя.
Задача второго шага, добавление гибкости, для этого нам необходимо к данному инструменту хотелось бы добавить, какие ни будь еще варианты для отображения значения отлаживаемой переменной. Что-то типа модулей для данного класса которые бы анализировали бы тип и если модуль «заточен» по данный тип, то отображением занимался не метод «varDump» а модуль. Для начала создадим интерфейс модуля, файл interface.debugplugin.php:
<?
// +--------------------------------------------------------+---------------+
// | Проект: Отладчик | PHP Version 5 |
// +--------------------------------------------------------+---------------+
// Интерфейс
/**
* Интерфейс расширений для класса отладки
*
*/
interface DebugPlugin {
/**
* Метод проверки соответствия типа переменной для данного расширения
*/
static function is($arg);
/**
* Форматирование полученной переменной
*/
static function format($arg, $tab=0);
}
Далее модернизируем класс Debug: добавим метод
/**
* Форматирует полученные данные с использование плагинов
*
* @param mixed $arg
*/
static public function varDumpExtend($arg, $tab=0)
{
if(count(self::$plugins)) {
foreach (self::$plugins as $plugin) {
if (call_user_func(array($plugin,'is'), $arg)) {
return call_user_func(array($plugin,'format'), $arg, $tab);
}
}
}
return self::varDump($arg)."n";
}
И изменим метод dump, заменим вызов метода varDump на varDumpExtend.
Добавим статическое свойство:
/**
* Подключенные модули для отображения отладочных данных
* @var unknown_type
*/
protected static $plugins = array('DebugCallable', 'DebugArray');
Прокомментирую метод varDumpExtend: Прежде чем вызвать стандартное форматирование методом varDump посмотрим, могут ли узнанные классы помочь в отображении данной переменной. А вот и примеры модулей:
<?php
// +--------------------------------------------------------+---------------+
// | Проект: Отладчик | PHP Version 5 |
// +--------------------------------------------------------+---------------+
// Реализация модуля отладчика
class DebugCallable implements DebugPlugin
{
static private $manual ='http://www.php.net/manual/ru/function.';
/**
* Функция проверки может ли модуль обработать данный тип данных
*
* @param mixed $arg
* @return unknown
*/
static public function is($arg) {
return is_callable($arg);
}
/**
* Форматирование массива
*
* @param mixed $arg
* @param integer $tab
*/
static public function format($arg, $tab=0)
{
if (is_string($arg)) {
$functions = get_defined_functions();
if (array_search($arg, $functions['internal'])) {
$param = “internal function <span style=\”cursor:hand;cursor:pointer;font-weight: bold;\” onclick=\”javascript: void(window.open(’”.self::$manual.$arg.”‘, ‘preview’, ‘resizable=yes, location=no, menubar=no, scrollbars=yes, status=no, toolbar=no, fullscreen=no, dependent=no, width=800, height=600′));\”>”.htmlspecialchars($arg).’</span>()’;
} else {
$param= ‘user function ‘.htmlspecialchars($arg).’()’;
}
} elseif (is_array($arg)) {
reset($arg);
$class = current($arg);
$method = next($arg);
if (is_string($class)) {
$param = ’static class ‘.$class.’::’.$method.’()’;
} elseif (is_object($class)){
$param = ‘object of class ‘.get_class($class).’->’.$method.’()’;
} else {
$param = ‘unknown “‘.strval($arg).’”‘;
}
} else {
$param = ‘unknown “‘.strval($arg).’”‘;
}
return ‘<span’.cmsDebug::getStyle(cmsDebug::$style_callable).’>callable</span> ‘.$param.”;
}
}
А вот более интересная реализация модуля отображения массивов с рекурсивным вызовом и мозможностью сворачивать вложенные массивы:
<?php
// +--------------------------------------------------------+---------------+
// | Проект: Отладчик | PHP Version 5 |
// +--------------------------------------------------------+---------------+
// Реализация модуля отладчика
class DebugArray implements DebugPlugin
{
public static $collapse_level = 1;
public static $max_level = 10;
public static $tab = ' ';
private static $onclick = "var s1=document.getElementById('dump_array_close_%s').style; var s2=document.getElementById('dump_array_open_%s').style; s1.display=s1.display=='none'?'inline':'none'; s2.display=s2.display=='none'?'inline':'none';";
/**
* Функция проверки может ли модуль обработать данный тип данных
*
* @param mixed $arg
* @return unknown
*/
static public function is($arg) {
return is_array($arg);
}
/**
* Форматирование массива
*
* @param mixed $arg
* @param integer $tab
*/
static public function format($arg, $tab=0)
{
static $id; // генерация индификатора узла
if (self::$max_level > $tab) {
if(is_null($id)) {
$id = 0;
}
$id++;
if(self::$collapse_level < $tab) {
$open = cmsDebug::getStyle('display:none;border:1px;');
$close= '';
} else {
$open = '';
$close= cmsDebug::getStyle('display:none;');
}
// Элемент при закрытом узле
$output = "<span id=\"dump_array_close_$id\"$close>";
$output .= '<span'.cmsDebug::getStyle('cursor:hand;cursor:pointer;'.cmsDebug::$style_array).' onClick="'.sprintf(self::$onclick,$id,$id).'">array['.count($arg).']</span> (…)’.”n”;
$output .= ‘</span>’;
// Элемент при открытом узле
$output .= “<span id=\”dump_array_open_$id\”$open>”;
$output .= ‘<span’.cmsDebug::getStyle(’cursor:hand;cursor:pointer;’.cmsDebug::$style_array).’ onClick=”‘.sprintf(self::$onclick,$id,$id).’”>array['.count($arg).']</span>’.”n”;
$tab++;
$output .= str_repeat(self::$tab,$tab).”(n”;
$tab++;
// Элементы массива
foreach($arg as $key=>$value) {
$output .= str_repeat(self::$tab,$tab).’<span’.cmsDebug::getStyle(cmsDebug::$style_array).’>['.$key.']</span> => ‘.Debug::varDumpExtend($value,$tab).”";
}
$tab–;
$output .= str_repeat(self::$tab,$tab).”)n”;
$tab–;
$output .= “</span>”;
} else {
$output=”WARNING: max dump leveln”;
}
return $output;
}
}
В данной реализации есть еще много путей куда двигаться, но уже сейчас можно например одним движением руки добавить модуль для отображения сгенерированных библиотекой GD изображений или объектов Directory или DirectoryIterator с отображением содержимого объявленных директорий и так до бесконечности.
И на последок: отображение GD изображения с интеграцией его в HTML, чтоб не создавать временный файлы. К сожалению, работает везде кроме IE.
<?php
// +--------------------------------------------------------+---------------+
// | Проект: Отладчик | PHP Version 5 |
// +--------------------------------------------------------+---------------+
// Реализация модуля отладчика
class DebugImage
{
/**
* Функция проверки может ли модуль обработать данный тип данных
*
* @param mixed $arg
* @return unknown
*/
static public function is($arg) {
return is_resource($arg) && get_resource_type($arg)=='gd';
}
/**
* Форматирование массива
*
* @param mixed $arg
* @param integer $tab
*/
static public function format($arg, $tab=0)
{
ob_start();
imagepng($arg);
$content = ob_get_contents();
ob_end_clean();
return '<span'.cmsDebug::getStyle(cmsDebug::$style_resource).'>resource</span> gd image <img src="data:image/png;base64,'.base64_encode($content).'" style="vertical-align:top;" alt="IE not support" border="0" />'."n";
}
}
Только не забудьте добавить имя класса DebugImage в свойство plugins класса Debug.
Пример работы:

Надо плагин править
чтоб корректно в коде отображались знаки больше и меньше. Вот так всегда. Берешь готовый инструмент, а его надо доделывать…
Нельзя-ли выложить ZIP с кодом?
Описываемый код я упрощал т.к. в рабочем варианте он “привязан” к системе и тянет за собой другие библиотеки. В будущем планирую этот отладчик и еще несколько интересных библиотек сделать самостоятельными скриптами. Вот тогда и появятся в архивы. Если все таки хочется повозиться с данным кодом пишите пришлю архив.
Окончание не видно строк почему-то
Возможно стоит дополнительно рассмотреть
print_debug_backtrace() + set_error_handler