PdoProfilerStorage.php 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  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\Component\HttpKernel\Profiler;
  11. /**
  12. * Base PDO storage for profiling information in a PDO database.
  13. *
  14. * @author Fabien Potencier <fabien@symfony.com>
  15. * @author Jan Schumann <js@schumann-it.com>
  16. */
  17. abstract class PdoProfilerStorage implements ProfilerStorageInterface
  18. {
  19. protected $dsn;
  20. protected $username;
  21. protected $password;
  22. protected $lifetime;
  23. protected $db;
  24. /**
  25. * Constructor.
  26. *
  27. * @param string $dsn A data source name
  28. * @param string $username The username for the database
  29. * @param string $password The password for the database
  30. * @param integer $lifetime The lifetime to use for the purge
  31. */
  32. public function __construct($dsn, $username = '', $password = '', $lifetime = 86400)
  33. {
  34. $this->dsn = $dsn;
  35. $this->username = $username;
  36. $this->password = $password;
  37. $this->lifetime = (int) $lifetime;
  38. }
  39. /**
  40. * {@inheritdoc}
  41. */
  42. public function find($ip, $url, $limit, $method, $start = null, $end = null)
  43. {
  44. if (null === $start) {
  45. $start = 0;
  46. }
  47. if (null === $end) {
  48. $end = time();
  49. }
  50. list($criteria, $args) = $this->buildCriteria($ip, $url, $start, $end, $limit, $method);
  51. $criteria = $criteria ? 'WHERE '.implode(' AND ', $criteria) : '';
  52. $db = $this->initDb();
  53. $tokens = $this->fetch($db, 'SELECT token, ip, method, url, time, parent FROM sf_profiler_data '.$criteria.' ORDER BY time DESC LIMIT '.((integer) $limit), $args);
  54. $this->close($db);
  55. return $tokens;
  56. }
  57. /**
  58. * {@inheritdoc}
  59. */
  60. public function read($token)
  61. {
  62. $db = $this->initDb();
  63. $args = array(':token' => $token);
  64. $data = $this->fetch($db, 'SELECT data, parent, ip, method, url, time FROM sf_profiler_data WHERE token = :token LIMIT 1', $args);
  65. $this->close($db);
  66. if (isset($data[0]['data'])) {
  67. return $this->createProfileFromData($token, $data[0]);
  68. }
  69. return null;
  70. }
  71. /**
  72. * {@inheritdoc}
  73. */
  74. public function write(Profile $profile)
  75. {
  76. $db = $this->initDb();
  77. $args = array(
  78. ':token' => $profile->getToken(),
  79. ':parent' => $profile->getParentToken(),
  80. ':data' => base64_encode(serialize($profile->getCollectors())),
  81. ':ip' => $profile->getIp(),
  82. ':method' => $profile->getMethod(),
  83. ':url' => $profile->getUrl(),
  84. ':time' => $profile->getTime(),
  85. ':created_at' => time(),
  86. );
  87. try {
  88. if ($this->has($profile->getToken())) {
  89. $this->exec($db, 'UPDATE sf_profiler_data SET parent = :parent, data = :data, ip = :ip, method = :method, url = :url, time = :time, created_at = :created_at WHERE token = :token', $args);
  90. } else {
  91. $this->exec($db, 'INSERT INTO sf_profiler_data (token, parent, data, ip, method, url, time, created_at) VALUES (:token, :parent, :data, :ip, :method, :url, :time, :created_at)', $args);
  92. }
  93. $this->cleanup();
  94. $status = true;
  95. } catch (\Exception $e) {
  96. $status = false;
  97. }
  98. $this->close($db);
  99. return $status;
  100. }
  101. /**
  102. * {@inheritdoc}
  103. */
  104. public function purge()
  105. {
  106. $db = $this->initDb();
  107. $this->exec($db, 'DELETE FROM sf_profiler_data');
  108. $this->close($db);
  109. }
  110. /**
  111. * Build SQL criteria to fetch records by ip and url
  112. *
  113. * @param string $ip The IP
  114. * @param string $url The URL
  115. * @param string $start The start period to search from
  116. * @param string $end The end period to search to
  117. * @param string $limit The maximum number of tokens to return
  118. * @param string $method The request method
  119. *
  120. * @return array An array with (criteria, args)
  121. */
  122. abstract protected function buildCriteria($ip, $url, $start, $end, $limit, $method);
  123. /**
  124. * Initializes the database
  125. *
  126. * @throws \RuntimeException When the requested database driver is not installed
  127. */
  128. abstract protected function initDb();
  129. protected function cleanup()
  130. {
  131. $db = $this->initDb();
  132. $this->exec($db, 'DELETE FROM sf_profiler_data WHERE created_at < :time', array(':time' => time() - $this->lifetime));
  133. $this->close($db);
  134. }
  135. protected function exec($db, $query, array $args = array())
  136. {
  137. $stmt = $this->prepareStatement($db, $query);
  138. foreach ($args as $arg => $val) {
  139. $stmt->bindValue($arg, $val, is_int($val) ? \PDO::PARAM_INT : \PDO::PARAM_STR);
  140. }
  141. $success = $stmt->execute();
  142. if (!$success) {
  143. throw new \RuntimeException(sprintf('Error executing query "%s"', $query));
  144. }
  145. }
  146. protected function prepareStatement($db, $query)
  147. {
  148. try {
  149. $stmt = $db->prepare($query);
  150. } catch (\Exception $e) {
  151. $stmt = false;
  152. }
  153. if (false === $stmt) {
  154. throw new \RuntimeException('The database cannot successfully prepare the statement');
  155. }
  156. return $stmt;
  157. }
  158. protected function fetch($db, $query, array $args = array())
  159. {
  160. $stmt = $this->prepareStatement($db, $query);
  161. foreach ($args as $arg => $val) {
  162. $stmt->bindValue($arg, $val, is_int($val) ? \PDO::PARAM_INT : \PDO::PARAM_STR);
  163. }
  164. $stmt->execute();
  165. $return = $stmt->fetchAll(\PDO::FETCH_ASSOC);
  166. return $return;
  167. }
  168. protected function close($db)
  169. {
  170. }
  171. protected function createProfileFromData($token, $data, $parent = null)
  172. {
  173. $profile = new Profile($token);
  174. $profile->setIp($data['ip']);
  175. $profile->setMethod($data['method']);
  176. $profile->setUrl($data['url']);
  177. $profile->setTime($data['time']);
  178. $profile->setCollectors(unserialize(base64_decode($data['data'])));
  179. if (!$parent && !empty($data['parent'])) {
  180. $parent = $this->read($data['parent']);
  181. }
  182. if ($parent) {
  183. $profile->setParent($parent);
  184. }
  185. $profile->setChildren($this->readChildren($token, $profile));
  186. return $profile;
  187. }
  188. /**
  189. * Reads the child profiles for the given token.
  190. *
  191. * @param string $token The parent token
  192. * @param string $parent The parent instance
  193. *
  194. * @return Profile[] An array of Profile instance
  195. */
  196. protected function readChildren($token, $parent)
  197. {
  198. $db = $this->initDb();
  199. $data = $this->fetch($db, 'SELECT token, data, ip, method, url, time FROM sf_profiler_data WHERE parent = :token', array(':token' => $token));
  200. $this->close($db);
  201. if (!$data) {
  202. return array();
  203. }
  204. $profiles = array();
  205. foreach ($data as $d) {
  206. $profiles[] = $this->createProfileFromData($d['token'], $d, $parent);
  207. }
  208. return $profiles;
  209. }
  210. /**
  211. * Returns whether data for the given token already exists in storage.
  212. *
  213. * @param string $token The profile token
  214. *
  215. * @return string
  216. */
  217. protected function has($token)
  218. {
  219. $db = $this->initDb();
  220. $tokenExists = $this->fetch($db, 'SELECT 1 FROM sf_profiler_data WHERE token = :token LIMIT 1', array(':token' => $token));
  221. $this->close($db);
  222. return !empty($tokenExists);
  223. }
  224. }