1
0
mirror of https://github.com/dg/dibi.git synced 2025-08-03 04:37:35 +02:00

- added DibiProfiler (experimental)

- removed dibi::addHandler() & dibi::startLogger()
This commit is contained in:
David Grudl
2008-10-22 11:44:11 +00:00
parent 1e33b67e37
commit 32dd3969a3
8 changed files with 337 additions and 179 deletions

View File

@@ -72,6 +72,7 @@ require_once dirname(__FILE__) . '/libs/DibiTable.php';
require_once dirname(__FILE__) . '/libs/DibiDataSource.php';
require_once dirname(__FILE__) . '/libs/DibiFluent.php';
require_once dirname(__FILE__) . '/libs/DibiDatabaseInfo.php';
require_once dirname(__FILE__) . '/libs/DibiProfiler.php';
@@ -239,7 +240,7 @@ class dibi
* Retrieve active connection.
*
* @param string connection registy name
* @return object DibiConnection object.
* @return DibiConnection
* @throws DibiException
*/
public static function getConnection($name = NULL)
@@ -275,6 +276,18 @@ class dibi
/**
* Retrieve active connection profiler.
* @return IDibiProfiler
* @throws DibiException
*/
public static function getProfiler()
{
return self::getConnection()->getProfiler();
}
/********************* monostate for active connection ****************d*g**/
@@ -676,64 +689,6 @@ class dibi
/********************* event handling ****************d*g**/
/**
* Add new event handler.
*
* @param callback
* @return void
* @throws InvalidArgumentException
*/
public static function addHandler($callback)
{
if (!is_callable($callback)) {
throw new InvalidArgumentException("Invalid callback.");
}
self::$handlers[] = $callback;
}
/**
* Event notification (events: exception, connected, beforeQuery, afterQuery, begin, commit, rollback).
*
* @param DibiConnection
* @param string event name
* @param mixed
* @return void
*/
public static function notify(DibiConnection $connection = NULL, $event, $arg = NULL)
{
foreach (self::$handlers as $handler) {
call_user_func($handler, $connection, $event, $arg);
}
}
/**
* Enable profiler & logger.
*
* @param string filename
* @param bool log all queries?
* @return DibiProfiler
*/
public static function startLogger($file, $logQueries = FALSE)
{
require_once dirname(__FILE__) . '/libs/DibiLogger.php';
$logger = new DibiLogger($file);
$logger->logQueries = $logQueries;
self::addHandler(array($logger, 'handler'));
return $logger;
}
/********************* misc tools ****************d*g**/

View File

