Skip to content
This repository was archived by the owner on Sep 14, 2018. It is now read-only.

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
denderello committed Oct 8, 2012
0 parents commit 16ffaf2
Show file tree
Hide file tree
Showing 9 changed files with 360 additions and 0 deletions.
73 changes: 73 additions & 0 deletions Analyzer/Query.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php

namespace SensioLabs\DoctrineQueryStatisticsBundle\Analyzer;

class Query
{
private $queries;

public function addQuery($sql, $parameters = array())
{
$this->queries[] = array(
'sql' => $sql,
'parameters' => $parameters
);
}

public function getQueryCount()
{
return count($this->queries);
}

public function getIdenticalQueries()
{
$groupedQueries = array();
foreach($this->queries as $query)
{
$queryKey = $this->generateQueryKeyWithParameters($query['sql'], $query['parameters']);
$groupedQueries[$queryKey][] = $query;
}

return $this->filterIndistinctQueries($groupedQueries);
}

public function getSimilarQueries()
{
$groupedQueries = array();
foreach($this->queries as $query)
{
$queryKey = $this->generateQueryKeyWithoutParameters($query['sql']);
$groupedQueries[$queryKey][] = $query;
}

return $this->filterIndistinctQueries($groupedQueries);
}

private function generateQueryKeyWithParameters($sql, array $parameters)
{
return $this->generateQueryKeyWithoutParameters($sql) . ':' . sha1(serialize($parameters));
}

private function generateQueryKeyWithoutParameters($sql)
{
return sha1($sql);
}

private function filterIndistinctQueries(array $allQueries)
{
$indistinctQueries = array();
foreach($allQueries as $queryKey => $queries)
{
if (count($queries) > 1)
{
$indistinctQueries[$queryKey] = array(
'sql' => $queries[0]['sql'],
'count' => count($queries),
'parameters' => $queries[0]['parameters']
);
}
}

return $indistinctQueries;
}
}
88 changes: 88 additions & 0 deletions DataCollector/DoctrineQueryStatisticsDataCollector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<?php

namespace SensioLabs\DoctrineQueryStatisticsBundle\DataCollector;

use Symfony\Component\HttpKernel\DataCollector\DataCollector;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Doctrine\DBAL\Logging\DebugStack;
use SensioLabs\DoctrineQueryStatisticsBundle\Analyzer\Query as QueryAnalyzer;

class DoctrineQueryStatisticsDataCollector extends DataCollector
{

private $loggers = array();

private function countGroupedQueries(array $queries)
{
return array_sum(array_map('count', $queries));
}

/**
* Adds the stack logger for a doctrine connection.
*
* @param string $name
* @param DebugStack $logger
*/
public function addLogger($name, DebugStack $logger)
{
$this->loggers[$name] = $logger;
}

/**
* {@inheritdoc}
*/
public function collect(Request $request, Response $response, \Exception $exception = null)
{
foreach ($this->loggers as $name => $logger) {
$this->data['queries'][$name] = $logger->queries;

$this->data['queryAnalyzers'][$name] = new QueryAnalyzer();

This comment has been minimized.

Copy link
@stof

stof Oct 8, 2012

Why putting the analyzer in the serialized data ? you never use it later so I don't see the reason here.

and btw, if you really want to serialize it, you should make it implement Serializable.

foreach($this->data['queries'][$name] as $query)
{
$this->data['queryAnalyzers'][$name]->addQuery($query['sql'], $query['params']);
}

$this->data['identical'][$name] = $this->data['queryAnalyzers'][$name]->getIdenticalQueries();
$this->data['similar'][$name] = $this->data['queryAnalyzers'][$name]->getSimilarQueries();
}
}

/**
* {@inheritdoc}
*/
public function getName()
{
return 'doctrinequerystatistics';
}

public function getQueries()
{
return $this->data['queries'];
}

public function getQueriesCount()
{
return $this->countGroupedQueries($this->data['queries']);
}

public function getIdenticalQueries()
{
return $this->data['identical'];
}

public function getIdenticalQueriesCount()
{
return $this->countGroupedQueries($this->data['identical']);
}

public function getSimilarQueries()
{
return $this->data['similar'];
}

public function getSimilarQueriesCount()
{
return $this->countGroupedQueries($this->data['similar']);
}
}
29 changes: 29 additions & 0 deletions DependencyInjection/CompilerPass/DataCollectorCompilerPass.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

namespace SensioLabs\DoctrineQueryStatisticsBundle\DependencyInjection\CompilerPass;

use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;

class DataCollectorCompilerPass implements CompilerPassInterface
{


/**
* {@inheritdoc}
*/
function process(ContainerBuilder $container)

This comment has been minimized.

Copy link
@stof

stof Oct 8, 2012

missing visibility :)
You should run the PHP-CS-Fixer

{
$dataCollectorDefinition = $container->getDefinition('sensiolabs.doctrinequerystatistics.data_collector');
$connectionNames = $container->get('doctrine')->getConnectionNames();

foreach ($connectionNames as $name => $serviceId)
{
$dataCollectorDefinition->addMethodCall('addLogger', array(
$name,
new Reference('doctrine.dbal.logger.profiling.' . $name)

This comment has been minimized.

Copy link
@stof

stof Oct 8, 2012

take care that profiling can be disabled on a connection bases. you need to check that this service exists before adding the reference.

));
}
}
}
29 changes: 29 additions & 0 deletions DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

