DoctrineMongoDBLogger.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  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\Bundle\DoctrineMongoDBBundle\Logger;
  11. use Doctrine\MongoDB\GridFSFile;
  12. use Symfony\Component\HttpKernel\Log\LoggerInterface;
  13. /**
  14. * Logger for the Doctrine MongoDB ODM.
  15. *
  16. * The {@link logQuery()} method is configured as the logger callable in the
  17. * service container.
  18. *
  19. * @author Kris Wallsmith <kris.wallsmith@symfony.com>
  20. */
  21. class DoctrineMongoDBLogger
  22. {
  23. protected $logger;
  24. protected $prefix;
  25. protected $queries;
  26. protected $processed;
  27. protected $formattedQueries;
  28. protected $nbRealQueries;
  29. /**
  30. * Constructor.
  31. *
  32. * @param LoggerInterface $logger The Symfony logger
  33. * @param string $prefix A prefix for messages sent to the Symfony logger
  34. */
  35. public function __construct(LoggerInterface $logger = null, $prefix = 'MongoDB query: ')
  36. {
  37. $this->logger = $logger;
  38. $this->prefix = $prefix;
  39. $this->queries = array();
  40. $this->processed = false;
  41. }
  42. /**
  43. * Logs a query.
  44. *
  45. * This method is configured as the logger callable in the service
  46. * container.
  47. *
  48. * @param array $query A query log array from Doctrine
  49. */
  50. public function logQuery(array $query)
  51. {
  52. $this->queries[] = $query;
  53. $this->processed = false;
  54. if (null !== $this->logger) {
  55. $this->logger->info($this->prefix.static::bsonEncode($query));
  56. }
  57. }
  58. /**
  59. * Returns the number of queries that have been logged.
  60. *
  61. * @return integer The number of queries logged
  62. */
  63. public function getNbQueries()
  64. {
  65. if (!$this->processed) {
  66. $this->processQueries();
  67. }
  68. return $this->nbRealQueries;
  69. }
  70. /**
  71. * Returns a human-readable array of queries logged.
  72. *
  73. * @return array An array of queries
  74. */
  75. public function getQueries()
  76. {
  77. if (!$this->processed) {
  78. $this->processQueries();
  79. }
  80. return $this->formattedQueries;
  81. }
  82. /**
  83. * Groups and formats query arrays.
  84. *
  85. * @param array $queries An array of query arrays
  86. *
  87. * @return array An array of human-readable queries
  88. */
  89. protected function processQueries()
  90. {
  91. $this->formattedQueries = array();
  92. $this->nbRealQueries = 0;
  93. $grouped = array();
  94. $ordered = array();
  95. foreach ($this->queries as $query) {
  96. if (!isset($query['query']) || !isset($query['fields'])) {
  97. // no grouping necessary
  98. $ordered[] = array($query);
  99. continue;
  100. }
  101. $cursor = serialize($query['query']).serialize($query['fields']);
  102. // append if issued from cursor (currently just "sort")
  103. if (isset($query['sort'])) {
  104. unset($query['query'], $query['fields']);
  105. $grouped[$cursor][count($grouped[$cursor]) - 1][] = $query;
  106. } else {
  107. $grouped[$cursor][] = array($query);
  108. $ordered[] =& $grouped[$cursor][count($grouped[$cursor]) - 1];
  109. }
  110. }
  111. $i = 0;
  112. $db = '';
  113. $query = '';
  114. foreach ($ordered as $logs) {
  115. foreach ($logs as $log) {
  116. if (isset($log['db']) && $db != $log['db']) {
  117. // for readability
  118. $this->formattedQueries[$i++] = 'use '.$log['db'].';';
  119. $db = $log['db'];
  120. }
  121. if (isset($log['collection'])) {
  122. // flush the previous and start a new query
  123. if (!empty($query)) {
  124. if ('.' == $query[0]) {
  125. $query = 'db'.$query;
  126. }
  127. $this->formattedQueries[$i++] = $query.';';
  128. ++$this->nbRealQueries;
  129. }
  130. $query = 'db.'.$log['collection'];
  131. }
  132. // format the method call
  133. if (isset($log['authenticate'])) {
  134. $query .= '.authenticate()';
  135. } elseif (isset($log['batchInsert'])) {
  136. $query .= '.batchInsert(**'.$log['num'].' item(s)**)';
  137. } elseif (isset($log['command'])) {
  138. $query .= '.command()';
  139. } elseif (isset($log['count'])) {
  140. $query .= '.count(';
  141. if ($log['query'] || $log['limit'] || $log['skip']) {
  142. $query .= static::bsonEncode($log['query']);
  143. if ($log['limit'] || $log['skip']) {
  144. $query .= ', '.static::bsonEncode($log['limit']);
  145. if ($log['skip']) {
  146. $query .= ', '.static::bsonEncode($log['skip']);
  147. }
  148. }
  149. }
  150. $query .= ')';
  151. } elseif (isset($log['createCollection'])) {
  152. $query .= '.createCollection()';
  153. } elseif (isset($log['createDBRef'])) {
  154. $query .= '.createDBRef()';
  155. } elseif (isset($log['deleteIndex'])) {
  156. $query .= '.dropIndex('.static::bsonEncode($log['keys']).')';
  157. } elseif (isset($log['deleteIndexes'])) {
  158. $query .= '.dropIndexes()';
  159. } elseif (isset($log['drop'])) {
  160. $query .= '.drop()';
  161. } elseif (isset($log['dropDatabase'])) {
  162. $query .= '.dropDatabase()';
  163. } elseif (isset($log['ensureIndex'])) {
  164. $query .= '.ensureIndex('.static::bsonEncode($log['keys']).', '.static::bsonEncode($log['options']).')';
  165. } elseif (isset($log['execute'])) {
  166. $query .= '.execute()';
  167. } elseif (isset($log['find'])) {
  168. $query .= '.find(';
  169. if ($log['query'] || $log['fields']) {
  170. $query .= static::bsonEncode($log['query']);
  171. if ($log['fields']) {
  172. $query .= ', '.static::bsonEncode($log['fields']);
  173. }
  174. }
  175. $query .= ')';
  176. } elseif (isset($log['findOne'])) {
  177. $query .= '.findOne(';
  178. if ($log['query'] || $log['fields']) {
  179. $query .= static::bsonEncode($log['query']);
  180. if ($log['fields']) {
  181. $query .= ', '.static::bsonEncode($log['fields']);
  182. }
  183. }
  184. $query .= ')';
  185. } elseif (isset($log['getDBRef'])) {
  186. $query .= '.getDBRef()';
  187. } elseif (isset($log['group'])) {
  188. $query .= '.group('.static::bsonEncode(array(
  189. 'keys' => $log['keys'],
  190. 'initial' => $log['initial'],
  191. 'reduce' => $log['reduce'],
  192. )).')';
  193. } elseif (isset($log['insert'])) {
  194. $query .= '.insert('.static::bsonEncode($log['document']).')';
  195. } elseif (isset($log['remove'])) {
  196. $query .= '.remove('.static::bsonEncode($log['query']).')';
  197. } elseif (isset($log['save'])) {
  198. $query .= '.save('.static::bsonEncode($log['document']).')';
  199. } elseif (isset($log['sort'])) {
  200. $query .= '.sort('.static::bsonEncode($log['sortFields']).')';
  201. } elseif (isset($log['update'])) {
  202. // todo: include $log['options']
  203. $query .= '.update('.static::bsonEncode($log['query']).', '.static::bsonEncode($log['newObj']).')';
  204. } elseif (isset($log['validate'])) {
  205. $query .= '.validate()';
  206. }
  207. }
  208. }
  209. if (!empty($query)) {
  210. if ('.' == $query[0]) {
  211. $query = 'db'.$query;
  212. }
  213. $this->formattedQueries[$i++] = $query.';';
  214. ++$this->nbRealQueries;
  215. }
  216. }
  217. static protected function bsonEncode($query, $array = true)
  218. {
  219. $parts = array();
  220. foreach ($query as $key => $value) {
  221. if (!is_numeric($key)) {
  222. $array = false;
  223. }
  224. if (null === $value) {
  225. $formatted = 'null';
  226. } elseif (is_bool($value)) {
  227. $formatted = $value ? 'true' : 'false';
  228. } elseif (is_numeric($value)) {
  229. $formatted = $value;
  230. } elseif (is_scalar($value)) {
  231. $formatted = '"'.$value.'"';
  232. } elseif (is_array($value)) {
  233. $formatted = static::bsonEncode($value);
  234. } elseif ($value instanceof \MongoId) {
  235. $formatted = 'ObjectId("'.$value.'")';
  236. } elseif ($value instanceof \MongoDate) {
  237. $formatted = 'new Date("'.date('r', $value->sec).'")';
  238. } elseif ($value instanceof \DateTime) {
  239. $formatted = 'new Date("'.date('r', $value->getTimestamp()).'")';
  240. } elseif ($value instanceof \MongoRegex) {
  241. $formatted = 'new RegExp("'.$value->regex.'", "'.$value->flags.'")';
  242. } elseif ($value instanceof \MongoMinKey) {
  243. $formatted = 'new MinKey()';
  244. } elseif ($value instanceof \MongoMaxKey) {
  245. $formatted = 'new MaxKey()';
  246. } elseif ($value instanceof \MongoBinData) {
  247. $formatted = 'new BinData("'.$value->bin.'", "'.$value->type.'")';
  248. } elseif ($value instanceof \MongoGridFSFile || $value instanceof GridFSFile) {
  249. $formatted = 'new MongoGridFSFile("'.$value->getFilename().'")';
  250. } elseif ($value instanceof \stdClass) {
  251. $formatted = static::bsonEncode((array) $value);
  252. } else {
  253. $formatted = (string) $value;
  254. }
  255. $parts['"'.$key.'"'] = $formatted;
  256. }
  257. if (0 == count($parts)) {
  258. return $array ? '[ ]' : '{ }';
  259. }
  260. if ($array) {
  261. return '[ '.implode(', ', $parts).' ]';
  262. } else {
  263. $mapper = function($key, $value)
  264. {
  265. return $key.': '.$value;
  266. };
  267. return '{ '.implode(', ', array_map($mapper, array_keys($parts), array_values($parts))).' }';
  268. }
  269. }
  270. }