4 * This file is part of the Symfony package.
6 * (c) Fabien Potencier <fabien@symfony.com>
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
12 namespace Symfony\Component\HttpKernel\Profiler;
14 @trigger_error('The '.__NAMESPACE__.'\RedisProfilerStorage class is deprecated since Symfony 2.8 and will be removed in 3.0. Use FileProfilerStorage instead.', E_USER_DEPRECATED);
17 * RedisProfilerStorage stores profiling information in Redis.
19 * @author Andrej Hudec <pulzarraider@gmail.com>
20 * @author Stephane PY <py.stephane1@gmail.com>
22 * @deprecated Deprecated since Symfony 2.8, to be removed in Symfony 3.0.
23 * Use {@link FileProfilerStorage} instead.
25 class RedisProfilerStorage implements ProfilerStorageInterface
27 const TOKEN_PREFIX = 'sf_profiler_';
29 const REDIS_OPT_SERIALIZER = 1;
30 const REDIS_OPT_PREFIX = 2;
31 const REDIS_SERIALIZER_NONE = 0;
32 const REDIS_SERIALIZER_PHP = 1;
45 * @param string $dsn A data source name
46 * @param string $username Not used
47 * @param string $password Not used
48 * @param int $lifetime The lifetime to use for the purge
50 public function __construct($dsn, $username = '', $password = '', $lifetime = 86400)
53 $this->lifetime = (int) $lifetime;
59 public function find($ip, $url, $limit, $method, $start = null, $end = null)
61 $indexName = $this->getIndexName();
63 if (!$indexContent = $this->getValue($indexName, self::REDIS_SERIALIZER_NONE)) {
67 $profileList = array_reverse(explode("\n", $indexContent));
70 foreach ($profileList as $item) {
79 $values = explode("\t", $item, 7);
80 list($itemToken, $itemIp, $itemMethod, $itemUrl, $itemTime, $itemParent) = $values;
81 $statusCode = isset($values[6]) ? $values[6] : null;
83 $itemTime = (int) $itemTime;
85 if ($ip && false === strpos($itemIp, $ip) || $url && false === strpos($itemUrl, $url) || $method && false === strpos($itemMethod, $method)) {
89 if (!empty($start) && $itemTime < $start) {
93 if (!empty($end) && $itemTime > $end) {
98 'token' => $itemToken,
100 'method' => $itemMethod,
103 'parent' => $itemParent,
104 'status_code' => $statusCode,
115 public function purge()
117 // delete only items from index
118 $indexName = $this->getIndexName();
120 $indexContent = $this->getValue($indexName, self::REDIS_SERIALIZER_NONE);
122 if (!$indexContent) {
126 $profileList = explode("\n", $indexContent);
130 foreach ($profileList as $item) {
135 if (false !== $pos = strpos($item, "\t")) {
136 $result[] = $this->getItemName(substr($item, 0, $pos));
140 $result[] = $indexName;
142 return $this->delete($result);
148 public function read($token)
154 $profile = $this->getValue($this->getItemName($token), self::REDIS_SERIALIZER_PHP);
156 if (false !== $profile) {
157 $profile = $this->createProfileFromData($token, $profile);
166 public function write(Profile $profile)
169 'token' => $profile->getToken(),
170 'parent' => $profile->getParentToken(),
171 'children' => array_map(function ($p) { return $p->getToken(); }, $profile->getChildren()),
172 'data' => $profile->getCollectors(),
173 'ip' => $profile->getIp(),
174 'method' => $profile->getMethod(),
175 'url' => $profile->getUrl(),
176 'time' => $profile->getTime(),
179 $profileIndexed = false !== $this->getValue($this->getItemName($profile->getToken()));
181 if ($this->setValue($this->getItemName($profile->getToken()), $data, $this->lifetime, self::REDIS_SERIALIZER_PHP)) {
182 if (!$profileIndexed) {
184 $indexName = $this->getIndexName();
186 $indexRow = implode("\t", array(
187 $profile->getToken(),
189 $profile->getMethod(),
192 $profile->getParentToken(),
193 $profile->getStatusCode(),
196 return $this->appendValue($indexName, $indexRow, $this->lifetime);
206 * Internal convenience method that returns the instance of Redis.
210 * @throws \RuntimeException
212 protected function getRedis()
214 if (null === $this->redis) {
215 $data = parse_url($this->dsn);
217 if (false === $data || !isset($data['scheme']) || $data['scheme'] !== 'redis' || !isset($data['host']) || !isset($data['port'])) {
218 throw new \RuntimeException(sprintf('Please check your configuration. You are trying to use Redis with an invalid dsn "%s". The minimal expected format is "redis://[host]:port".', $this->dsn));
221 if (!extension_loaded('redis')) {
222 throw new \RuntimeException('RedisProfilerStorage requires that the redis extension is loaded.');
225 $redis = new \Redis();
226 $redis->connect($data['host'], $data['port']);
228 if (isset($data['path'])) {
229 $redis->select(substr($data['path'], 1));
232 if (isset($data['pass'])) {
233 $redis->auth($data['pass']);
236 $redis->setOption(self::REDIS_OPT_PREFIX, self::TOKEN_PREFIX);
238 $this->redis = $redis;
245 * Set instance of the Redis.
247 * @param \Redis $redis
249 public function setRedis($redis)
251 $this->redis = $redis;
254 private function createProfileFromData($token, $data, $parent = null)
256 $profile = new Profile($token);
257 $profile->setIp($data['ip']);
258 $profile->setMethod($data['method']);
259 $profile->setUrl($data['url']);
260 $profile->setTime($data['time']);
261 $profile->setCollectors($data['data']);
263 if (!$parent && $data['parent']) {
264 $parent = $this->read($data['parent']);
268 $profile->setParent($parent);
271 foreach ($data['children'] as $token) {
276 if (!$childProfileData = $this->getValue($this->getItemName($token), self::REDIS_SERIALIZER_PHP)) {
280 $profile->addChild($this->createProfileFromData($token, $childProfileData, $profile));
287 * Gets the item name.
289 * @param string $token
293 private function getItemName($token)
297 if ($this->isItemNameValid($name)) {
305 * Gets the name of the index.
309 private function getIndexName()
313 if ($this->isItemNameValid($name)) {
320 private function isItemNameValid($name)
322 $length = strlen($name);
324 if ($length > 2147483648) {
325 throw new \RuntimeException(sprintf('The Redis item key "%s" is too long (%s bytes). Allowed maximum size is 2^31 bytes.', $name, $length));
332 * Retrieves an item from the Redis server.
335 * @param int $serializer
339 private function getValue($key, $serializer = self::REDIS_SERIALIZER_NONE)
341 $redis = $this->getRedis();
342 $redis->setOption(self::REDIS_OPT_SERIALIZER, $serializer);
344 return $redis->get($key);
348 * Stores an item on the Redis server under the specified key.
351 * @param mixed $value
352 * @param int $expiration
353 * @param int $serializer
357 private function setValue($key, $value, $expiration = 0, $serializer = self::REDIS_SERIALIZER_NONE)
359 $redis = $this->getRedis();
360 $redis->setOption(self::REDIS_OPT_SERIALIZER, $serializer);
362 return $redis->setex($key, $expiration, $value);
366 * Appends data to an existing item on the Redis server.
369 * @param string $value
370 * @param int $expiration
374 private function appendValue($key, $value, $expiration = 0)
376 $redis = $this->getRedis();
377 $redis->setOption(self::REDIS_OPT_SERIALIZER, self::REDIS_SERIALIZER_NONE);
379 if ($redis->exists($key)) {
380 $redis->append($key, $value);
382 return $redis->setTimeout($key, $expiration);
385 return $redis->setex($key, $expiration, $value);
389 * Removes the specified keys.
395 private function delete(array $keys)
397 return (bool) $this->getRedis()->delete($keys);