[DoctrineBridge] Refactored the query sanitization in the collector

The original parameters are kept whenever possible to allow using them
again to explain the query.
This commit is contained in:
Christophe Coevoet 2012-01-23 10:57:46 +01:00
parent 3b260d268b
commit e37783f4f9
2 changed files with 73 additions and 34 deletions

View File

@ -13,6 +13,7 @@ namespace Symfony\Bridge\Doctrine\DataCollector;
use Doctrine\Common\Persistence\ManagerRegistry; use Doctrine\Common\Persistence\ManagerRegistry;
use Doctrine\DBAL\Logging\DebugStack; use Doctrine\DBAL\Logging\DebugStack;
use Doctrine\DBAL\Types\Type;
use Symfony\Component\HttpKernel\DataCollector\DataCollector; use Symfony\Component\HttpKernel\DataCollector\DataCollector;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
@ -54,7 +55,7 @@ class DoctrineDataCollector extends DataCollector
{ {
$queries = array(); $queries = array();
foreach ($this->loggers as $name => $logger) { foreach ($this->loggers as $name => $logger) {
$queries[$name] = $this->sanitizeQueries($logger->queries); $queries[$name] = $this->sanitizeQueries($name, $logger->queries);
} }
$this->data = array( $this->data = array(
@ -104,48 +105,73 @@ class DoctrineDataCollector extends DataCollector
return 'db'; return 'db';
} }
private function sanitizeQueries($queries) private function sanitizeQueries($connectionName, $queries)
{ {
foreach ($queries as $i => $query) { foreach ($queries as $i => $query) {
foreach ((array) $query['params'] as $j => $param) { $queries[$i] = $this->sanitizeQuery($connectionName, $query);
$queries[$i]['params'][$j] = $this->varToString($param);
}
} }
return $queries; return $queries;
} }
private function varToString($var) private function sanitizeQuery($connectionName, $query)
{
$query['explainable'] = true;
$query['params'] = (array) $query['params'];
foreach ($query['params'] as $j => &$param) {
if (isset($query['types'][$j])) {
// Transform the param according to the type
$type = $query['types'][$j];
if (is_string($type)) {
$type = Type::getType($type);
}
if ($type instanceof Type) {
$query['types'][$j] = $type->getBindingType();
$param = $type->convertToDatabaseValue($param, $this->registry->getConnection($connectionName)->getDatabasePlatform());
}
}
list($param, $explainable) = $this->sanitizeParam($param);
if (!$explainable) {
$query['explainable'] = false;
}
}
return $query;
}
/**
* Sanitizes a param.
*
* The return value is an array with the sanitized value and a boolean
* indicating if the original value was kept (allowing to use the sanitized
* value to explain the query).
*
* @param mixed $var
* @return array
*/
private function sanitizeParam($var)
{ {
if (is_object($var)) { if (is_object($var)) {
return sprintf('Object(%s)', get_class($var)); return array(sprintf('Object(%s)', get_class($var)), false);
} }
if (is_array($var)) { if (is_array($var)) {
$a = array(); $a = array();
$original = true;
foreach ($var as $k => $v) { foreach ($var as $k => $v) {
$a[] = sprintf('%s => %s', $k, $this->varToString($v)); list($value, $orig) = $this->sanitizeParam($v);
$original = $original && $orig;
$a[$k] = $value;
} }
return sprintf("Array(%s)", implode(', ', $a)); return array($a, $original);
} }
if (is_resource($var)) { if (is_resource($var)) {
return sprintf('Resource(%s)', get_resource_type($var)); return array(sprintf('Resource(%s)', get_resource_type($var)), false);
} }
if (null === $var) { return array($var, true);
return 'null';
}
if (false === $var) {
return 'false';
}
if (true === $var) {
return 'true';
}
return (string) $var;
} }
} }

View File

