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()
|
public function boot()
|
||||||
{
|
{
|
||||||
$this->configureConnections();
|
$this->configureConnections();
|
||||||
|
|
||||||
|
if ($this->container->getParameter('propel.logging')) {
|
||||||
|
$this->configureLogging();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -64,4 +68,18 @@ class PropelBundle extends Bundle
|
||||||
$serviceContainer->setConnectionManager($name, $manager);
|
$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>
|
<parameters>
|
||||||
<parameter key="propel.dbal.default_connection">default</parameter>
|
<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.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>
|
</parameters>
|
||||||
|
|
||||||
<services>
|
<services>
|
||||||
<service id="propel.schema_locator" class="%propel.schema_locator.class%">
|
<service id="propel.schema_locator" class="%propel.schema_locator.class%">
|
||||||
<argument type="service" id="file_locator" />
|
<argument type="service" id="file_locator" />
|
||||||
</service>
|
</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>
|
</services>
|
||||||
</container>
|
</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