@@ -41,6 +41,12 @@ class DibiConnection extends DibiObject
*/
private $driver;
/**
* Profiler
* @var IDibiProfiler
*/
private $profiler;
/**
* Is connected?
* @var bool
@@ -89,7 +95,7 @@ class DibiConnection extends DibiObject
include_once dirname(__FILE__) . "/../drivers/$driver.php";
if (!class_exists($class, FALSE)) {
throw new DibiException("Unable to create instance of dibi driver class '$class'.");
throw new DibiException("Unable to create instance of dibi driver '$class'.");
}
}
@@ -97,6 +103,17 @@ class DibiConnection extends DibiObject
$this->config = $config;
$this->driver = new $class;
if (!empty($config['profiler'])) {
$class = $config['profiler'];
if (is_numeric($class) || is_bool($class)) {
$class = 'DibiProfiler';
}
if (!class_exists($class)) {
throw new DibiException("Unable to create instance of dibi profiler '$class'.");
}
$this->setProfiler(new $class);
}
if (empty($config['lazy'])) {
$this->connect();
}
@@ -125,9 +142,14 @@ class DibiConnection extends DibiObject
final protected function connect()
{
if (!$this->connected) {
if ($this->profiler !== NULL) {
$ticket = $this->profiler->before($this, IDibiProfiler::CONNECT);
}
$this->driver->connect($this->config);
$this->connected = TRUE;
dibi::notify($this, 'connected');
if (isset($ticket)) {
$this->profiler->after($ticket);
}
}
}
@@ -146,7 +168,6 @@ class DibiConnection extends DibiObject
}
$this->driver->disconnect();
$this->connected = FALSE;
dibi::notify($this, 'disconnected');
}
}
@@ -271,11 +292,22 @@ class DibiConnection extends DibiObject
{
$this->connect();
if ($this->profiler !== NULL) {
$event = IDibiProfiler::QUERY;
if (preg_match('#\s*(SELECT|UPDATE|INSERT|DELETE)#i', $sql, $matches)) {
static $events = array(
'SELECT' => IDibiProfiler::SELECT, 'UPDATE' => IDibiProfiler::UPDATE,
'INSERT' => IDibiProfiler::INSERT, 'DELETE' => IDibiProfiler::DELETE,
);
$event = $events[strtoupper($matches[1])];
}
$ticket = $this->profiler->before($this, $event, $sql);
}
// TODO: move to profiler?
dibi::$numOfQueries++;
dibi::$sql = $sql;
dibi::$elapsedTime = FALSE;
$time = -microtime(TRUE);
dibi::notify($this, 'beforeQuery', $sql);
if ($res = $this->driver->query($sql)) { // intentionally =
$res = new DibiResult($res, $this->config);
@@ -284,8 +316,10 @@ class DibiConnection extends DibiObject
$time += microtime(TRUE);
dibi::$elapsedTime = $time;
dibi::$totalTime += $time;
dibi::notify($this, 'afterQuery', $res);
if (isset($ticket)) {
$this->profiler->after($ticket, $res);
}
return $res;
}
@@ -332,9 +366,14 @@ class DibiConnection extends DibiObject
if ($this->inTxn) {
throw new DibiException('There is already an active transaction.');
}
if ($this->profiler !== NULL) {
$ticket = $this->profiler->before($this, IDibiProfiler::BEGIN);
}
$this->driver->begin();
$this->inTxn = TRUE;
dibi::notify($this, 'begin');
if (isset($ticket)) {
$this->profiler->after($ticket);
}
}
@@ -348,9 +387,14 @@ class DibiConnection extends DibiObject
if (!$this->inTxn) {
throw new DibiException('There is no active transaction.');
}
if ($this->profiler !== NULL) {
$ticket = $this->profiler->before($this, IDibiProfiler::COMMIT);
}
$this->driver->commit();
$this->inTxn = FALSE;
dibi::notify($this, 'commit');
if (isset($ticket)) {
$this->profiler->after($ticket);
}
}
@@ -364,9 +408,14 @@ class DibiConnection extends DibiObject
if (!$this->inTxn) {
throw new DibiException('There is no active transaction.');
}
if ($this->profiler !== NULL) {
$ticket = $this->profiler->before($this, IDibiProfiler::ROLLBACK);
}
$this->driver->rollback();
$this->inTxn = FALSE;
dibi::notify($this, 'rollback');
if (isset($ticket)) {
$this->profiler->after($ticket);
}
}
@@ -490,6 +539,31 @@ class DibiConnection extends DibiObject
/********************* profiler ****************d*g**/
/**
* @param IDibiProfiler
* @return void
*/
public function setProfiler(IDibiProfiler $profiler = NULL)
{
$this->profiler = $profiler;
}
/**
* @return IDibiProfiler
*/
public function getProfiler()
{
return $this->profiler;
}
/********************* misc ****************d*g**/

View File

@@ -61,7 +61,7 @@ class DibiDriverException extends DibiException implements /*Nette::*/IDebuggabl
{
parent::__construct($message, (int) $code);
$this->sql = $sql;
dibi::notify(NULL, 'exception', $this);
// TODO: add $profiler->exception($this);
}

View File

