Подписка на RSS-ленту
последние обновления
новостей и публикаций сайта

Отладки приложения PHP или надоело писать print_r

Все 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.
Пример работы:

Пример работы

Отзывов (5) на

Отладки приложения PHP или надоело писать print_r

  • Валентин Гернович |

    Надо плагин править :( чтоб корректно в коде отображались знаки больше и меньше. Вот так всегда. Берешь готовый инструмент, а его надо доделывать…

  • Slawa |

    Нельзя-ли выложить ZIP с кодом?

  • Валентин Гернович |

    Описываемый код я упрощал т.к. в рабочем варианте он “привязан” к системе и тянет за собой другие библиотеки. В будущем планирую этот отладчик и еще несколько интересных библиотек сделать самостоятельными скриптами. Вот тогда и появятся в архивы. Если все таки хочется повозиться с данным кодом пишите пришлю архив.

  • ареда |

    Окончание не видно строк почему-то

  • Саша |

    Возможно стоит дополнительно рассмотреть
    print_debug_backtrace() + set_error_handler

У вас есть мнение?
Оставьте свой отзыв:

Имя *
Почта *
Вы можете использовать следующие теги:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>