Отладка и статистика запросов в PHP PDO

09.01.2010 12:38 / Артём Волк / 4189 просмотров / ...

PDO часто используется в качестве низкоуровневой библиотеки доступа к данным, один из примеров — библиотека Outlet ORM. В прекрасной библиотеке доступа к данным DbSimple есть удобные возможности по записи кода исполняемых SQL-запросов в лог и встроенные средства по подсчёту их количества и времени выполнения. Всего этого в PDO по умолчанию нет.

Решение нашлось совершенно случайно, спасибо Alvaro Carrasco, одному из авторов Outlet ORM за подсказку. Способ заключается в создании собственных наследников от классов PDO и PDOStatement, подобное давно реализовано для Propel'a. Уже пытаясь сделать это самостоятельно я так и не нашел способа подменить класс для создания экземпляров PDOStatement, делается это действительно нетривиально :).

class DebugPDO extends PDO 
{ 
	protected 
		$query_count = 0,
		$exec_time = 0;

	private 
		$logger_callback = NULL;

	public function __construct($dsn, $username = null, $password = null, $driver_options = array(), $logger_callback = NULL)
	{
		parent::__construct($dsn, $username, $password, $driver_options);
		$this->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
		if (!$this->getAttribute(PDO::ATTR_PERSISTENT)) 
		{
			$this->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('DebugPDOStatement', array($this)));
		}
		$this->logger_callback = $logger_callback;
	}

	public function increment_query_count()
	{
		$this->query_count++;
	}

	public function get_query_count()
	{
		return $this->query_count;
	}

	public function add_exec_time($time)
	{
		$this->exec_time += $time;
	}

	public function get_exec_time_ms()
	{
		return $this->exec_time;
	}

	public function log()
	{
		if (!is_null($this->logger_callback))
		{
			$args = func_get_args();
			call_user_func_array($this->logger_callback, $args); 
		}
	}

	public function exec($sql)
	{
		$this->log($sql, 'Query (PDO->exec())');
		$this->increment_query_count();

		$start = microtime(true);
		$return = parent::exec($sql);	
		$finish = microtime(true);
		$this->add_exec_time($finish-$start);
		return $return;			
	}

	public function query()
	{	
		$this->increment_query_count();
		$args = func_get_args();
		$this->log($args, 'Query (PDO->query())');

		$start = microtime(true);
		$return = call_user_func_array(array($this, 'parent::query'), $args);
		$finish = microtime(true);
		$this->add_exec_time($finish-$start);

		return $return;
	}
}

class DebugPDOStatement extends PDOStatement
{
	protected 
		$pdo;

	private 
		$params = array();

	protected static $type_map = array(
		PDO::PARAM_BOOL => "PDO::PARAM_BOOL",
		PDO::PARAM_INT => "PDO::PARAM_INT",
		PDO::PARAM_STR => "PDO::PARAM_STR",
		PDO::PARAM_LOB => "PDO::PARAM_LOB",
		PDO::PARAM_NULL => "PDO::PARAM_NULL"
	);

	protected function __construct(DebugPDO $pdo)
	{
		$this->pdo = $pdo;
	}

	public function execute($input_parameters = null)
	{
		$this->pdo->log($this->queryString, 'Query (PDOStatement->execute())');		
		if (!empty($this->params))
		{ 
			$this->pdo->log($this->params, 'Parameters');
		}	
		if (!empty($input_parameters))
		{
			$this->pdo->log($input_parameters, 'Parameters');
		}		
		$this->pdo->increment_query_count();		

		$start = microtime(true);
		$return = parent::execute($input_parameters);
		$finish = microtime(true);
		$this->pdo->add_exec_time($finish-$start);

		return $return;	
	}

	public function bindValue($pos, $value, $type = PDO::PARAM_STR)
	{
		$type_name = isset(self::$type_map[$type]) ? self::$type_map[$type] : '(default)';
		$this->params[] = array($pos, $value, $type_name);
		$return	= parent::bindValue($pos, $value, $type);
		return $return;		
	}

}

Пример использования (за пример спасибо Павлу Устинову (Arigato))

require_once('debugpdo.classes.php');

function logger($sql)
{
	var_dump($sql);
}

$dbh = new DebugPDO("mysql:host=localhost;dbname=example", 'root', '', array(), 'logger');

$sth = $dbh->query('SELECT * FROM `users`');
$result = $sth->fetchAll(PDO::FETCH_ASSOC);

$query_count = $dbh->get_query_count();
$query_time = $dbh->get_exec_time_ms();
printf('<h1>Queries count: %d, execution time: %f</h1>', $query_count, $query_time);

var_dump($result);