@ -11,6 +11,7 @@
namespace Symfony\Tests\Bridge\Doctrine\DataCollector; namespace Symfony\Tests\Bridge\Doctrine\DataCollector;
use Doctrine\DBAL\Platforms\MySqlPlatform;
use Symfony\Bridge\Doctrine\DataCollector\DoctrineDataCollector; use Symfony\Bridge\Doctrine\DataCollector\DoctrineDataCollector;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
@ -70,25 +71,26 @@ class DoctrineDataCollectorTest extends \PHPUnit_Framework_TestCase
/** /**
* @dataProvider paramProvider * @dataProvider paramProvider
*/ */
public function testCollectQueries($param, $expected) public function testCollectQueries($param, $types, $expected, $explainable)
{ {
$queries = array( $queries = array(
array('sql' => "SELECT * FROM table1 WHERE field1 = ?1", 'params' => array($param), 'types' => array(), 'executionMS' => 1) array('sql' => "SELECT * FROM table1 WHERE field1 = ?1", 'params' => array($param), 'types' => $types, 'executionMS' => 1)
); );
$c = $this->createCollector($queries); $c = $this->createCollector($queries);
$c->collect(new Request(), new Response()); $c->collect(new Request(), new Response());
$collected_queries = $c->getQueries(); $collected_queries = $c->getQueries();
$this->assertEquals($expected, $collected_queries['default'][0]['params'][0]); $this->assertEquals($expected, $collected_queries['default'][0]['params'][0]);
$this->assertEquals($explainable, $collected_queries['default'][0]['explainable']);
} }
/** /**
* @dataProvider paramProvider * @dataProvider paramProvider
*/ */
public function testSerialization($param, $expected) public function testSerialization($param, $types, $expected, $explainable)
{ {
$queries = array( $queries = array(
array('sql' => "SELECT * FROM table1 WHERE field1 = ?1", 'params' => array($param), 'types' => array(), 'executionMS' => 1) array('sql' => "SELECT * FROM table1 WHERE field1 = ?1", 'params' => array($param), 'types' => $types, 'executionMS' => 1)
); );
$c = $this->createCollector($queries); $c = $this->createCollector($queries);
$c->collect(new Request(), new Response()); $c->collect(new Request(), new Response());
@ -96,23 +98,31 @@ class DoctrineDataCollectorTest extends \PHPUnit_Framework_TestCase
$collected_queries = $c->getQueries(); $collected_queries = $c->getQueries();
$this->assertEquals($expected, $collected_queries['default'][0]['params'][0]); $this->assertEquals($expected, $collected_queries['default'][0]['params'][0]);
$this->assertEquals($explainable, $collected_queries['default'][0]['explainable']);
} }
public function paramProvider() public function paramProvider()
{ {
return array( return array(
array('some value', 'some value'), array('some value', array(), 'some value', true),
array(1, '1'), array(1, array(), 1, true),
array(true, 'true'), array(true, array(), true, true),
array(null, 'null'), array(null, array(), null, true),
array(new \stdClass(), 'Object(stdClass)'), array(new \DateTime('2011-09-11'), array('date'), '2011-09-11', true),
array(fopen(__FILE__, 'r'), 'Resource(stream)'), array(fopen(__FILE__, 'r'), array(), 'Resource(stream)', false),
array(new \SplFileInfo(__FILE__), 'Object(SplFileInfo)'), array(new \SplFileInfo(__FILE__), array(), 'Object(SplFileInfo)', false),
); );
} }
private function createCollector($queries) private function createCollector($queries)
{ {
$connection = $this->getMockBuilder('Doctrine\DBAL\Connection')
->disableOriginalConstructor()
->getMock();
$connection->expects($this->any())
->method('getDatabasePlatform')
->will($this->returnValue(new MySqlPlatform()));
$registry = $this->getMock('Doctrine\Common\Persistence\ManagerRegistry'); $registry = $this->getMock('Doctrine\Common\Persistence\ManagerRegistry');
$registry $registry
->expects($this->any()) ->expects($this->any())
@ -122,6 +132,9 @@ class DoctrineDataCollectorTest extends \PHPUnit_Framework_TestCase
->expects($this->any()) ->expects($this->any())
->method('getManagerNames') ->method('getManagerNames')
->will($this->returnValue(array('default' => 'doctrine.orm.default_entity_manager'))); ->will($this->returnValue(array('default' => 'doctrine.orm.default_entity_manager')));
$registry->expects($this->any())
->method('getConnection')
->will($this->returnValue($connection));
$logger = $this->getMock('Doctrine\DBAL\Logging\DebugStack'); $logger = $this->getMock('Doctrine\DBAL\Logging\DebugStack');
$logger->queries = $queries; $logger->queries = $queries;