@@ -1,106 +0,0 @@
<?php
/**
* dibi - tiny'n'smart database abstraction layer
* ----------------------------------------------
*
* Copyright (c) 2005, 2008 David Grudl (http://davidgrudl.com)
*
* This source file is subject to the "dibi license" that is bundled
* with this package in the file license.txt.
*
* For more information please see http://dibiphp.com
*
* @copyright Copyright (c) 2005, 2008 David Grudl
* @license http://dibiphp.com/license dibi license
* @link http://dibiphp.com
* @package dibi
* @version $Id$
*/
/**
* dibi basic logger & profiler (experimental).
*
* @author David Grudl
* @copyright Copyright (c) 2005, 2008 David Grudl
* @package dibi
*/
final class DibiLogger extends DibiObject
{
/** @var string Name of the file where SQL errors should be logged */
private $file;
/** @var bool */
public $logErrors = TRUE;
/** @var bool */
public $logQueries = TRUE;
/**
* @param string filename
*/
public function __construct($file)
{
$this->file = $file;
}
/**
* Event handler (events: exception, connected, beforeQuery, afterQuery, begin, commit, rollback).
*
* @param DibiConnection
* @param string event name
* @param mixed
* @return void
*/
public function handler($connection, $event, $arg)
{
if ($event === 'afterQuery' && $this->logQueries) {
$this->write(
"OK: " . dibi::$sql
. ($arg instanceof DibiResult ? ";\n-- rows: " . count($arg) : '')
. "\n-- takes: " . sprintf('%0.3f', dibi::$elapsedTime * 1000) . ' ms'
. "\n-- driver: " . $connection->getConfig('driver')
. "\n-- " . date('Y-m-d H:i:s')
. "\n\n"
);
return;
}
if ($event === 'exception' && $this->logErrors) {
// $arg is DibiDriverException
$message = $arg->getMessage();
$code = $arg->getCode();
if ($code) {
$message = "[$code] $message";
}
$this->write(
"ERROR: $message"
. "\n-- SQL: " . dibi::$sql
. "\n-- driver: " //. $connection->getConfig('driver')
. ";\n-- " . date('Y-m-d H:i:s')
. "\n\n"
);
return;
}
}
private function write($message)
{
$handle = fopen($this->file, 'a');
if (!$handle) return; // or throw exception?
flock($handle, LOCK_EX);
fwrite($handle, $message);
fclose($handle);
}
}

182
dibi/libs/DibiProfiler.php Normal file
View File

@@ -0,0 +1,182 @@
<?php
/**
* dibi - tiny'n'smart database abstraction layer
* ----------------------------------------------
*
* Copyright (c) 2005, 2008 David Grudl (http://davidgrudl.com)
*
* This source file is subject to the "dibi license" that is bundled
* with this package in the file license.txt.
*
* For more information please see http://dibiphp.com
*
* @copyright Copyright (c) 2005, 2008 David Grudl
* @license http://dibiphp.com/license dibi license
* @link http://dibiphp.com
* @package dibi
* @version $Id$
*/
/**
* dibi basic logger & profiler (experimental).
*
* @author David Grudl
* @copyright Copyright (c) 2005, 2008 David Grudl
* @package dibi
*/
class DibiProfiler extends DibiObject implements IDibiProfiler
{
/** @var string Name of the file where SQL errors should be logged */
private $file;
/** @var bool log to firebug? */
private $useFirebug;
/** @var int */
private $filter = self::ALL;
/** @var array */
public $tickets = array();
/** @var array */
public static $table = array(array('Time', 'SQL Statement', 'Rows', 'Connection'));
public function __construct()
{
$this->useFirebug = isset($_SERVER['HTTP_USER_AGENT']) && strpos($_SERVER['HTTP_USER_AGENT'], 'FirePHP/');
}
/**
* @param string filename
* @return void
*/
public function setFile($file)
{
$this->file = $file;
}
/**
* @param int
* @return void
*/
public function setFilter($filter)
{
$this->filter = (int) $filter;
}
/**
* Before event notification.
* @param DibiConnection
* @param int event name
* @param string sql
* @return int
*/
public function before(DibiConnection $connection, $event, $sql = NULL)
{
$this->tickets[] = array($connection, $event, $sql);
end($this->tickets);
return key($this->tickets);
}
/**
* After event notification.
* @param int
* @param DibiResult
* @return void
*/
public function after($ticket, $res = NULL)
{
if (!isset($this->tickets[$ticket])) {
throw new InvalidArgumentException('Bad ticket number.');
}
list($connection, $event, $sql) = $this->tickets[$ticket];
if (($event & $this->filter) === 0) return;
if ($event & self::QUERY) {
if ($this->useFirebug) {
self::$table[] = array(
sprintf('%0.3f', dibi::$elapsedTime * 1000),
trim(preg_replace('#\s+#', ' ', $sql)),
$res instanceof DibiResult ? count($res) : '-',
$connection->getConfig('driver') . '/' . $connection->getConfig('name')
);
$caption = 'dibi profiler (' . dibi::$numOfQueries . ' SQL queries took ' . sprintf('%0.3f', dibi::$totalTime * 1000) . ' ms)';
$payload['FirePHP.Firebug.Console'][] = array('TABLE', array($caption, self::$table));
$payload = json_encode($payload);
foreach (str_split($payload, 4998) as $num => $s) {
header('X-FirePHP-Data-' . str_pad(++$num, 12, '0', STR_PAD_LEFT) . ': ' . $s);
}
}
if ($this->file) {
$this->writeFile(
"OK: " . $sql
. ($res instanceof DibiResult ? ";\n-- rows: " . count($res) : '')
. "\n-- takes: " . sprintf('%0.3f', dibi::$elapsedTime * 1000) . ' ms'
. "\n-- driver: " . $connection->getConfig('driver') . '/' . $connection->getConfig('name')
. "\n-- " . date('Y-m-d H:i:s')
. "\n\n"
);
}
}
}
/**
* After exception notification.
* @param DibiDriverException
* @return void
*/
public function exception(DibiDriverException $exception)
{
if ((self::EXCEPTION & $this->filter) === 0) return;
if ($this->useFirebug) {
// TODO: implement
}
if ($this->file) {
$message = $exception->getMessage();
$code = $exception->getCode();
if ($code) {
$message = "[$code] $message";
}
$this->writeFile(
"ERROR: $message"
. "\n-- SQL: " . dibi::$sql
. "\n-- driver: " //. $connection->getConfig('driver')
. ";\n-- " . date('Y-m-d H:i:s')
. "\n\n"
);
}
}
private function writeFile($message)
{
$handle = fopen($this->file, 'a');
if (!$handle) return; // or throw exception?
flock($handle, LOCK_EX);
fwrite($handle, $message);
fclose($handle);
}
}

