ProfilerController.php 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. <?php
  2. namespace Doctrine\Bundle\DoctrineBundle\Controller;
  3. use Doctrine\DBAL\Connection;
  4. use Doctrine\DBAL\ForwardCompatibility\Result;
  5. use Doctrine\DBAL\Platforms\OraclePlatform;
  6. use Doctrine\DBAL\Platforms\SqlitePlatform;
  7. use Doctrine\DBAL\Platforms\SQLServerPlatform;
  8. use LogicException;
  9. use PDO;
  10. use PDOStatement;
  11. use Symfony\Bridge\Doctrine\DataCollector\DoctrineDataCollector;
  12. use Symfony\Component\DependencyInjection\ContainerAwareInterface;
  13. use Symfony\Component\DependencyInjection\ContainerInterface;
  14. use Symfony\Component\HttpFoundation\Response;
  15. use Symfony\Component\HttpKernel\Profiler\Profiler;
  16. use Symfony\Component\VarDumper\Cloner\Data;
  17. use Throwable;
  18. use function assert;
  19. use function stripos;
  20. class ProfilerController implements ContainerAwareInterface
  21. {
  22. /** @var ContainerInterface */
  23. private $container;
  24. /**
  25. * {@inheritDoc}
  26. */
  27. public function setContainer(?ContainerInterface $container = null)
  28. {
  29. $this->container = $container;
  30. }
  31. /**
  32. * Renders the profiler panel for the given token.
  33. *
  34. * @param string $token The profiler token
  35. * @param string $connectionName
  36. * @param int $query
  37. *
  38. * @return Response A Response instance
  39. */
  40. public function explainAction($token, $connectionName, $query)
  41. {
  42. $profiler = $this->container->get('profiler');
  43. assert($profiler instanceof Profiler);
  44. $profiler->disable();
  45. $profile = $profiler->loadProfile($token);
  46. $collector = $profile->getCollector('db');
  47. assert($collector instanceof DoctrineDataCollector);
  48. $queries = $collector->getQueries();
  49. if (! isset($queries[$connectionName][$query])) {
  50. return new Response('This query does not exist.');
  51. }
  52. $query = $queries[$connectionName][$query];
  53. if (! $query['explainable']) {
  54. return new Response('This query cannot be explained.');
  55. }
  56. $connection = $this->container->get('doctrine')->getConnection($connectionName);
  57. assert($connection instanceof Connection);
  58. try {
  59. $platform = $connection->getDatabasePlatform();
  60. if ($platform instanceof SqlitePlatform) {
  61. $results = $this->explainSQLitePlatform($connection, $query);
  62. } elseif ($platform instanceof SQLServerPlatform) {
  63. $results = $this->explainSQLServerPlatform($connection, $query);
  64. } elseif ($platform instanceof OraclePlatform) {
  65. $results = $this->explainOraclePlatform($connection, $query);
  66. } else {
  67. $results = $this->explainOtherPlatform($connection, $query);
  68. }
  69. } catch (Throwable $e) {
  70. return new Response('This query cannot be explained.');
  71. }
  72. return new Response($this->container->get('twig')->render('@Doctrine/Collector/explain.html.twig', [
  73. 'data' => $results,
  74. 'query' => $query,
  75. ]));
  76. }
  77. /**
  78. * @param mixed[] $query
  79. *
  80. * @return mixed[]
  81. */
  82. private function explainSQLitePlatform(Connection $connection, array $query): array
  83. {
  84. $params = $query['params'];
  85. if ($params instanceof Data) {
  86. $params = $params->getValue(true);
  87. }
  88. return $connection->executeQuery('EXPLAIN QUERY PLAN ' . $query['sql'], $params, $query['types'])
  89. ->fetchAll(PDO::FETCH_ASSOC);
  90. }
  91. /**
  92. * @param mixed[] $query
  93. *
  94. * @return mixed[]
  95. */
  96. private function explainSQLServerPlatform(Connection $connection, array $query): array
  97. {
  98. if (stripos($query['sql'], 'SELECT') === 0) {
  99. $sql = 'SET STATISTICS PROFILE ON; ' . $query['sql'] . '; SET STATISTICS PROFILE OFF;';
  100. } else {
  101. $sql = 'SET SHOWPLAN_TEXT ON; GO; SET NOEXEC ON; ' . $query['sql'] . '; SET NOEXEC OFF; GO; SET SHOWPLAN_TEXT OFF;';
  102. }
  103. $params = $query['params'];
  104. if ($params instanceof Data) {
  105. $params = $params->getValue(true);
  106. }
  107. $stmt = $connection->executeQuery($sql, $params, $query['types']);
  108. // DBAL 2.13 "forward compatibility" BC break handling
  109. if ($stmt instanceof Result) {
  110. $stmt = $stmt->getIterator();
  111. }
  112. if (! $stmt instanceof PDOStatement) {
  113. throw new LogicException('We need nextRowSet() functionality feature, which is not available with current DBAL driver');
  114. }
  115. $stmt->nextRowset();
  116. return $stmt->fetchAll(PDO::FETCH_ASSOC);
  117. }
  118. /**
  119. * @param mixed[] $query
  120. *
  121. * @return mixed[]
  122. */
  123. private function explainOtherPlatform(Connection $connection, array $query): array
  124. {
  125. $params = $query['params'];
  126. if ($params instanceof Data) {
  127. $params = $params->getValue(true);
  128. }
  129. return $connection->executeQuery('EXPLAIN ' . $query['sql'], $params, $query['types'])
  130. ->fetchAll(PDO::FETCH_ASSOC);
  131. }
  132. /**
  133. * @param mixed[] $query
  134. *
  135. * @return mixed[]
  136. */
  137. private function explainOraclePlatform(Connection $connection, array $query): array
  138. {
  139. $connection->executeQuery('EXPLAIN PLAN FOR ' . $query['sql']);
  140. return $connection->executeQuery('SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY())')
  141. ->fetchAll(PDO::FETCH_ASSOC);
  142. }
  143. }