namespace SensioLabs\DoctrineQueryStatisticsBundle\DependencyInjection;

use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;

/**
* This is the class that validates and merges configuration from your app/config files
*
* To learn more see {@link http://symfony.com/doc/current/cookbook/bundles/extension.html#cookbook-bundles-extension-config-class}
*/
class Configuration implements ConfigurationInterface
{
/**
* {@inheritDoc}
*/
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root('sensio_labs_doctrine_query_statistics');

// Here you should define the parameters that are allowed to
// configure your bundle. See the documentation linked above for
// more information on that topic.

return $treeBuilder;
}
}
28 changes: 28 additions & 0 deletions DependencyInjection/SensioLabsDoctrineQueryStatisticsExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

namespace SensioLabs\DoctrineQueryStatisticsBundle\DependencyInjection;

use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\Loader;

/**
* This is the class that loads and manages your bundle configuration
*
* To learn more see {@link http://symfony.com/doc/current/cookbook/bundles/extension.html}
*/
class SensioLabsDoctrineQueryStatisticsExtension extends Extension
{
/**
* {@inheritDoc}
*/
public function load(array $configs, ContainerBuilder $container)
{
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);

$loader = new Loader\XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.xml');
}
}
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
SensioLabsDoctrineQueryStatisticsBundle
=======================================

This bundle adds a tab to your Profiler which gathers statistical information about the Doctrine queries that have
been executed during a request.

Right now the bundle generates statistical information about:

- Duplicate queries
- Similar queries (same queries with different parameters)
17 changes: 17 additions & 0 deletions Resources/config/services.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.0" ?>

<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">

<parameters>
<parameter key="sensiolabs.doctrinequerystatistics.data_collector.class">SensioLabs\DoctrineQueryStatisticsBundle\DataCollector\DoctrineQueryStatisticsDataCollector</parameter>
</parameters>

<services>

<service id="sensiolabs.doctrinequerystatistics.data_collector" class="%sensiolabs.doctrinequerystatistics.data_collector.class%">
<tag name="data_collector" template="SensioLabsDoctrineQueryStatisticsBundle:Collector:doctrinequerystatistics" id="doctrinequerystatistics" priority="255" />
</service>
</services>
</container>
67 changes: 67 additions & 0 deletions Resources/views/Collector/doctrinequerystatistics.html.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
{% extends 'WebProfilerBundle:Profiler:layout.html.twig' %}

{% block menu %}
<span class="label">
<span class="icon"><img src="{{ asset('bundles/webprofiler/images/profiler/db.png') }}" alt="" /></span>
<strong>Doctrine Statistics</strong>
</span>
{% endblock %}

{% block panel %}
<h2>All Queries ({{ collector.queriesCount }})</h2>
{% for connection, queries in collector.queries %}
<h3>Connection <em>{{ connection }}</em></h3>
<table>
<tr>
<th>SQL</th>
<th>Parameters</th>
</tr>

{% for query in queries %}
<tr>
<td>{{ query.sql }}</td>
<td>{{ query.params|yaml_encode }}</td>
</tr>
{% endfor %}
</table>
{% endfor%}

This comment has been minimized.

Copy link
@stof

stof Oct 8, 2012

This table duplicates the list available in the Doctrine panel, but with less features. Is it really needed ?


<h2>Identical Queries ({{ collector.identicalQueriesCount }})</h2>
{% for connection, identicalQueries in collector.identicalQueries %}
<h3>Connection <em>{{ connection }}</em></h3>
<table>
<tr>
<th>Times called</th>
<th>SQL</th>
<th>Parameters</th>
</tr>

{% for identicalQuery in identicalQueries %}
<tr>
<td>{{ identicalQuery.count }}</td>
<td>{{ identicalQuery.sql }}</td>
<td>{{ identicalQuery.parameters|yaml_encode }}</td>
</tr>
{% endfor %}
</table>
{% endfor%}

<h2>Similar Queries ({{ collector.similarQueriesCount }})</h2>
{% for connection, similarQueries in collector.similarQueries %}
<h3>Connection <em>{{ connection }}</em></h3>
<table>
<tr>
<th>Times called</th>
<th>SQL</th>
</tr>

{% for similarQuery in similarQueries %}
<tr>
<td>{{ similarQuery.count }}</td>
<td>{{ similarQuery.sql }}</td>
</tr>
{% endfor %}
</table>
{% endfor%}

{% endblock %}
19 changes: 19 additions & 0 deletions SensioLabsDoctrineQueryStatisticsBundle.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

namespace SensioLabs\DoctrineQueryStatisticsBundle;

use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
use SensioLabs\DoctrineQueryStatisticsBundle\DependencyInjection\CompilerPass\DataCollectorCompilerPass;

class SensioLabsDoctrineQueryStatisticsBundle extends Bundle
{
/**
* {@inheritDoc}
*/
public function build(ContainerBuilder $container)
{
$container->addCompilerPass(new DataCollectorCompilerPass(), PassConfig::TYPE_BEFORE_REMOVING);
}
}

0 comments on commit 16ffaf2

Please sign in to comment.