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