Scripts

PDO debug class

Een uitbreiding op PDO welke alle prepared statements opvangt en opslaat. Bij het weergeven kan je de parameters die je aan je prepared statements hebt gegeven terug laten plaatsen om te zien hoe de query eruit had gezien als je hem zonder placeholders had geschreven. Als het goed is kan hij ook goed overweg met het meerdere malen uitvoeren van hetzelfde statement, maar dan met andere waarden. Hij neemt ook nog even de tijd op, en het aantal affected rows. Doordat het een uitbreiding van PDO zelf is, kan je hem zo in de plaats van PDO gebruiken. Verder is er de PDO::dump functie, welke je opgemaakte HTML oplevert. Niet wat je in een klasse zou verwachten, maar wel handig in combinatie met register_shutdown_function. download : http://phphulp.ikhoefgeen.nl/debug_pdo.phps Last note: hij doet niet aan foutafhandeling. Aangezien de meesten PDO in combinatie met exceptions gebruiken zou dat geen probleem moeten opleveren. Update 07-02-2009 : - Hernoemd van PDO_Debug naar Debug_PDO. Is wat logischer wanneer je Poor Man's Namespacing gebruikt (oftewel, autoload + klassenaam als padnaam) - Ondersteund nu ook anonieme placeholders (de ?'s in je statement) - Wat advanced features toegevoegd die ik zelf toch graag blijk te gebruiken, zie hieronder. Met Debug_PDO::add_filter kan je een callback meegeven die beslist of de query wel of niet wordt uitgevoerd. Het eerste argument aan deze callback is een snapshot van de query, het tweede argument mag een reference zijn naar de $success variabele, de return-variabele van Debug_PDO_Statement::execute. Zo kan je bepaalde queries wel verhinderen van uitvoeren, maar je script toch voorhouden dat ze prima lukten. Debug_PDO::add_listener kan je een callback meegeven welke bij het uitvoeren van een query wordt aangeroepen. Op die manier kan je live meekijken welke queries er door je script worden uitgevoerd. Het eerste en enige argument aan de callback is een snapshot. Merk wel op dat er nog geen begin- en eindtijden beschikbaar zijn in dit snapshot, de callback wordt voor de werkelijke execute aanroep al geroepen, niet na. Debug_PDO::add_decorator kan je een mooie opmaakfunctie meegeven. Deze wordt aangeroepen voor iedere placeholder die bij Debug_PDO_Snapshot::query_as_sql() wordt vervangen door z'n werkelijke waarde. Is bijvoorbeeld nuttig om hele grote strings in te korten, of HTML opmaak om je waarden te zetten zodat ze beter zichtbaar zijn, html te escapen, of zoals in het voorbeeld hieronder leuke terminal kleurtjes eromheen te plakken.

pdo-debug-class
Simpel voorbeeldje:
<?php
$pdo = new Debug_PDO('mysql:host=localhost;dbname=test', 'root', '');
register_shutdown_function(array($pdo, 'dump'));
?>

Advanced voorbeeldje (gebruikt mijn log_socket scriptje http://phphulp.nl/php/scripts/11/1465/):
<?php
$pdo = new Debug_PDO('mysql:host=localhost;dbname=test', 'root', '');

/* Alle INSERT INTO queries niet uitvoeren, maar wel als succesvol merken */
$pdo->add_filter(function($snapshot, &$success) {
	if(strstr($snapshot->query, 'INSERT INTO')) {
		$success = true;
		return false;
	} 
	
	return true;
});

/* In het antwoord van Debug_PDO_Snapshot::query_as_sql() de argumenten met
 * wat terminal-kleurtjes versieren, zodat ze goed opvallen */
$pdo->add_decorator(function($value) {
	if($value === null) {
		return chr(0x1B) . "[0;37;45mNULL" . chr(0x1B). "[0;32;40m";
	} else {
		return chr(0x1B) . "[0;37;41m" . $value . chr(0x1B). "[0;32;40m";
	}
});

/* Alle INSERT INTO queries (die dus niet uitgevoerd worden) over de log_socket
 * sturen zodat ze gekleurd en wel in mijn recieve programmaatje verschijnen */
$pdo->add_listener(function($snapshot) {
	if(strstr($snapshot->query, 'INSERT INTO')) {
		log_send((string) $snapshot);
	}
});
?>


De klassen:
<?php

<?php

class Debug_PDO extends PDO {
	
	public $snapshots = array();
	
	protected $_filters = array();
	
	protected $_listeners = array();
	
	protected $_decorators = array();
	
	public function __construct($uri, $username = null, $password = null) {
		parent::__construct($uri, $username, $password);
		$this->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('Debug_PDO_Statement', array($this)));
	}
	
	public function dump() {
		foreach($this->snapshots as $snapshot) {
			echo "<pre>" . $snapshot . "</pre><br><br>\n";
		}
	}
	
	public function add_listener($listener) {
		if(!is_callable($listener)) {
			throw new InvalidArgumentException('add_listener expects a valid callback');
		}
		
		$this->_listeners[] = $listener;
	}
	
	public function add_filter($filter) {
		if(!is_callable($filter)) {
			throw new InvalidArgumentException('add_filter expects a valid callback');
		}
		
		$this->_filters[] = $filter;
	}
	
	public function add_decorator($decorator) {
		if(!is_callable($decorator)) {
			throw new InvalidArgumentException('add_decorator expects a valid callback');
		}

		$this->_decorators[] = $decorator;
	}
	
	public function should_execute_statement(Debug_PDO_Snapshot $snapshot, &$success) {
		foreach($this->_filters as $filter) {
			if(!call_user_func_array($filter, array($snapshot, &$success))) return false;
		}
		
		return true;
	}
	
	public function add_snapshot(Debug_PDO_Snapshot $snapshot) {
		$this->snapshots[] = $snapshot;

		$snapshot->set_decorators($this->_decorators);
		
		foreach($this->_listeners as $listener) {
			call_user_func($listener, $snapshot, $this);
		}
	}

	/*public function lastInsertId() {
		return $this->snapshots[count($this->snapshots) - 1]->insert_id;
	}*/
}

