DoctrineDataCollector.php 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Bridge\Doctrine\DataCollector;
  11. use Doctrine\DBAL\Logging\DebugStack;
  12. use Doctrine\DBAL\Types\ConversionException;
  13. use Doctrine\DBAL\Types\Type;
  14. use Doctrine\Persistence\ManagerRegistry;
  15. use Symfony\Component\HttpFoundation\Request;
  16. use Symfony\Component\HttpFoundation\Response;
  17. use Symfony\Component\HttpKernel\DataCollector\DataCollector;
  18. use Symfony\Component\VarDumper\Caster\Caster;
  19. use Symfony\Component\VarDumper\Cloner\Stub;
  20. /**
  21. * DoctrineDataCollector.
  22. *
  23. * @author Fabien Potencier <fabien@symfony.com>
  24. */
  25. class DoctrineDataCollector extends DataCollector
  26. {
  27. private $registry;
  28. private $connections;
  29. private $managers;
  30. /**
  31. * @var DebugStack[]
  32. */
  33. private $loggers = [];
  34. public function __construct(ManagerRegistry $registry)
  35. {
  36. $this->registry = $registry;
  37. $this->connections = $registry->getConnectionNames();
  38. $this->managers = $registry->getManagerNames();
  39. }
  40. /**
  41. * Adds the stack logger for a connection.
  42. */
  43. public function addLogger(string $name, DebugStack $logger)
  44. {
  45. $this->loggers[$name] = $logger;
  46. }
  47. /**
  48. * {@inheritdoc}
  49. */
  50. public function collect(Request $request, Response $response, \Throwable $exception = null)
  51. {
  52. $queries = [];
  53. foreach ($this->loggers as $name => $logger) {
  54. $queries[$name] = $this->sanitizeQueries($name, $logger->queries);
  55. }
  56. $this->data = [
  57. 'queries' => $queries,
  58. 'connections' => $this->connections,
  59. 'managers' => $this->managers,
  60. ];
  61. }
  62. public function reset()
  63. {
  64. $this->data = [];
  65. foreach ($this->loggers as $logger) {
  66. $logger->queries = [];
  67. $logger->currentQuery = 0;
  68. }
  69. }
  70. public function getManagers()
  71. {
  72. return $this->data['managers'];
  73. }
  74. public function getConnections()
  75. {
  76. return $this->data['connections'];
  77. }
  78. public function getQueryCount()
  79. {
  80. return array_sum(array_map('count', $this->data['queries']));
  81. }
  82. public function getQueries()
  83. {
  84. return $this->data['queries'];
  85. }
  86. public function getTime()
  87. {
  88. $time = 0;
  89. foreach ($this->data['queries'] as $queries) {
  90. foreach ($queries as $query) {
  91. $time += $query['executionMS'];
  92. }
  93. }
  94. return $time;
  95. }
  96. /**
  97. * {@inheritdoc}
  98. */
  99. public function getName()
  100. {
  101. return 'db';
  102. }
  103. /**
  104. * {@inheritdoc}
  105. */
  106. protected function getCasters()
  107. {
  108. return parent::getCasters() + [
  109. ObjectParameter::class => static function (ObjectParameter $o, array $a, Stub $s): array {
  110. $s->class = $o->getClass();
  111. $s->value = $o->getObject();
  112. $r = new \ReflectionClass($o->getClass());
  113. if ($f = $r->getFileName()) {
  114. $s->attr['file'] = $f;
  115. $s->attr['line'] = $r->getStartLine();
  116. } else {
  117. unset($s->attr['file']);
  118. unset($s->attr['line']);
  119. }
  120. if ($error = $o->getError()) {
  121. return [Caster::PREFIX_VIRTUAL.'⚠' => $error->getMessage()];
  122. }
  123. if ($o->isStringable()) {
  124. return [Caster::PREFIX_VIRTUAL.'__toString()' => (string) $o->getObject()];
  125. }
  126. return [Caster::PREFIX_VIRTUAL.'⚠' => sprintf('Object of class "%s" could not be converted to string.', $o->getClass())];
  127. },
  128. ];
  129. }
  130. private function sanitizeQueries(string $connectionName, array $queries): array
  131. {
  132. foreach ($queries as $i => $query) {
  133. $queries[$i] = $this->sanitizeQuery($connectionName, $query);
  134. }
  135. return $queries;
  136. }
  137. private function sanitizeQuery(string $connectionName, array $query): array
  138. {
  139. $query['explainable'] = true;
  140. $query['runnable'] = true;
  141. if (null === $query['params']) {
  142. $query['params'] = [];
  143. }
  144. if (!\is_array($query['params'])) {
  145. $query['params'] = [$query['params']];
  146. }
  147. if (!\is_array($query['types'])) {
  148. $query['types'] = [];
  149. }
  150. foreach ($query['params'] as $j => $param) {
  151. $e = null;
  152. if (isset($query['types'][$j])) {
  153. // Transform the param according to the type
  154. $type = $query['types'][$j];
  155. if (\is_string($type)) {
  156. $type = Type::getType($type);
  157. }
  158. if ($type instanceof Type) {
  159. $query['types'][$j] = $type->getBindingType();
  160. try {
  161. $param = $type->convertToDatabaseValue($param, $this->registry->getConnection($connectionName)->getDatabasePlatform());
  162. } catch (\TypeError $e) {
  163. } catch (ConversionException $e) {
  164. }
  165. }
  166. }
  167. [$query['params'][$j], $explainable, $runnable] = $this->sanitizeParam($param, $e);
  168. if (!$explainable) {
  169. $query['explainable'] = false;
  170. }
  171. if (!$runnable) {
  172. $query['runnable'] = false;
  173. }
  174. }
  175. $query['params'] = $this->cloneVar($query['params']);
  176. return $query;
  177. }
  178. /**
  179. * Sanitizes a param.
  180. *
  181. * The return value is an array with the sanitized value and a boolean
  182. * indicating if the original value was kept (allowing to use the sanitized
  183. * value to explain the query).
  184. */
  185. private function sanitizeParam($var, ?\Throwable $error): array
  186. {
  187. if (\is_object($var)) {
  188. return [$o = new ObjectParameter($var, $error), false, $o->isStringable() && !$error];
  189. }
  190. if ($error) {
  191. return ['⚠ '.$error->getMessage(), false, false];
  192. }
  193. if (\is_array($var)) {
  194. $a = [];
  195. $explainable = $runnable = true;
  196. foreach ($var as $k => $v) {
  197. [$value, $e, $r] = $this->sanitizeParam($v, null);
  198. $explainable = $explainable && $e;
  199. $runnable = $runnable && $r;
  200. $a[$k] = $value;
  201. }
  202. return [$a, $explainable, $runnable];
  203. }
  204. if (\is_resource($var)) {
  205. return [sprintf('/* Resource(%s) */', get_resource_type($var)), false, false];
  206. }
  207. return [$var, true, true];
  208. }
  209. }