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
*
* @param string $queryString
* @param array $queryData
* @param int $queryData
* @param float $queryTimings
* @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[] = [
'queryString' => $queryString,

View File

@ -68,6 +68,13 @@ class PDOEngine extends DatabaseDriver implements iDatabaseEngine
*/
protected $pdoConnection;
/**
* Connection string with the database
*
* @var string
*/
protected $dsn;
/**
* Whether a transaction has failed and should be reverted in the future
*
@ -95,6 +102,11 @@ class PDOEngine extends DatabaseDriver implements iDatabaseEngine
return 'pdo';
}
public function getConnectionDescription(): string
{
return is_null($this->dsn) ? 'none' : $this->dsn;
}
/**
* 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
{
// 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'] : '';
$password = isset($parameters['password']) ? $parameters['password'] : '';
// 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");
// Set some base parameters which are required for FuzeWorks
$parameters[PDO::ATTR_ERRMODE] = PDO::ERRMODE_SILENT;
// Attempt to connect. Throw exception on failure
try {
$this->pdoConnection = new PDO($dsn, $username, $password, $parameters);
$this->pdoConnection = new PDO($this->dsn, $username, $password, $parameters);
} catch (PDOException $e) {
throw new DatabaseException("Could not setUp PDOEngine. PDO threw PDOException: '" . $e->getMessage() . "'");
}
@ -192,12 +207,13 @@ class PDOEngine extends DatabaseDriver implements iDatabaseEngine
*
* @internal
* @param string $queryString
* @param array $queryData
* @param int $queryData
* @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
{
$error = ['code' => '00000', 'message' => ''];
$error = [];
$pdoError = $this->pdoConnection->errorInfo();
if (empty($pdoError[0]))
if (empty($pdoError[0]) || $pdoError[0] == '00000')
return $error;
$error['code'] = isset($pdoError[1]) ? $pdoError[0] . '/' . $pdoError[1] : $pdoError[0];

View File

@ -60,19 +60,20 @@ class PDOStatementWrapper
$this->logQueryCallable = $logQueryCallable;
}
public function execute(array $input_parameters = null)
public function execute(array $input_parameters = [])
{
// Run the query and benchmark the time
$benchmarkStart = microtime(true);
$result = $this->statement->execute($input_parameters);
$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 ($result === false)
{
// 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;
@ -85,9 +86,9 @@ class PDOStatementWrapper
*/
private function error(): array
{
$error = ['code' => '00000', 'message' => ''];
$error = [];
$pdoError = $this->statement->errorInfo();
if (empty($pdoError[0]))
if (empty($pdoError[0]) || $pdoError[0] == '00000')
return $error;
$error['code'] = isset($pdoError[1]) ? $pdoError[0] . '/' . $pdoError[1] : $pdoError[0];

View File

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

View File

@ -35,8 +35,10 @@
*/
namespace FuzeWorks;
use FuzeWorks\DatabaseEngine\iDatabaseEngine;
use Tracy\IBarPanel;
use Tracy\Debugger;
use Tracy\Dumper;
/**
* DatabaseTracyBridge Class.
@ -47,12 +49,16 @@ use Tracy\Debugger;
* It hooks into database usage and provides the information on the Tracy Bar panel.
*
* @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
{
/**
* @var iDatabaseEngine[]
*/
public static $databases = array();
protected $results = array();
public static function register()
@ -62,7 +68,7 @@ class DatabaseTracyBridge implements IBarPanel
$bar->addPanel($class);
}
public static function registerDatabase($database)
public static function registerDatabase(iDatabaseEngine $database)
{
self::$databases[] = $database;
}
@ -86,41 +92,48 @@ class DatabaseTracyBridge implements IBarPanel
// Increase total databases
$results['dbCount']++;
// First determine the ID
if (!empty($database->dsn))
{
$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);
}
// First determine the ID
$databaseId = $database->getConnectionDescription();
// Go through all queries
foreach ($database->queries as $key => $query) {
$results['queryCount']++;
$results['queryTimings'] += $database->query_times[$key];
$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 (!method_exists($database, 'getQueries'))
{
continue;
}
// If errors are found, set this at the top of the array
if ($database->query_data[$key]['error']['code'] != 0)
$results['errorsFound'] = true;
}
foreach ($database->getQueries() as $query)
{
$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
$results['queryCountProvided'] = 0;
foreach ($results['queries'] as $id => $database) {
$results['queries'][$id] = array_reverse(array_slice($database, -10));
$results['queryCountProvided'] += count($results['queries'][$id]);
}
$results = array_slice($results, -10);
if (isset($results['queries']))
{
foreach ($results['queries'] as $id => $database) {
$results['queries'][$id] = array_reverse(array_slice($database, -10));
$results['queryCountProvided'] += count($results['queries'][$id]);
}
$results = array_slice($results, -10);
}
//dump($results['queries']['mysql:host=localhost;dbname=hello']);
return $this->results = $results;
}

View File

@ -140,6 +140,14 @@ class PDOTableModel implements iDatabaseTableModel
return true;
}
/**
* @todo: WRITE ABOUT FETCHMODE
*
* @param array $filter
* @param array $options
* @return array
* @throws DatabaseException
*/
public function read(array $filter = [], array $options = []): array
{
// Determine which fields to select. If none provided, select all
@ -158,7 +166,8 @@ class PDOTableModel implements iDatabaseTableModel
$this->lastStatement->execute($filter);
// 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

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;
}
/**
* @expectedException \FuzeWorks\Exception\DatabaseException
*/
public function testInvalidDb()
{
$this->database->get('unknown://unknown:password@unknown/database');
}
}