class Debug_PDO_Statement extends PDOStatement {
	
	protected $_delegate;
	
	protected $_parameters = array();
	
	protected function __construct($delegate) {
		$this->_delegate = $delegate;
	}
	
	public function execute($arguments = null) {
		
		$snapshot = $this->make_snapshot($arguments);
		
		$this->_delegate->add_snapshot($snapshot);
		
		if($this->_delegate->should_execute_statement($snapshot, $success)) {
		
			$snapshot->timer->start();
		
			$success = parent::execute($arguments);
		
			$snapshot->timer->stop();
			
			$snapshot->insert_id = $this->_delegate->lastInsertId();
		
			$snapshot->affected_rows = $this->rowCount();
		}
		
		return $success;
	}
	
	public function bindParam($name, &$value, $data_type = null, $length = null, $driver_options = null) {
		$this->_parameters[$name] = $this->_delegate->quote($value);
		
		return parent::bindParam($name, $value, $data_type, $length, $driver_options);
	}
	
	public function bindValue($name, $value, $data_type = null, $length = null, $driver_options = null) {
		return $this->bindParam($name, $value, $data_type, $length, $driver_options);
	}
	
	public function make_snapshot($arguments) {
		$snapshot = new Debug_PDO_Snapshot();
		$snapshot->query = $this->queryString;
		$snapshot->parameters = $this->_parameters;
		$snapshot->anonymous_parameters = $arguments;
		
		return $snapshot;
	}
}

class Debug_PDO_Snapshot {
	
	public $query;
	
	public $parameters;
	
	public $insert_id;
	
	public $anonymous_parameters;
	
	private $_anonymous_parameter_index = 0;
	
	public $affected_rows;
	
	public $timer;
	
	protected $_decorators = array();
	
	public function __construct() {
		$this->timer = new Debug_PDO_Timer();
	}
		
	public function query_as_sql() {
		$this->_anonymous_parameter_index = 0;
		
		$query = self::_remove_indent(preg_replace_callback('/(?:(\:([a-zA-Z0-9\_]+))|(\?))/', array($this, '_replace_placeholder'), $this->query));
		
		return $query;
	}
	
	public function execution_time() {
		return $this->timer->delta();
	}
	
	public function set_decorators(array $decorators) {
		$this->_decorators = $decorators;
	}
	
	public function __toString() {
		return sprintf("%s\n%f seconds, %d affected rows\n",
			$this->query_as_sql(), $this->execution_time(), $this->affected_rows);
	}
	
	protected function _replace_placeholder($matches) {
		if($matches[0] == '?') {
			$value = $this->anonymous_parameters[$this->_anonymous_parameter_index++];
		} else {
			$value = $this->parameters[$matches[1]];
		}
		
		foreach($this->_decorators as $decorator) {
			$value = call_user_func($decorator, $value);
		}
		
		return $value;
	}
	
	static protected function _remove_indent($text) {
		$lines = explode("\n", $text);
		
		$smallest_indent = INF;
		
		foreach($lines as $line) {
			
			if(trim($line) == '') continue;
			
			$leading_tabs = 0;
			while($line[$leading_tabs] == "\t") $leading_tabs++;
			
			if($leading_tabs < $smallest_indent) {
				$smallest_indent = $leading_tabs;
			}
		}
		
		foreach($lines as $line_number => $line) {
			if(trim($line) == '') continue;
			 
			$lines[$line_number] = substr($line, $smallest_indent);
		}
		
		return implode("\n", $lines);
	}
}

class Debug_PDO_Timer {
	
	protected $_start_time;
	
	protected $_stop_time;
	
	public function start() {
		$this->_start_time = microtime(true);
	}
	
	public function stop() {
		$this->_stop_time = microtime(true);
	}
	
	public function delta() {
		return $this->_start_time && $this->_stop_time ? $this->_stop_time - $this->_start_time : false;
	}
}
?>

Reacties

0
Nog geen reacties.