View File

@@ -29,7 +29,7 @@ interface IDibiVariable
/**
* Format for SQL.
*
* @param object DibiTranslator
* @param DibiTranslator
* @param string optional modifier
* @return string SQL code
*/
@@ -54,6 +54,57 @@ interface IDataSource extends Countable, IteratorAggregate
/**
* Defines method that must profiler implement.
* @package dibi
*/
interface IDibiProfiler
{
/**#@+ event type */
const CONNECT = 1;
const SELECT = 4;
const INSERT = 8;
const DELETE = 16;
const UPDATE = 32;
const QUERY = 60;
const BEGIN = 64;
const COMMIT = 128;
const ROLLBACK = 256;
const TRANSACTION = 448;
const EXCEPTION = 512;
const ALL = 1023;
/**#@-*/
/**
* Before event notification.
* @param DibiConnection
* @param int event name
* @param string sql
* @return int
*/
function before(DibiConnection $connection, $event, $sql = NULL);
/**
* After event notification.
* @param int
* @param DibiResult
* @return void
*/
function after($ticket, $result = NULL);
/**
* After exception notification.
* @param DibiDriverException
* @return void
*/
function exception(DibiDriverException $exception);
}
/**
* dibi driver interface.
*

View File

@@ -3,17 +3,18 @@
require_once '../dibi/dibi.php';
// enable log to this file, TRUE means "log all queries"
dibi::startLogger('log.sql', TRUE);
dibi::connect(array(
'driver' => 'sqlite',
'database' => 'sample.sdb',
'profiler' => TRUE,
));
// enable log to this file
dibi::getProfiler()->setFile('log.sql');
try {
$res = dibi::query('SELECT * FROM [customers] WHERE [customer_id] = %i', 1);

View File

@@ -7,6 +7,7 @@ require_once '../dibi/dibi.php';
dibi::connect(array(
'driver' => 'sqlite',
'database' => 'sample.sdb',
'profiler' => TRUE,
));