Fixed the DataCollector
This commit is contained in:
parent
44729e47f5
commit
3889566728
82
Controller/PanelController.php
Normal file
82
Controller/PanelController.php
Normal file
|
@ -0,0 +1,82 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the PropelBundle package.
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*
|
||||
* @license MIT License
|
||||
*/
|
||||
|
||||
namespace Propel\PropelBundle\Controller;
|
||||
|
||||
use Propel\Runtime\Propel;
|
||||
use Symfony\Component\DependencyInjection\ContainerAware;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* PanelController is designed to display information in the Propel Panel.
|
||||
*
|
||||
* @author William DURAND <william.durand1@gmail.com>
|
||||
*/
|
||||
class PanelController extends ContainerAware
|
||||
{
|
||||
/**
|
||||
* This method renders the global Propel configuration.
|
||||
*/
|
||||
public function configurationAction()
|
||||
{
|
||||
$templating = $this->container->get('templating');
|
||||
|
||||
return $templating->renderResponse(
|
||||
'PropelBundle:Panel:configuration.html.twig',
|
||||
array(
|
||||
'propel_version' => Propel::VERSION,
|
||||
'configuration' => $this->container->getParameter('propel.configuration'),
|
||||
'default_connection' => $this->container->getParameter('propel.dbal.default_connection'),
|
||||
'logging' => $this->container->getParameter('propel.logging'),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the profiler panel for the given token.
|
||||
*
|
||||
* @param string $token The profiler token
|
||||
* @param string $connection The connection name
|
||||
* @param integer $query
|
||||
*
|
||||
* @return Symfony\Component\HttpFoundation\Response A Response instance
|
||||
*/
|
||||
public function explainAction($token, $connection, $query)
|
||||
{
|
||||
$profiler = $this->container->get('profiler');
|
||||
$profiler->disable();
|
||||
|
||||
$profile = $profiler->loadProfile($token);
|
||||
$queries = $profile->getCollector('propel')->getQueries();
|
||||
|
||||
if (!isset($queries[$query])) {
|
||||
return new Response('This query does not exist.');
|
||||
}
|
||||
|
||||
// Open the connection
|
||||
$con = Propel::getConnection($connection);
|
||||
|
||||
try {
|
||||
$dataFetcher = $con->query('EXPLAIN ' . $queries[$query]['sql']);
|
||||
$results = array();
|
||||
while (($results[] = $dataFetcher->fetch(\PDO::FETCH_ASSOC)));
|
||||
} catch (\Exception $e) {
|
||||
return new Response('<div class="error">This query cannot be explained.</div>');
|
||||
}
|
||||
|
||||
return $this->container->get('templating')->renderResponse(
|
||||
'PropelBundle:Panel:explain.html.twig',
|
||||
array(
|
||||
'data' => $results,
|
||||
'query' => $query,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
109
DataCollector/PropelDataCollector.php
Normal file
109
DataCollector/PropelDataCollector.php
Normal file
|
@ -0,0 +1,109 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Propel\PropelBundle\DataCollector;
|
||||
|
||||
use Propel\PropelBundle\Logger\PropelLogger;
|
||||
use Propel\Runtime\Util\Profiler;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
|
||||
|
||||
/**
|
||||
* The PropelDataCollector collector class collects information.
|
||||
*
|
||||
* @author Kévin Gomez <contact@kevingomez.fr>
|
||||
*/
|
||||
class PropelDataCollector extends DataCollector
|
||||
{
|
||||
protected $logger;
|
||||
|
||||
public function __construct(PropelLogger $logger)
|
||||
{
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function collect(Request $request, Response $response, \Exception $exception = null)
|
||||
{
|
||||
$this->data = array(
|
||||
'queries' => $this->buildQueries(),
|
||||
'querycount' => $this->countQueries(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the collector name.
|
||||
*
|
||||
* @return string The collector name.
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return 'propel';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns queries.
|
||||
*
|
||||
* @return array Queries
|
||||
*/
|
||||
public function getQueries()
|
||||
{
|
||||
return $this->data['queries'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the query count.
|
||||
*
|
||||
* @return int The query count
|
||||
*/
|
||||
public function getQueryCount()
|
||||
{
|
||||
return $this->data['querycount'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total time of queries.
|
||||
*
|
||||
* @return float The total time of queries
|
||||
*/
|
||||
public function getTime()
|
||||
{
|
||||
$time = 0;
|
||||
foreach ($this->data['queries'] as $query) {
|
||||
$time += (float) $query['time'];
|
||||
}
|
||||
|
||||
return $time;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an array of Build objects.
|
||||
*
|
||||
* @return array An array of Build objects
|
||||
*/
|
||||
private function buildQueries()
|
||||
{
|
||||
return $this->logger->getQueries();
|
||||
}
|
||||
|
||||
/**
|
||||
* Count queries.
|
||||
*
|
||||
* @return int The number of queries.
|
||||
*/
|
||||
private function countQueries()
|
||||
{
|
||||
return count($this->logger->getQueries());
|
||||
}
|
||||
}
|
102
Logger/PropelLogger.php
Normal file
102
Logger/PropelLogger.php
Normal file
|
@ -0,0 +1,102 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the PropelBundle package.
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*
|
||||
* @license MIT License
|
||||
*/
|
||||
|
||||
namespace Propel\PropelBundle\Logger;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\LoggerTrait;
|
||||
use Symfony\Component\Stopwatch\Stopwatch;
|
||||
|
||||
/**
|
||||
* @author Kévin Gomez <contact@kevingomez.fr>
|
||||
*/
|
||||
class PropelLogger implements LoggerInterface
|
||||
{
|
||||
/**
|
||||
* @var LoggerInterface
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $queries = array();
|
||||
|
||||
/**
|
||||
* @var Stopwatch
|
||||
*/
|
||||
protected $stopwatch;
|
||||
|
||||
use LoggerTrait;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param LoggerInterface $logger A LoggerInterface instance
|
||||
* @param Stopwatch $stopwatch A Stopwatch instance
|
||||
*/
|
||||
public function __construct(LoggerInterface $logger = null, Stopwatch $stopwatch = null)
|
||||
{
|
||||
$this->logger = $logger;
|
||||
$this->stopwatch = $stopwatch;
|
||||
$this->isPrepared = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs with an arbitrary level.
|
||||
*
|
||||
* @param mixed $level
|
||||
* @param string $message
|
||||
* @param array $context
|
||||
* @return null
|
||||
*/
|
||||
public function log($level, $message, array $context = array())
|
||||
{
|
||||
if (null === $this->logger) {
|
||||
return;
|
||||
}
|
||||
|
||||
$add = true;
|
||||
|
||||
if (null !== $this->stopwatch) {
|
||||
$trace = debug_backtrace();
|
||||
$method = $trace[3]['function'];
|
||||
|
||||
$watch = 'Propel Query '.(count($this->queries)+1);
|
||||
if ('prepare' === $method) {
|
||||
$this->isPrepared = true;
|
||||
$this->stopwatch->start($watch, 'propel');
|
||||
|
||||
$add = false;
|
||||
} elseif ($this->isPrepared) {
|
||||
$this->isPrepared = false;
|
||||
$event = $this->stopwatch->stop($watch);
|
||||
}
|
||||
}
|
||||
|
||||
if ($add && isset($event)) {
|
||||
$connection = $trace[2]['object'];
|
||||
|
||||
$this->queries[] = array(
|
||||
'sql' => $message,
|
||||
'connection' => $connection->getName(),
|
||||
'time' => $event->getDuration() / 1000,
|
||||
'memory' => $event->getMemory(),
|
||||
);
|
||||
}
|
||||
|
||||
$this->logger->log($level, $message, $context);
|
||||
}
|
||||
|
||||
public function getQueries()
|
||||
{
|
||||
return $this->queries;
|
||||
}
|
||||
}
|
|
@ -30,6 +30,10 @@ class PropelBundle extends Bundle
|
|||
public function boot()
|
||||
{
|
||||
$this->configureConnections();
|
||||
|
||||
if ($this->container->getParameter('propel.logging')) {
|
||||
$this->configureLogging();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -64,4 +68,18 @@ class PropelBundle extends Bundle
|
|||
$serviceContainer->setConnectionManager($name, $manager);
|
||||
}
|
||||
}
|
||||
|
||||
protected function configureLogging()
|
||||
{
|
||||
$serviceContainer = Propel::getServiceContainer();
|
||||
$serviceContainer->setLogger('defaultLogger', $this->container->get('propel.logger'));
|
||||
|
||||
foreach ($serviceContainer->getConnectionManagers() as $manager) {
|
||||
$connection = $manager->getReadConnection($serviceContainer->getAdapter($manager->getName()));
|
||||
$connection->setLogMethods(array_merge($connection->getLogMethods(), array('prepare')));
|
||||
|
||||
$connection = $manager->getWriteConnection();
|
||||
$connection->setLogMethods(array_merge($connection->getLogMethods(), array('prepare')));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,12 +7,30 @@
|
|||
<parameters>
|
||||
<parameter key="propel.dbal.default_connection">default</parameter>
|
||||
|
||||
<parameter key="propel.class">Propel\Runtime\Propel</parameter>
|
||||
<parameter key="propel.schema_locator.class">Propel\PropelBundle\Service\SchemaLocator</parameter>
|
||||
<parameter key="propel.data_collector.class">Propel\PropelBundle\DataCollector\PropelDataCollector</parameter>
|
||||
<parameter key="propel.logger.class">Propel\PropelBundle\Logger\PropelLogger</parameter>
|
||||
<parameter key="propel.twig.extension.syntax.class">Propel\PropelBundle\Twig\Extension\SyntaxExtension</parameter>
|
||||
</parameters>
|
||||
|
||||
<services>
|
||||
<service id="propel.schema_locator" class="%propel.schema_locator.class%">
|
||||
<argument type="service" id="file_locator" />
|
||||
</service>
|
||||
|
||||
<service id="propel.logger" class="%propel.logger.class%">
|
||||
<argument type="service" id="logger" on-invalid="null" />
|
||||
<argument type="service" id="debug.stopwatch" on-invalid="null" />
|
||||
</service>
|
||||
|
||||
<service id="propel.data_collector" class="%propel.data_collector.class%" public="false">
|
||||
<tag name="data_collector" template="PropelBundle:Collector:propel" id="propel" />
|
||||
<argument type="service" id="propel.logger" />
|
||||
</service>
|
||||
|
||||
<service id="propel.twig.extension.syntax" class="%propel.twig.extension.syntax.class%">
|
||||
<tag name="twig.extension" />
|
||||
</service>
|
||||
</services>
|
||||
</container>
|
||||
|
|
111
Resources/views/Collector/propel.html.twig
Normal file
111
Resources/views/Collector/propel.html.twig
Normal file
|
@ -0,0 +1,111 @@
|
|||
{% extends 'WebProfilerBundle:Profiler:layout.html.twig' %}
|
||||
|
||||
{% block toolbar %}
|
||||
{# the web debug toolbar content #}
|
||||
{% set icon %}
|
||||
<img alt="Propel" src="data:image/png;base64,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" />
|
||||
<span class="sf-toolbar-status">{{ collector.querycount }}</span>
|
||||
{% endset %}
|
||||
{% set text %}
|
||||
<div class="sf-toolbar-info-piece">
|
||||
<b>DB Queries</b>
|
||||
<span>{{ collector.querycount }}</span>
|
||||
</div>
|
||||
<div class="sf-toolbar-info-piece">
|
||||
<b>Query time</b>
|
||||
<span>{{ '%0.2f'|format(collector.time * 1000) }} ms</span>
|
||||
</div>
|
||||
{% endset %}
|
||||
{% include 'WebProfilerBundle:Profiler:toolbar_item.html.twig' with { 'link': profiler_url } %}
|
||||
{% endblock %}
|
||||
|
||||
{% block menu %}
|
||||
{# the menu content #}
|
||||
<span class="label">
|
||||
<span class="icon"><img src="{{ asset('bundles/propel/images/profiler/propel.png') }}" alt="" /></span>
|
||||
<strong>Propel</strong>
|
||||
<span class="count">
|
||||
<span>{{ collector.querycount }}</span>
|
||||
<span>{{ '%0.0f'|format(collector.time * 1000) }} ms</span>
|
||||
</span>
|
||||
</span>
|
||||
{% endblock %}
|
||||
|
||||
{% block panel %}
|
||||
{# the panel content #}
|
||||
<style type="text/css">
|
||||
.SQLKeyword {
|
||||
color: blue;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.SQLName {
|
||||
color: #464646;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.SQLInfo, .SQLComment {
|
||||
color: gray;
|
||||
display: block;
|
||||
font-size: 0.9em;
|
||||
margin: 3px 0;
|
||||
}
|
||||
|
||||
.SQLExplain {
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.SQLExplain .error {
|
||||
background-color: #F2DEDE;
|
||||
border-color: #EED3D7;
|
||||
color: #B94A48;
|
||||
padding: 8px 35px 8px 14px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#content .SQLExplain h2 {
|
||||
font-size: 17px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<h2>Queries</h2>
|
||||
<table summary="Show logged queries">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>SQL queries</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% if not collector.querycount %}
|
||||
<tr><td>No queries.</td></tr>
|
||||
{% else %}
|
||||
{% for i, query in collector.queries %}
|
||||
<tr>
|
||||
<td>
|
||||
<a name="propel-query-{{ i }}" ></a>
|
||||
<code>{{ query.sql|format_sql }}</code>
|
||||
{% if app.request.query.has('query') and app.request.query.get('query') == i %}
|
||||
<div class="SQLExplain">
|
||||
{% render controller('PropelBundle:Panel:explain', {
|
||||
'token': token,
|
||||
'panel': 'propel',
|
||||
'query': app.request.query.get('query'),
|
||||
'connection': app.request.query.get('connection')
|
||||
}) %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="SQLInfo">
|
||||
Time: {{ query.time }} - Memory: {{ query.memory|format_memory }} - Connection: {{ query.connection }}
|
||||
|
||||
{% if app.request.query.get('query', -1) != i %}
|
||||
- <a href="{{ path('_profiler', {'panel': 'propel', 'token': token, 'connection': query.connection, 'query': i}) }}#propel-query-{{ i }}">Explain the query</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{% render controller('PropelBundle:Panel:configuration') %}
|
||||
{% endblock %}
|
69
Resources/views/Panel/configuration.html.twig
Normal file
69
Resources/views/Panel/configuration.html.twig
Normal file
|
@ -0,0 +1,69 @@
|
|||
<h2>Propel configuration</h2>
|
||||
|
||||
<table summary="Current Propel configuration">
|
||||
<thead>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Propel version</th>
|
||||
<td>{{ propel_version }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Default connection</th>
|
||||
<td>{{ default_connection }}</td>
|
||||
<tr>
|
||||
<th>Logging</th>
|
||||
<td>{{ logging ? 'enabled' : 'disabled' }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2>Propel connections</h2>
|
||||
|
||||
<table summary="Current Propel connections">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Connection name</th>
|
||||
<th colspan="2" style="text-align: center;">Configuration parameters</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for name, config in configuration %}
|
||||
<tr>
|
||||
<th rowspan="5" style="vertical-align: top;">
|
||||
{{ name }}
|
||||
</th>
|
||||
<th>Adapter</th>
|
||||
<td>{{ config.adapter }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>DSN</th>
|
||||
<td>{{ config.connection.dsn }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Class</th>
|
||||
<td>{{ config.connection.classname }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Options</th>
|
||||
<td>
|
||||
<ul>
|
||||
{% for key, value in config.connection.options %}
|
||||
<li>{{ key }} : {{ value }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Attributes</th>
|
||||
<td>
|
||||
<ul>
|
||||
{% for key, value in config.connection.attributes %}
|
||||
<li>{{ key }} : {{ value }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
16
Resources/views/Panel/explain.html.twig
Normal file
16
Resources/views/Panel/explain.html.twig
Normal file
|
@ -0,0 +1,16 @@
|
|||
<h2>Explanation</h2>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
{% for label in data[0]|keys %}
|
||||
<th>{{ label }}</th>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% for row in data %}
|
||||
<tr>
|
||||
{% for item in row %}
|
||||
<td>{{ item }}</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
139
Twig/Extension/SyntaxExtension.php
Normal file
139
Twig/Extension/SyntaxExtension.php
Normal file
|
@ -0,0 +1,139 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the PropelBundle package.
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*
|
||||
* @license MIT License
|
||||
*/
|
||||
|
||||
namespace Propel\PropelBundle\Twig\Extension;
|
||||
|
||||
/**
|
||||
* SyntaxExtension class
|
||||
*
|
||||
* @package PropelBundle
|
||||
* @subpackage Extension
|
||||
* @author William DURAND <william.durand1@gmail.com>
|
||||
*/
|
||||
class SyntaxExtension extends \Twig_Extension
|
||||
{
|
||||
public function getFilters()
|
||||
{
|
||||
return array(
|
||||
'format_sql' => new \Twig_Filter_Method($this, 'formatSQL', array('is_safe' => array('html'))),
|
||||
'format_memory' => new \Twig_Filter_Method($this, 'formatMemory'),
|
||||
);
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
return 'propel_syntax_extension';
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a byte count into a human-readable representation.
|
||||
*
|
||||
* @param integer $bytes Byte count to convert. Can be negative.
|
||||
* @param integer $precision How many decimals to include.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function formatMemory($bytes, $precision = 3)
|
||||
{
|
||||
$absBytes = abs($bytes);
|
||||
$sign = ($bytes == $absBytes) ? 1 : -1;
|
||||
$suffix = array('B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB');
|
||||
$total = count($suffix);
|
||||
|
||||
for ($i = 0; $absBytes > 1024 && $i < $total; $i++) {
|
||||
$absBytes /= 1024;
|
||||
}
|
||||
|
||||
return self::toPrecision($sign * $absBytes, $precision) . $suffix[$i];
|
||||
}
|
||||
|
||||
public function formatSQL($sql)
|
||||
{
|
||||
// list of keywords to prepend a newline in output
|
||||
$newlines = array(
|
||||
'FROM',
|
||||
'(((FULL|LEFT|RIGHT)? ?(OUTER|INNER)?|CROSS|NATURAL)? JOIN)',
|
||||
'VALUES',
|
||||
'WHERE',
|
||||
'ORDER BY',
|
||||
'GROUP BY',
|
||||
'HAVING',
|
||||
'LIMIT',
|
||||
);
|
||||
|
||||
// list of keywords to highlight
|
||||
$keywords = array_merge($newlines, array(
|
||||
// base
|
||||
'SELECT', 'UPDATE', 'DELETE', 'INSERT', 'REPLACE',
|
||||
'SET',
|
||||
'INTO',
|
||||
'AS',
|
||||
'DISTINCT',
|
||||
|
||||
// most used methods
|
||||
'COUNT',
|
||||
'AVG',
|
||||
'MIN',
|
||||
'MAX',
|
||||
|
||||
// joins
|
||||
'ON', 'USING',
|
||||
|
||||
// where clause
|
||||
'(IS (NOT)?)?NULL',
|
||||
'(NOT )?IN',
|
||||
'(NOT )?I?LIKE',
|
||||
'AND', 'OR', 'XOR',
|
||||
'BETWEEN',
|
||||
|
||||
// order, group, limit ..
|
||||
'ASC',
|
||||
'DESC',
|
||||
'OFFSET',
|
||||
));
|
||||
|
||||
$sql = preg_replace(array(
|
||||
'/\b('.implode('|', $newlines).')\b/',
|
||||
'/\b('.implode('|', $keywords).')\b/',
|
||||
'/(\/\*.*\*\/)/',
|
||||
'/(`[^`.]*`)/',
|
||||
'/(([0-9a-zA-Z$_]+)\.([0-9a-zA-Z$_]+))/',
|
||||
), array(
|
||||
'<br />\\1',
|
||||
'<span class="SQLKeyword">\\1</span>',
|
||||
'<span class="SQLComment">\\1</span>',
|
||||
'<span class="SQLName">\\1</span>',
|
||||
'<span class="SQLName">\\1</span>',
|
||||
), $sql);
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rounding to significant digits (sort of like JavaScript's toPrecision()).
|
||||
*
|
||||
* @param float $number Value to round
|
||||
* @param integer $significantFigures Number of significant figures
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public static function toPrecision($number, $significantFigures = 3)
|
||||
{
|
||||
if (0 === $number) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$significantDecimals = floor($significantFigures - log10(abs($number)));
|
||||
$magnitude = pow(10, $significantDecimals);
|
||||
$shifted = round($number * $magnitude);
|
||||
|
||||
return number_format($shifted / $magnitude, $significantDecimals);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue