BaseMemcacheProfilerStorage::getItemName()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 1
dl 0
loc 9
rs 10
c 0
b 0
f 0
1
<?php
2
3
/*
4
 * This file is part of the Symfony package.
5
 *
6
 * (c) Fabien Potencier <[email protected]>
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 Sitetheory\Bundle\ProfilerStorageBundle\Profiler;
13
14
use Symfony\Component\HttpKernel\Profiler\Profile;
15
16
/**
17
 * Base Memcache storage for profiling information in a Memcache.
18
 *
19
 * Class BaseMemcacheProfilerStorage
20
 *
21
 * @author Andrej Hudec <[email protected]>
22
 */
23
abstract class BaseMemcacheProfilerStorage implements ProfilerStorageInterface
24
{
25
    const TOKEN_PREFIX = 'sf_profiler_';
26
27
    protected $dsn;
28
    protected $lifetime;
29
30
    /**
31
     * Constructor.
32
     *
33
     * @param string $dsn      A data source name
34
     * @param string $username
35
     * @param string $password
36
     * @param int    $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->lifetime = (int) $lifetime;
42
    }
43
44
    /**
45
     * {@inheritdoc}
46
     */
47
    public function find($ip, $url, $limit, $method, $start = null, $end = null)
48
    {
49
        $indexName = $this->getIndexName();
50
51
        $indexContent = $this->getValue($indexName);
52
        if (!$indexContent) {
53
            return array();
54
        }
55
56
        $profileList = explode("\n", $indexContent);
57
        $result = array();
58
59
        foreach ($profileList as $item) {
60
            if (0 === $limit) {
61
                break;
62
            }
63
64
            if ('' == $item) {
65
                continue;
66
            }
67
68
            $values = explode("\t", $item, 7);
69
            list($itemToken, $itemIp, $itemMethod, $itemUrl, $itemTime, $itemParent) = $values;
70
            $statusCode = isset($values[6]) ? $values[6] : null;
71
72
            $itemTime = (int) $itemTime;
73
74
            if ($ip && false === strpos($itemIp, $ip) || $url && false === strpos($itemUrl, $url) || $method && false === strpos($itemMethod, $method)) {
75
                continue;
76
            }
77
78
            if (!empty($start) && $itemTime < $start) {
79
                continue;
80
            }
81
82
            if (!empty($end) && $itemTime > $end) {
83
                continue;
84
            }
85
86
            $result[$itemToken] = array(
87
                'token' => $itemToken,
88
                'ip' => $itemIp,
89
                'method' => $itemMethod,
90
                'url' => $itemUrl,
91
                'time' => $itemTime,
92
                'parent' => $itemParent,
93
                'status_code' => $statusCode,
94
            );
95
            --$limit;
96
        }
97
98
        usort($result, function ($a, $b) {
99
            if ($a['time'] === $b['time']) {
100
                return 0;
101
            }
102
103
            return $a['time'] > $b['time'] ? -1 : 1;
104
        });
105
106
        return $result;
107
    }
108
109
    /**
110
     * {@inheritdoc}
111
     */
112
    public function purge()
113
    {
114
        // delete only items from index
115
        $indexName = $this->getIndexName();
116
117
        $indexContent = $this->getValue($indexName);
118
119
        if (!$indexContent) {
120
            return false;
121
        }
122
123
        $profileList = explode("\n", $indexContent);
124
125
        foreach ($profileList as $item) {
126
            if ('' == $item) {
127
                continue;
128
            }
129
130
            if (false !== $pos = strpos($item, "\t")) {
131
                $this->delete($this->getItemName(substr($item, 0, $pos)));
132
            }
133
        }
134
135
        return $this->delete($indexName);
136
    }
137
138
    /**
139
     * {@inheritdoc}
140
     */
141
    public function read($token)
142
    {
143
        if (empty($token)) {
144
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the return type mandated by Symfony\Component\HttpKe...torageInterface::read() of Symfony\Component\HttpKernel\Profiler\Profile.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
145
        }
146
147
        $profile = $this->getValue($this->getItemName($token));
148
149
        if (false !== $profile) {
150
            $profile = $this->createProfileFromData($token, $profile);
151
        }
152
153
        return $profile;
154
    }
155
156
    /**
157
     * {@inheritdoc}
158
     */
159
    public function write(Profile $profile)
160
    {
161
        $data = array(
162
            'token' => $profile->getToken(),
163
            'parent' => $profile->getParentToken(),
164
            'children' => array_map(function ($p) {
165
                return $p->getToken();
166
            }, $profile->getChildren()),
167
            'data' => $profile->getCollectors(),
168
            'ip' => $profile->getIp(),
169
            'method' => $profile->getMethod(),
170
            'url' => $profile->getUrl(),
171
            'time' => $profile->getTime(),
172
        );
173
174
        $profileIndexed = false !== $this->getValue($this->getItemName($profile->getToken()));
175
176
        if ($this->setValue($this->getItemName($profile->getToken()), $data, $this->lifetime)) {
177
            if (!$profileIndexed) {
178
                // Add to index
179
                $indexName = $this->getIndexName();
180
181
                $indexRow = implode("\t", array(
182
                    $profile->getToken(),
183
                    $profile->getIp(),
184
                    $profile->getMethod(),
185
                    $profile->getUrl(),
186
                    $profile->getTime(),
187
                    $profile->getParentToken(),
188
                    $profile->getStatusCode(),
189
                ))."\n";
190
191
                return $this->appendValue($indexName, $indexRow, $this->lifetime);
192
            }
193
194
            return true;
195
        }
196
197
        return false;
198
    }
199
200
    /**
201
     * Retrieve item from the memcache server.
202
     *
203
     * @param string $key
204
     *
205
     * @return mixed
206
     */
207
    abstract protected function getValue($key);
208
209
    /**
210
     * Store an item on the memcache server under the specified key.
211
     *
212
     * @param string $key
213
     * @param mixed  $value
214
     * @param int    $expiration
215
     *
216
     * @return bool
217
     */
218
    abstract protected function setValue($key, $value, $expiration = 0);
219
220
    /**
221
     * Delete item from the memcache server.
222
     *
223
     * @param string $key
224
     *
225
     * @return bool
226
     */
227
    abstract protected function delete($key);
228
229
    /**
230
     * Append data to an existing item on the memcache server.
231
     *
232
     * @param string $key
233
     * @param string $value
234
     * @param int    $expiration
235
     *
236
     * @return bool
237
     */
238
    abstract protected function appendValue($key, $value, $expiration = 0);
239
240
    private function createProfileFromData($token, $data, $parent = null)
241
    {
242
        $profile = new Profile($token);
243
        $profile->setIp($data['ip']);
244
        $profile->setMethod($data['method']);
245
        $profile->setUrl($data['url']);
246
        $profile->setTime($data['time']);
247
        $profile->setCollectors($data['data']);
248
249
        if (!$parent && $data['parent']) {
250
            $parent = $this->read($data['parent']);
251
        }
252
253
        if ($parent) {
254
            $profile->setParent($parent);
255
        }
256
257
        foreach ($data['children'] as $token) {
0 ignored issues
show
introduced by
$token is overwriting one of the parameters of this function.
Loading history...
258
            if (!$token) {
259
                continue;
260
            }
261
262
            if (!$childProfileData = $this->getValue($this->getItemName($token))) {
263
                continue;
264
            }
265
266
            $profile->addChild($this->createProfileFromData($token, $childProfileData, $profile));
267
        }
268
269
        return $profile;
270
    }
271
272
    /**
273
     * Get item name.
274
     *
275
     * @param string $token
276
     *
277
     * @return string
278
     */
279
    private function getItemName($token)
280
    {
281
        $name = self::TOKEN_PREFIX.$token;
282
283
        if ($this->isItemNameValid($name)) {
284
            return $name;
285
        }
286
287
        return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type string.
Loading history...
288
    }
289
290
    /**
291
     * Get name of index.
292
     *
293
     * @return string
294
     */
295
    private function getIndexName()
296
    {
297
        $name = self::TOKEN_PREFIX.'index';
298
299
        if ($this->isItemNameValid($name)) {
300
            return $name;
301
        }
302
303
        return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type string.
Loading history...
304
    }
305
306
    private function isItemNameValid($name)
307
    {
308
        $length = strlen($name);
309
310
        if ($length > 250) {
311
            throw new \RuntimeException(sprintf('The memcache item key "%s" is too long (%s bytes). Allowed maximum size is 250 bytes.', $name, $length));
312
        }
313
314
        return true;
315
    }
316
}
317