From 32dd3969a3221f2f8a1510a344f9a848f20cb45c Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 22 Oct 2008 11:44:11 +0000 Subject: [PATCH] - added DibiProfiler (experimental) - removed dibi::addHandler() & dibi::startLogger() --- dibi/dibi.php | 73 +++----------- dibi/libs/DibiConnection.php | 90 +++++++++++++++-- dibi/libs/DibiException.php | 2 +- dibi/libs/DibiLogger.php | 106 -------------------- dibi/libs/DibiProfiler.php | 182 +++++++++++++++++++++++++++++++++++ dibi/libs/interfaces.php | 53 +++++++++- examples/logger.php | 9 +- examples/profiler.php | 1 + 8 files changed, 337 insertions(+), 179 deletions(-) delete mode 100644 dibi/libs/DibiLogger.php create mode 100644 dibi/libs/DibiProfiler.php diff --git a/dibi/dibi.php b/dibi/dibi.php index 5579ceb3..e8d23cec 100644 --- a/dibi/dibi.php +++ b/dibi/dibi.php @@ -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**/ diff --git a/dibi/libs/DibiConnection.php b/dibi/libs/DibiConnection.php index f99903e7..91aac3eb 100644 --- a/dibi/libs/DibiConnection.php +++ b/dibi/libs/DibiConnection.php @@ -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**/ diff --git a/dibi/libs/DibiException.php b/dibi/libs/DibiException.php index 6ef95fda..d47c252c 100644 --- a/dibi/libs/DibiException.php +++ b/dibi/libs/DibiException.php @@ -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); } diff --git a/dibi/libs/DibiLogger.php b/dibi/libs/DibiLogger.php deleted file mode 100644 index fca238ac..00000000 --- a/dibi/libs/DibiLogger.php +++ /dev/null @@ -1,106 +0,0 @@ -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); - } - -} diff --git a/dibi/libs/DibiProfiler.php b/dibi/libs/DibiProfiler.php new file mode 100644 index 00000000..eaee26d9 --- /dev/null +++ b/dibi/libs/DibiProfiler.php @@ -0,0 +1,182 @@ +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); + } + +} diff --git a/dibi/libs/interfaces.php b/dibi/libs/interfaces.php index dbf58c46..821d513c 100644 --- a/dibi/libs/interfaces.php +++ b/dibi/libs/interfaces.php @@ -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. * diff --git a/examples/logger.php b/examples/logger.php index 306ac821..1abea350 100644 --- a/examples/logger.php +++ b/examples/logger.php @@ -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); diff --git a/examples/profiler.php b/examples/profiler.php index 4e762240..9e80ea1d 100644 --- a/examples/profiler.php +++ b/examples/profiler.php @@ -7,6 +7,7 @@ require_once '../dibi/dibi.php'; dibi::connect(array( 'driver' => 'sqlite', 'database' => 'sample.sdb', + 'profiler' => TRUE, ));