LoggedQuery.php 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
  5. * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  6. *
  7. * Licensed under The MIT License
  8. * For full copyright and license information, please see the LICENSE.txt
  9. * Redistributions of files must retain the above copyright notice.
  10. *
  11. * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  12. * @link https://cakephp.org CakePHP(tm) Project
  13. * @since 3.0.0
  14. * @license https://opensource.org/licenses/mit-license.php MIT License
  15. */
  16. namespace Cake\Database\Log;
  17. use Cake\Database\Driver\Sqlserver;
  18. use Cake\Database\DriverInterface;
  19. use Exception;
  20. use JsonSerializable;
  21. use Stringable;
  22. /**
  23. * Contains a query string, the params used to executed it, time taken to do it
  24. * and the number of rows found or affected by its execution.
  25. *
  26. * @internal
  27. */
  28. class LoggedQuery implements JsonSerializable, Stringable
  29. {
  30. /**
  31. * Driver executing the query
  32. *
  33. * @var \Cake\Database\DriverInterface|null
  34. */
  35. protected ?DriverInterface $driver = null;
  36. /**
  37. * Query string that was executed
  38. *
  39. * @var string
  40. */
  41. protected string $query = '';
  42. /**
  43. * Number of milliseconds this query took to complete
  44. *
  45. * @var float
  46. */
  47. protected float $took = 0;
  48. /**
  49. * Associative array with the params bound to the query string
  50. *
  51. * @var array
  52. */
  53. protected array $params = [];
  54. /**
  55. * Number of rows affected or returned by the query execution
  56. *
  57. * @var int
  58. */
  59. protected int $numRows = 0;
  60. /**
  61. * The exception that was thrown by the execution of this query
  62. *
  63. * @var \Exception|null
  64. */
  65. protected ?Exception $error = null;
  66. /**
  67. * Helper function used to replace query placeholders by the real
  68. * params used to execute the query
  69. *
  70. * @return string
  71. */
  72. protected function interpolate(): string
  73. {
  74. $params = array_map(function ($p) {
  75. if ($p === null) {
  76. return 'NULL';
  77. }
  78. if (is_bool($p)) {
  79. if ($this->driver instanceof Sqlserver) {
  80. return $p ? '1' : '0';
  81. }
  82. return $p ? 'TRUE' : 'FALSE';
  83. }
  84. if (is_string($p)) {
  85. // Likely binary data like a blob or binary uuid.
  86. // pattern matches ascii control chars.
  87. if (preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/u', '', $p) !== $p) {
  88. $p = bin2hex($p);
  89. }
  90. $replacements = [
  91. '$' => '\\$',
  92. '\\' => '\\\\\\\\',
  93. "'" => "''",
  94. ];
  95. $p = strtr($p, $replacements);
  96. return "'$p'";
  97. }
  98. return $p;
  99. }, $this->params);
  100. $keys = [];
  101. $limit = is_int(key($params)) ? 1 : -1;
  102. foreach ($params as $key => $param) {
  103. $keys[] = is_string($key) ? "/:$key\b/" : '/[?]/';
  104. }
  105. return preg_replace($keys, $params, $this->query, $limit);
  106. }
  107. /**
  108. * Get the logging context data for a query.
  109. *
  110. * @return array<string, mixed>
  111. */
  112. public function getContext(): array
  113. {
  114. return [
  115. 'numRows' => $this->numRows,
  116. 'took' => $this->took,
  117. ];
  118. }
  119. /**
  120. * Set logging context for this query.
  121. *
  122. * @param array $context Context data.
  123. * @return void
  124. */
  125. public function setContext(array $context): void
  126. {
  127. foreach ($context as $key => $val) {
  128. $this->{$key} = $val;
  129. }
  130. }
  131. /**
  132. * Returns data that will be serialized as JSON
  133. *
  134. * @return array<string, mixed>
  135. */
  136. public function jsonSerialize(): array
  137. {
  138. $error = $this->error;
  139. if ($error !== null) {
  140. $error = [
  141. 'class' => get_class($error),
  142. 'message' => $error->getMessage(),
  143. 'code' => $error->getCode(),
  144. ];
  145. }
  146. return [
  147. 'query' => $this->query,
  148. 'numRows' => $this->numRows,
  149. 'params' => $this->params,
  150. 'took' => $this->took,
  151. 'error' => $error,
  152. ];
  153. }
  154. /**
  155. * Returns the string representation of this logged query
  156. *
  157. * @return string
  158. */
  159. public function __toString(): string
  160. {
  161. $sql = $this->query;
  162. if (!empty($this->params)) {
  163. $sql = $this->interpolate();
  164. }
  165. return $sql;
  166. }
  167. }