Yaffs site version 1.1
[yaffs-website] / vendor / symfony / http-kernel / Profiler / RedisProfilerStorage.php
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 @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);
15
16 /**
17  * RedisProfilerStorage stores profiling information in Redis.
18  *
19  * @author Andrej Hudec <pulzarraider@gmail.com>
20  * @author Stephane PY <py.stephane1@gmail.com>
21  *
22  * @deprecated Deprecated since Symfony 2.8, to be removed in Symfony 3.0.
23  *             Use {@link FileProfilerStorage} instead.
24  */
25 class RedisProfilerStorage implements ProfilerStorageInterface
26 {
27     const TOKEN_PREFIX = 'sf_profiler_';
28
29     const REDIS_OPT_SERIALIZER = 1;
30     const REDIS_OPT_PREFIX = 2;
31     const REDIS_SERIALIZER_NONE = 0;
32     const REDIS_SERIALIZER_PHP = 1;
33
34     protected $dsn;
35     protected $lifetime;
36
37     /**
38      * @var \Redis
39      */
40     private $redis;
41
42     /**
43      * Constructor.
44      *
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
49      */
50     public function __construct($dsn, $username = '', $password = '', $lifetime = 86400)
51     {
52         $this->dsn = $dsn;
53         $this->lifetime = (int) $lifetime;
54     }
55
56     /**
57      * {@inheritdoc}
58      */
59     public function find($ip, $url, $limit, $method, $start = null, $end = null)
60     {
61         $indexName = $this->getIndexName();
62
63         if (!$indexContent = $this->getValue($indexName, self::REDIS_SERIALIZER_NONE)) {
64             return array();
65         }
66
67         $profileList = array_reverse(explode("\n", $indexContent));
68         $result = array();
69
70         foreach ($profileList as $item) {
71             if ($limit === 0) {
72                 break;
73             }
74
75             if ($item == '') {
76                 continue;
77             }
78
79             $values = explode("\t", $item, 7);
80             list($itemToken, $itemIp, $itemMethod, $itemUrl, $itemTime, $itemParent) = $values;
81             $statusCode = isset($values[6]) ? $values[6] : null;
82
83             $itemTime = (int) $itemTime;
84
85             if ($ip && false === strpos($itemIp, $ip) || $url && false === strpos($itemUrl, $url) || $method && false === strpos($itemMethod, $method)) {
86                 continue;
87             }
88
89             if (!empty($start) && $itemTime < $start) {
90                 continue;
91             }
92
93             if (!empty($end) && $itemTime > $end) {
94                 continue;
95             }
96
97             $result[] = array(
98                 'token' => $itemToken,
99                 'ip' => $itemIp,
100                 'method' => $itemMethod,
101                 'url' => $itemUrl,
102                 'time' => $itemTime,
103                 'parent' => $itemParent,
104                 'status_code' => $statusCode,
105             );
106             --$limit;
107         }
108
109         return $result;
110     }
111
112     /**
113      * {@inheritdoc}
114      */
115     public function purge()
116     {
117         // delete only items from index
118         $indexName = $this->getIndexName();
119
120         $indexContent = $this->getValue($indexName, self::REDIS_SERIALIZER_NONE);
121
122         if (!$indexContent) {
123             return false;
124         }
125
126         $profileList = explode("\n", $indexContent);
127
128         $result = array();
129
130         foreach ($profileList as $item) {
131             if ($item == '') {
132                 continue;
133             }
134
135             if (false !== $pos = strpos($item, "\t")) {
136                 $result[] = $this->getItemName(substr($item, 0, $pos));
137             }
138         }
139
140         $result[] = $indexName;
141
142         return $this->delete($result);
143     }
144
145     /**
146      * {@inheritdoc}
147      */
148     public function read($token)
149     {
150         if (empty($token)) {
151             return false;
152         }
153
154         $profile = $this->getValue($this->getItemName($token), self::REDIS_SERIALIZER_PHP);
155
156         if (false !== $profile) {
157             $profile = $this->createProfileFromData($token, $profile);
158         }
159
160         return $profile;
161     }
162
163     /**
164      * {@inheritdoc}
165      */
166     public function write(Profile $profile)
167     {
168         $data = array(
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(),
177         );
178
179         $profileIndexed = false !== $this->getValue($this->getItemName($profile->getToken()));
180
181         if ($this->setValue($this->getItemName($profile->getToken()), $data, $this->lifetime, self::REDIS_SERIALIZER_PHP)) {
182             if (!$profileIndexed) {
183                 // Add to index
184                 $indexName = $this->getIndexName();
185
186                 $indexRow = implode("\t", array(
187                     $profile->getToken(),
188                     $profile->getIp(),
189                     $profile->getMethod(),
190                     $profile->getUrl(),
191                     $profile->getTime(),
192                     $profile->getParentToken(),
193                     $profile->getStatusCode(),
194                 ))."\n";
195
196                 return $this->appendValue($indexName, $indexRow, $this->lifetime);
197             }
198
199             return true;
200         }
201
202         return false;
203     }
204
205     /**
206      * Internal convenience method that returns the instance of Redis.
207      *
208      * @return \Redis
209      *
210      * @throws \RuntimeException
211      */
212     protected function getRedis()
213     {
214         if (null === $this->redis) {
215             $data = parse_url($this->dsn);
216
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));
219             }
220
221             if (!extension_loaded('redis')) {
222                 throw new \RuntimeException('RedisProfilerStorage requires that the redis extension is loaded.');
223             }
224
225             $redis = new \Redis();
226             $redis->connect($data['host'], $data['port']);
227
228             if (isset($data['path'])) {
229                 $redis->select(substr($data['path'], 1));
230             }
231
232             if (isset($data['pass'])) {
233                 $redis->auth($data['pass']);
234             }
235
236             $redis->setOption(self::REDIS_OPT_PREFIX, self::TOKEN_PREFIX);
237
238             $this->redis = $redis;
239         }
240
241         return $this->redis;
242     }
243
244     /**
245      * Set instance of the Redis.
246      *
247      * @param \Redis $redis
248      */
249     public function setRedis($redis)
250     {
251         $this->redis = $redis;
252     }
253
254     private function createProfileFromData($token, $data, $parent = null)
255     {
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']);
262
263         if (!$parent && $data['parent']) {
264             $parent = $this->read($data['parent']);
265         }
266
267         if ($parent) {
268             $profile->setParent($parent);
269         }
270
271         foreach ($data['children'] as $token) {
272             if (!$token) {
273                 continue;
274             }
275
276             if (!$childProfileData = $this->getValue($this->getItemName($token), self::REDIS_SERIALIZER_PHP)) {
277                 continue;
278             }
279
280             $profile->addChild($this->createProfileFromData($token, $childProfileData, $profile));
281         }
282
283         return $profile;
284     }
285
286     /**
287      * Gets the item name.
288      *
289      * @param string $token
290      *
291      * @return string
292      */
293     private function getItemName($token)
294     {
295         $name = $token;
296
297         if ($this->isItemNameValid($name)) {
298             return $name;
299         }
300
301         return false;
302     }
303
304     /**
305      * Gets the name of the index.
306      *
307      * @return string
308      */
309     private function getIndexName()
310     {
311         $name = 'index';
312
313         if ($this->isItemNameValid($name)) {
314             return $name;
315         }
316
317         return false;
318     }
319
320     private function isItemNameValid($name)
321     {
322         $length = strlen($name);
323
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));
326         }
327
328         return true;
329     }
330
331     /**
332      * Retrieves an item from the Redis server.
333      *
334      * @param string $key
335      * @param int    $serializer
336      *
337      * @return mixed
338      */
339     private function getValue($key, $serializer = self::REDIS_SERIALIZER_NONE)
340     {
341         $redis = $this->getRedis();
342         $redis->setOption(self::REDIS_OPT_SERIALIZER, $serializer);
343
344         return $redis->get($key);
345     }
346
347     /**
348      * Stores an item on the Redis server under the specified key.
349      *
350      * @param string $key
351      * @param mixed  $value
352      * @param int    $expiration
353      * @param int    $serializer
354      *
355      * @return bool
356      */
357     private function setValue($key, $value, $expiration = 0, $serializer = self::REDIS_SERIALIZER_NONE)
358     {
359         $redis = $this->getRedis();
360         $redis->setOption(self::REDIS_OPT_SERIALIZER, $serializer);
361
362         return $redis->setex($key, $expiration, $value);
363     }
364
365     /**
366      * Appends data to an existing item on the Redis server.
367      *
368      * @param string $key
369      * @param string $value
370      * @param int    $expiration
371      *
372      * @return bool
373      */
374     private function appendValue($key, $value, $expiration = 0)
375     {
376         $redis = $this->getRedis();
377         $redis->setOption(self::REDIS_OPT_SERIALIZER, self::REDIS_SERIALIZER_NONE);
378
379         if ($redis->exists($key)) {
380             $redis->append($key, $value);
381
382             return $redis->setTimeout($key, $expiration);
383         }
384
385         return $redis->setex($key, $expiration, $value);
386     }
387
388     /**
389      * Removes the specified keys.
390      *
391      * @param array $keys
392      *
393      * @return bool
394      */
395     private function delete(array $keys)
396     {
397         return (bool) $this->getRedis()->delete($keys);
398     }
399 }