Implemented changes requested by FuzeWorks\Application.

- TracyBridge is now fully functional.
This commit is contained in:
Abel Hoogeveen 2019-03-01 11:15:30 +01:00
parent 330d521f98
commit 6f1b1b814d
No known key found for this signature in database
GPG Key ID: 96C2234920BF4292
9 changed files with 157 additions and 53 deletions

View File

@ -55,11 +55,11 @@ abstract class DatabaseDriver
* Log information about a query. Used for debugging issues * Log information about a query. Used for debugging issues
* *
* @param string $queryString * @param string $queryString
* @param array $queryData * @param int $queryData
* @param float $queryTimings * @param float $queryTimings
* @param array $queryError * @param array $queryError
*/ */
protected function logQuery(string $queryString, array $queryData, float $queryTimings, array $queryError = []) protected function logQuery(string $queryString, int $queryData, float $queryTimings, array $queryError = [])
{ {
$this->queries[] = [ $this->queries[] = [
'queryString' => $queryString, 'queryString' => $queryString,

View File

@ -68,6 +68,13 @@ class PDOEngine extends DatabaseDriver implements iDatabaseEngine
*/ */
protected $pdoConnection; protected $pdoConnection;
/**
* Connection string with the database
*
* @var string
*/
protected $dsn;
/** /**
* Whether a transaction has failed and should be reverted in the future * Whether a transaction has failed and should be reverted in the future
* *
@ -95,6 +102,11 @@ class PDOEngine extends DatabaseDriver implements iDatabaseEngine
return 'pdo'; return 'pdo';
} }
public function getConnectionDescription(): string
{
return is_null($this->dsn) ? 'none' : $this->dsn;
}
/** /**
* Whether the database connection has been set up yet * Whether the database connection has been set up yet
* *
@ -115,17 +127,20 @@ class PDOEngine extends DatabaseDriver implements iDatabaseEngine
public function setUp(array $parameters): bool public function setUp(array $parameters): bool
{ {
// Prepare variables for connection // Prepare variables for connection
$dsn = isset($parameters['dsn']) ? $parameters['dsn'] : null; $this->dsn = isset($parameters['dsn']) ? $parameters['dsn'] : null;
$username = isset($parameters['username']) ? $parameters['username'] : ''; $username = isset($parameters['username']) ? $parameters['username'] : '';
$password = isset($parameters['password']) ? $parameters['password'] : ''; $password = isset($parameters['password']) ? $parameters['password'] : '';
// Don't attempt connection without DSN // Don't attempt connection without DSN
if (is_null($dsn)) if (is_null($this->dsn))
throw new DatabaseException("Could not setUp PDOEngine. No DSN provided"); throw new DatabaseException("Could not setUp PDOEngine. No DSN provided");
// Set some base parameters which are required for FuzeWorks
$parameters[PDO::ATTR_ERRMODE] = PDO::ERRMODE_SILENT;
// Attempt to connect. Throw exception on failure // Attempt to connect. Throw exception on failure
try { try {
$this->pdoConnection = new PDO($dsn, $username, $password, $parameters); $this->pdoConnection = new PDO($this->dsn, $username, $password, $parameters);
} catch (PDOException $e) { } catch (PDOException $e) {
throw new DatabaseException("Could not setUp PDOEngine. PDO threw PDOException: '" . $e->getMessage() . "'"); throw new DatabaseException("Could not setUp PDOEngine. PDO threw PDOException: '" . $e->getMessage() . "'");
} }
@ -192,12 +207,13 @@ class PDOEngine extends DatabaseDriver implements iDatabaseEngine
* *
* @internal * @internal
* @param string $queryString * @param string $queryString
* @param array $queryData * @param int $queryData
* @param float $queryTimings * @param float $queryTimings
*/ */
public function logPDOQuery(string $queryString, array $queryData, float $queryTimings) public function logPDOQuery(string $queryString, int $queryData, float $queryTimings, $errorInfo = [])
{ {
$this->logQuery($queryString, $queryData, $queryTimings, $this->error()); $errorInfo = empty($errorInfo) ? $this->error() : $errorInfo;
$this->logQuery($queryString, $queryData, $queryTimings, $errorInfo);
} }
/** /**
@ -257,9 +273,9 @@ class PDOEngine extends DatabaseDriver implements iDatabaseEngine
*/ */
protected function error(): array protected function error(): array
{ {
$error = ['code' => '00000', 'message' => '']; $error = [];
$pdoError = $this->pdoConnection->errorInfo(); $pdoError = $this->pdoConnection->errorInfo();
if (empty($pdoError[0])) if (empty($pdoError[0]) || $pdoError[0] == '00000')
return $error; return $error;
$error['code'] = isset($pdoError[1]) ? $pdoError[0] . '/' . $pdoError[1] : $pdoError[0]; $error['code'] = isset($pdoError[1]) ? $pdoError[0] . '/' . $pdoError[1] : $pdoError[0];

View File

@ -60,19 +60,20 @@ class PDOStatementWrapper
$this->logQueryCallable = $logQueryCallable; $this->logQueryCallable = $logQueryCallable;
} }
public function execute(array $input_parameters = null) public function execute(array $input_parameters = [])
{ {
// Run the query and benchmark the time // Run the query and benchmark the time
$benchmarkStart = microtime(true); $benchmarkStart = microtime(true);
$result = $this->statement->execute($input_parameters); $result = $this->statement->execute($input_parameters);
$benchmarkEnd = microtime(true) - $benchmarkStart; $benchmarkEnd = microtime(true) - $benchmarkStart;
call_user_func_array($this->logQueryCallable, [$this->statement->queryString, $input_parameters, $benchmarkEnd]); $errInfo = $this->error();
call_user_func_array($this->logQueryCallable, [$this->statement->queryString, $this->statement->rowCount(), $benchmarkEnd, $errInfo]);
// If the query failed, throw an error // If the query failed, throw an error
if ($result === false) if ($result === false)
{ {
// And throw an exception // And throw an exception
throw new DatabaseException("Could not run query. Database returned an error. Error code: " . $this->error()['code']); throw new DatabaseException("Could not run query. Database returned an error. Error code: " . $errInfo['code']);
} }
return $result; return $result;
@ -85,9 +86,9 @@ class PDOStatementWrapper
*/ */
private function error(): array private function error(): array
{ {
$error = ['code' => '00000', 'message' => '']; $error = [];
$pdoError = $this->statement->errorInfo(); $pdoError = $this->statement->errorInfo();
if (empty($pdoError[0])) if (empty($pdoError[0]) || $pdoError[0] == '00000')
return $error; return $error;
$error['code'] = isset($pdoError[1]) ? $pdoError[0] . '/' . $pdoError[1] : $pdoError[0]; $error['code'] = isset($pdoError[1]) ? $pdoError[0] . '/' . $pdoError[1] : $pdoError[0];

View File

@ -40,6 +40,7 @@ namespace FuzeWorks\DatabaseEngine;
interface iDatabaseEngine interface iDatabaseEngine
{ {
public function getName(): string; public function getName(): string;
public function getConnectionDescription(): string;
public function isSetup(): bool; public function isSetup(): bool;
public function setUp(array $parameters): bool; public function setUp(array $parameters): bool;
public function tearDown(): bool; public function tearDown(): bool;

View File

@ -35,8 +35,10 @@
*/ */
namespace FuzeWorks; namespace FuzeWorks;
use FuzeWorks\DatabaseEngine\iDatabaseEngine;
use Tracy\IBarPanel; use Tracy\IBarPanel;
use Tracy\Debugger; use Tracy\Debugger;
use Tracy\Dumper;
/** /**
* DatabaseTracyBridge Class. * DatabaseTracyBridge Class.
@ -47,12 +49,16 @@ use Tracy\Debugger;
* It hooks into database usage and provides the information on the Tracy Bar panel. * It hooks into database usage and provides the information on the Tracy Bar panel.
* *
* @author Abel Hoogeveen <abel@techfuze.net> * @author Abel Hoogeveen <abel@techfuze.net>
* @copyright Copyright (c) 2013 - 2017, TechFuze. (http://techfuze.net) * @copyright Copyright (c) 2013 - 2019, TechFuze. (http://techfuze.net)
*/ */
class DatabaseTracyBridge implements IBarPanel class DatabaseTracyBridge implements IBarPanel
{ {
/**
* @var iDatabaseEngine[]
*/
public static $databases = array(); public static $databases = array();
protected $results = array(); protected $results = array();
public static function register() public static function register()
@ -62,7 +68,7 @@ class DatabaseTracyBridge implements IBarPanel
$bar->addPanel($class); $bar->addPanel($class);
} }
public static function registerDatabase($database) public static function registerDatabase(iDatabaseEngine $database)
{ {
self::$databases[] = $database; self::$databases[] = $database;
} }
@ -86,41 +92,48 @@ class DatabaseTracyBridge implements IBarPanel
// Increase total databases // Increase total databases
$results['dbCount']++; $results['dbCount']++;
// First determine the ID // First determine the ID
if (!empty($database->dsn)) $databaseId = $database->getConnectionDescription();
{
$databaseId = $database->dsn;
}
elseif (!empty($database->username) && !empty($database->database) && !empty($database->hostname))
{
$databaseId = $database->username . '@' . $database->hostname . '/' . $database->database;
}
else
{
$databaseId = spl_object_hash($database);
}
// Go through all queries // Go through all queries
foreach ($database->queries as $key => $query) { if (!method_exists($database, 'getQueries'))
$results['queryCount']++; {
$results['queryTimings'] += $database->query_times[$key]; continue;
$results['queries'][$databaseId][$key]['query'] = $query; }
$results['queries'][$databaseId][$key]['timings'] = $database->query_times[$key];
$results['queries'][$databaseId][$key]['data'] = $database->query_data[$key];
// If errors are found, set this at the top of the array foreach ($database->getQueries() as $query)
if ($database->query_data[$key]['error']['code'] != 0) {
$results['errorsFound'] = true; $results['queryTimings'] += $query['queryTimings'];
} $key = $query['queryString'];
if (!isset($results['queries'][$databaseId][$key]))
$results['queryCount']++;
$results['queries'][$databaseId][$key]['query'] = $query['queryString'];
$results['queries'][$databaseId][$key]['timings'] = $query['queryTimings'];
$results['queries'][$databaseId][$key]['errors'] = $query['queryError'];
if (!isset($results['queries'][$databaseId][$key]['data']))
$results['queries'][$databaseId][$key]['data'] = $query['queryData'];
else
$results['queries'][$databaseId][$key]['data'] += $query['queryData'];
if (!empty($query['queryError']))
$results['errorsFound'] = true;
}
} }
// Limit the amount in order to keep things readable // Limit the amount in order to keep things readable
$results['queryCountProvided'] = 0; $results['queryCountProvided'] = 0;
foreach ($results['queries'] as $id => $database) { if (isset($results['queries']))
$results['queries'][$id] = array_reverse(array_slice($database, -10)); {
$results['queryCountProvided'] += count($results['queries'][$id]); foreach ($results['queries'] as $id => $database) {
} $results['queries'][$id] = array_reverse(array_slice($database, -10));
$results = array_slice($results, -10); $results['queryCountProvided'] += count($results['queries'][$id]);
}
$results = array_slice($results, -10);
}
//dump($results['queries']['mysql:host=localhost;dbname=hello']);
return $this->results = $results; return $this->results = $results;
} }

View File

@ -140,6 +140,14 @@ class PDOTableModel implements iDatabaseTableModel
return true; return true;
} }
/**
* @todo: WRITE ABOUT FETCHMODE
*
* @param array $filter
* @param array $options
* @return array
* @throws DatabaseException
*/
public function read(array $filter = [], array $options = []): array public function read(array $filter = [], array $options = []): array
{ {
// Determine which fields to select. If none provided, select all // Determine which fields to select. If none provided, select all
@ -158,7 +166,8 @@ class PDOTableModel implements iDatabaseTableModel
$this->lastStatement->execute($filter); $this->lastStatement->execute($filter);
// And return the result // And return the result
return $this->lastStatement->fetchAll(PDO::FETCH_ASSOC); $fetchMode = (isset($options['fetchMode']) ? $options['fetchMode'] : PDO::FETCH_ASSOC);
return $this->lastStatement->fetchAll($fetchMode);
} }
public function update(array $data, array $filter, array $options = []): bool public function update(array $data, array $filter, array $options = []): bool

View File

@ -0,0 +1,52 @@
<style class="tracy-debug">
#tracy-debug td.nette-DbConnectionPanel-sql { background: white !important }
#tracy-debug .nette-DbConnectionPanel-source { color: #BBB !important }
#tracy-debug .nette-DbConnectionPanel-explain td { white-space: pre }
#tracy-debug .fuzeworks-DbDescriptor th { background: #FDF5CE !important }
</style>
<h1 title="Database">Queries: <?php
echo $results['queryCount'], ($results['queryTimings'] ? sprintf(', time: %0.3f ms', $results['queryTimings'] * 1000) : ''); ?></h1>
<div class="tracy-inner">
<table>
<?php
if (isset($results['queries'])):
foreach ($results['queries'] as $database => $queries): ?>
<tr class='fuzeworks-DbDescriptor'>
<th>Database:</th>
<th><?= htmlSpecialChars($database, ENT_QUOTES, 'UTF-8') ?></th>
<th>#</th>
</tr>
<tr><th>Time&nbsp;ms</th><th>SQL Query</th><th>Rows</th></tr>
<?php foreach ($queries as $query): ?>
<tr>
<td>
<?php if (!empty($query['errors'])): ?>
<span title="<?= htmlSpecialChars((isset($query['errors']['message']) ? $query['errors']['message'] : ''), ENT_IGNORE | ENT_QUOTES, 'UTF-8') ?>">ERROR</span>
<br /><a class="tracy-toggle tracy-collapsed" data-tracy-ref="^tr .nette-DbConnectionPanel-explain">explain</a>
<?php elseif ($query['timings'] !== 0): echo sprintf('%0.3f', $query['timings'] * 1000); endif ?>
</td>
<td class="nette-DbConnectionPanel-sql"><?= htmlSpecialChars($query['query'], ENT_QUOTES, 'UTF-8') ?>
<?php if (!empty($query['errors'])): ?>
<table class="tracy-collapsed nette-DbConnectionPanel-explain">
<tr>
<th>Code</th>
<th>Message</th>
</tr>
<tr>
<td><?= htmlSpecialChars($query['errors']['code'], ENT_NOQUOTES, 'UTF-8') ?></td>
<td><?= htmlSpecialChars((isset($query['errors']['message']) ? $query['errors']['message'] : ''), ENT_NOQUOTES, 'UTF-8') ?></td>
</tr>
</table>
<?php endif ?>
</td>
<td> <?= htmlSpecialChars(var_export($query['data'], true), ENT_QUOTES, 'UTF-8') ?> </td>
</tr>
<?php endforeach;
endforeach; endif; ?>
</table>
<?php if ($results['queryCountProvided'] < $results['queryCount']): ?><p>...and more</p><?php endif ?>
</div>

View File

@ -0,0 +1,19 @@
<?php
if ($results['queryCount'] && !$results['errorsFound'])
{
$color = "#6ba9e6";
}
elseif ($results['queryCount'] && $results['errorsFound'])
{
$color = "#990000";
}
else
{
$color = "#aaa";
}
?>
<span title="Database">
<svg viewBox="0 0 2048 2048"><path fill="<?= $color ?>" d="M1024 896q237 0 443-43t325-127v170q0 69-103 128t-280 93.5-385 34.5-385-34.5-280-93.5-103-128v-170q119 84 325 127t443 43zm0 768q237 0 443-43t325-127v170q0 69-103 128t-280 93.5-385 34.5-385-34.5-280-93.5-103-128v-170q119 84 325 127t443 43zm0-384q237 0 443-43t325-127v170q0 69-103 128t-280 93.5-385 34.5-385-34.5-280-93.5-103-128v-170q119 84 325 127t443 43zm0-1152q208 0 385 34.5t280 93.5 103 128v128q0 69-103 128t-280 93.5-385 34.5-385-34.5-280-93.5-103-128v-128q0-69 103-128t280-93.5 385-34.5z"/>
</svg><span class="tracy-label"><?= ($results['queryTimings'] ? sprintf('%0.1fms/', $results['queryTimings'] * 1000) : '') . $results['queryCount'] ?></span>
</span>

View File

@ -56,12 +56,5 @@ class DatabaseTest extends DatabaseTestAbstract
$this->database = Factory::getInstance()->database; $this->database = Factory::getInstance()->database;
} }
/**
* @expectedException \FuzeWorks\Exception\DatabaseException
*/
public function testInvalidDb()
{
$this->database->get('unknown://unknown:password@unknown/database');
}
} }