DoctrineExtension.php 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. <?php
  2. namespace Doctrine\Bundle\DoctrineBundle\Twig;
  3. use Doctrine\SqlFormatter\HtmlHighlighter;
  4. use Doctrine\SqlFormatter\NullHighlighter;
  5. use Doctrine\SqlFormatter\SqlFormatter;
  6. use Symfony\Component\VarDumper\Cloner\Data;
  7. use Twig\Extension\AbstractExtension;
  8. use Twig\TwigFilter;
  9. use function addslashes;
  10. use function array_key_exists;
  11. use function bin2hex;
  12. use function implode;
  13. use function is_array;
  14. use function is_bool;
  15. use function is_object;
  16. use function is_string;
  17. use function method_exists;
  18. use function preg_match;
  19. use function preg_replace_callback;
  20. use function sprintf;
  21. use function strtoupper;
  22. use function substr;
  23. use function trigger_error;
  24. use const E_USER_DEPRECATED;
  25. /**
  26. * This class contains the needed functions in order to do the query highlighting
  27. */
  28. class DoctrineExtension extends AbstractExtension
  29. {
  30. /** @var SqlFormatter */
  31. private $sqlFormatter;
  32. /**
  33. * Define our functions
  34. *
  35. * @return TwigFilter[]
  36. */
  37. public function getFilters()
  38. {
  39. return [
  40. new TwigFilter('doctrine_pretty_query', [$this, 'formatQuery'], ['is_safe' => ['html'], 'deprecated' => true]),
  41. new TwigFilter('doctrine_prettify_sql', [$this, 'prettifySql'], ['is_safe' => ['html']]),
  42. new TwigFilter('doctrine_format_sql', [$this, 'formatSql'], ['is_safe' => ['html']]),
  43. new TwigFilter('doctrine_replace_query_parameters', [$this, 'replaceQueryParameters']),
  44. ];
  45. }
  46. /**
  47. * Escape parameters of a SQL query
  48. * DON'T USE THIS FUNCTION OUTSIDE ITS INTENDED SCOPE
  49. *
  50. * @internal
  51. *
  52. * @param mixed $parameter
  53. *
  54. * @return string
  55. */
  56. public static function escapeFunction($parameter)
  57. {
  58. $result = $parameter;
  59. switch (true) {
  60. // Check if result is non-unicode string using PCRE_UTF8 modifier
  61. case is_string($result) && ! preg_match('//u', $result):
  62. $result = '0x' . strtoupper(bin2hex($result));
  63. break;
  64. case is_string($result):
  65. $result = "'" . addslashes($result) . "'";
  66. break;
  67. case is_array($result):
  68. foreach ($result as &$value) {
  69. $value = static::escapeFunction($value);
  70. }
  71. $result = implode(', ', $result) ?: 'NULL';
  72. break;
  73. case is_object($result) && method_exists($result, '__toString'):
  74. $result = addslashes($result->__toString());
  75. break;
  76. case $result === null:
  77. $result = 'NULL';
  78. break;
  79. case is_bool($result):
  80. $result = $result ? '1' : '0';
  81. break;
  82. }
  83. return $result;
  84. }
  85. /**
  86. * Return a query with the parameters replaced
  87. *
  88. * @param string $query
  89. * @param mixed[]|Data $parameters
  90. *
  91. * @return string
  92. */
  93. public function replaceQueryParameters($query, $parameters)
  94. {
  95. if ($parameters instanceof Data) {
  96. $parameters = $parameters->getValue(true);
  97. }
  98. $i = 0;
  99. if (! array_key_exists(0, $parameters) && array_key_exists(1, $parameters)) {
  100. $i = 1;
  101. }
  102. return preg_replace_callback(
  103. '/\?|((?<!:):[a-z0-9_]+)/i',
  104. static function ($matches) use ($parameters, &$i) {
  105. $key = substr($matches[0], 1);
  106. if (! array_key_exists($i, $parameters) && ($key === false || ! array_key_exists($key, $parameters))) {
  107. return $matches[0];
  108. }
  109. $value = array_key_exists($i, $parameters) ? $parameters[$i] : $parameters[$key];
  110. $result = DoctrineExtension::escapeFunction($value);
  111. $i++;
  112. return $result;
  113. },
  114. $query
  115. );
  116. }
  117. /**
  118. * Formats and/or highlights the given SQL statement.
  119. *
  120. * @param string $sql
  121. * @param bool $highlightOnly If true the query is not formatted, just highlighted
  122. *
  123. * @return string
  124. */
  125. public function formatQuery($sql, $highlightOnly = false)
  126. {
  127. @trigger_error(sprintf('The "%s()" method is deprecated and will be removed in DoctrineBundle 3.0.', __METHOD__), E_USER_DEPRECATED);
  128. $this->setUpSqlFormatter(true, true);
  129. if ($highlightOnly) {
  130. return $this->sqlFormatter->highlight($sql);
  131. }
  132. return sprintf(
  133. '<div class="highlight highlight-sql"><pre>%s</pre></div>',
  134. $this->sqlFormatter->format($sql)
  135. );
  136. }
  137. public function prettifySql(string $sql): string
  138. {
  139. $this->setUpSqlFormatter();
  140. return $this->sqlFormatter->highlight($sql);
  141. }
  142. public function formatSql(string $sql, bool $highlight): string
  143. {
  144. $this->setUpSqlFormatter($highlight);
  145. return $this->sqlFormatter->format($sql);
  146. }
  147. private function setUpSqlFormatter(bool $highlight = true, bool $legacy = false): void
  148. {
  149. $this->sqlFormatter = new SqlFormatter($highlight ? new HtmlHighlighter([
  150. HtmlHighlighter::HIGHLIGHT_PRE => 'class="highlight highlight-sql"',
  151. HtmlHighlighter::HIGHLIGHT_QUOTE => 'class="string"',
  152. HtmlHighlighter::HIGHLIGHT_BACKTICK_QUOTE => 'class="string"',
  153. HtmlHighlighter::HIGHLIGHT_RESERVED => 'class="keyword"',
  154. HtmlHighlighter::HIGHLIGHT_BOUNDARY => 'class="symbol"',
  155. HtmlHighlighter::HIGHLIGHT_NUMBER => 'class="number"',
  156. HtmlHighlighter::HIGHLIGHT_WORD => 'class="word"',
  157. HtmlHighlighter::HIGHLIGHT_ERROR => 'class="error"',
  158. HtmlHighlighter::HIGHLIGHT_COMMENT => 'class="comment"',
  159. HtmlHighlighter::HIGHLIGHT_VARIABLE => 'class="variable"',
  160. ], ! $legacy) : new NullHighlighter());
  161. }
  162. /**
  163. * Get the name of the extension
  164. *
  165. * @return string
  166. */
  167. public function getName()
  168. {
  169. return 'doctrine_extension';
  170. }
  171. }