MongoDbProfilerStorage::read()   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
 * MongoDbProfilerStorage stores profiling information in a Mongo database.
18
 *
19
 * Class MongoDbProfilerStorage
20
 *
21
 * @author Fabien Potencier <[email protected]>
22
 * @author Alex Gurrola <[email protected]>
23
 */
24
class MongoDbProfilerStorage implements ProfilerStorageInterface
25
{
26
    /**
27
     * @var string
28
     */
29
    protected $dsn;
30
31
    /**
32
     * @var int
33
     */
34
    protected $lifetime;
35
36
    /**
37
     * @var \MongoDB\Collection
0 ignored issues
show
Bug introduced by
The type MongoDB\Collection was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
38
     */
39
    private $mongo;
40
41
    /**
42
     * Constructor.
43
     *
44
     * @param string $dsn      A data source name
45
     * @param string $username Not used
46
     * @param string $password Not used
47
     * @param int    $lifetime The lifetime to use for the purge
48
     */
49
    public function __construct($dsn, $username = '', $password = '', $lifetime = 86400)
50
    {
51
        $this->dsn = $dsn;
52
        $this->lifetime = (int) $lifetime;
53
    }
54
55
    /**
56
     * {@inheritdoc}
57
     */
58
    public function find($ip, $url, $limit, $method, $start = null, $end = null, $statusCode = null)
59
    {
60
        $cursor = $this->getMongo()->find(
61
            $this->buildQuery($ip, $url, $method, $start, $end, $statusCode),
62
            array(
63
                'projection' => array('data' => 0),
64
                'sort' => array('time' => -1),
65
                'limit' => intval($limit)
66
            )
67
        );
68
69
        $tokens = array();
70
        foreach ($cursor as $profile) {
71
            $tokens[] = $this->getData($profile);
72
        }
73
74
        return $tokens;
75
    }
76
77
    /**
78
     * {@inheritdoc}
79
     */
80
    public function purge()
81
    {
82
        return $this->getMongo()->deleteMany(array());
83
    }
84
85
    /**
86
     * {@inheritdoc}
87
     */
88
    public function read($token)
89
    {
90
        $profile = $this->getMongo()->findOne(array('_id' => $token, 'data' => array('$exists' => true)));
91
92
        if (null !== $profile) {
93
            $profile = $this->createProfileFromData($this->getData($profile));
94
        }
95
96
        return $profile;
97
    }
98
99
    /**
100
     * {@inheritdoc}
101
     */
102
    public function write(Profile $profile)
103
    {
104
        $this->cleanup();
105
106
        $record = array(
107
            '_id' => $profile->getToken(),
108
            'parent' => $profile->getParentToken(),
109
            'data' => base64_encode(serialize($profile->getCollectors())),
110
            'ip' => $profile->getIp(),
111
            'method' => $profile->getMethod(),
112
            'url' => $profile->getUrl(),
113
            'time' => $profile->getTime(),
114
            'status_code' => $profile->getStatusCode(),
115
        );
116
117
        $result = $this->getMongo()->updateOne(array('_id' => $profile->getToken()), array(
118
            '$set' => array_filter($record, function ($v) {
119
                return !empty($v);
120
            }),
121
        ), array('upsert' => true));
122
123
        return $result->isAcknowledged();
124
    }
125
126
    /**
127
     * Internal convenience method that returns the instance of the MongoDB Collection.
128
     *
129
     * @return \MongoDB\Collection
130
     */
131
    protected function getMongo()
132
    {
133
        if (null !== $this->mongo) {
134
            return $this->mongo;
135
        }
136
137
        if (!$parsedDsn = $this->parseDsn($this->dsn)) {
138
            throw new \RuntimeException(sprintf('Please check your configuration. You are trying to use MongoDB with an invalid dsn "%s". The expected format is "mongodb://[user:pass@]host/database/collection"', $this->dsn));
139
        }
140
141
        list($server, $database, $collection) = $parsedDsn;
142
143
        $client = new \MongoDB\Client('mongodb://'.$server.'/'.$database);
0 ignored issues
show
Bug introduced by
The type MongoDB\Client was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
144
145
        return $this->mongo = $client->selectCollection($database, $collection);
146
    }
147
148
    /**
149
     * @param array $data
150
     *
151
     * @return Profile
152
     */
153
    protected function createProfileFromData(array $data)
154
    {
155
        $profile = $this->getProfile($data);
156
157
        if ($data['parent']) {
158
            $parent = $this->getMongo()->findOne(array('_id' => $data['parent'], 'data' => array('$exists' => true)));
159
            if ($parent) {
160
                $profile->setParent($this->getProfile($this->getData($parent)));
161
            }
162
        }
163
164
        $profile->setChildren($this->readChildren($data['token']));
165
166
        return $profile;
167
    }
168
169
    /**
170
     * @param string $token
171
     *
172
     * @return Profile[] An array of Profile instances
173
     */
174
    protected function readChildren($token)
175
    {
176
        $profiles = array();
177
178
        $cursor = $this->getMongo()->find(array('parent' => $token, 'data' => array('$exists' => true)));
179
        foreach ($cursor as $d) {
180
            $profiles[] = $this->getProfile($this->getData($d));
181
        }
182
183
        return $profiles;
184
    }
185
186
    protected function cleanup()
187
    {
188
        $this->getMongo()->deleteMany(array('time' => array('$lt' => time() - $this->lifetime)));
189
    }
190
191
    /**
192
     * @param string $ip
193
     * @param string $url
194
     * @param string $method
195
     * @param int    $start
196
     * @param int    $end
197
     * @param int    $statusCode
198
     *
199
     * @return array
200
     */
201
    private function buildQuery($ip, $url, $method, $start, $end, $statusCode)
202
    {
203
        $query = array();
204
205
        if (!empty($ip)) {
206
            $query['ip'] = $ip;
207
        }
208
209
        if (!empty($url)) {
210
            $query['url'] = $url;
211
        }
212
213
        if (!empty($method)) {
214
            $query['method'] = $method;
215
        }
216
217
        if (!empty($start) || !empty($end)) {
218
            $query['time'] = array();
219
        }
220
221
        if (!empty($start)) {
222
            $query['time']['$gte'] = $start;
223
        }
224
225
        if (!empty($end)) {
226
            $query['time']['$lte'] = $end;
227
        }
228
229
        if (!empty($statusCode)) {
230
            $query['status_code'] = intval($statusCode);
231
        }
232
233
        return $query;
234
    }
235
236
    /**
237
     * @param \MongoDB\Model\BSONDocument $data
238
     *
239
     * @return array
240
     */
241
    private function getData(\MongoDB\Model\BSONDocument $data)
0 ignored issues
show
Bug introduced by
The type MongoDB\Model\BSONDocument was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
242
    {
243
        $data = $data->getArrayCopy();
244
245
        return array(
246
            'token' => $data['_id'],
247
            'parent' => isset($data['parent']) ? $data['parent'] : null,
248
            'ip' => isset($data['ip']) ? $data['ip'] : null,
249
            'method' => isset($data['method']) ? $data['method'] : null,
250
            'url' => isset($data['url']) ? $data['url'] : null,
251
            'time' => isset($data['time']) ? $data['time'] : null,
252
            'data' => isset($data['data']) ? $data['data'] : null,
253
            'status_code' => isset($data['status_code']) ? $data['status_code'] : null,
254
        );
255
    }
256
257
    /**
258
     * @param array $data
259
     *
260
     * @return Profile
261
     */
262
    private function getProfile(array $data)
263
    {
264
        $profile = new Profile($data['token']);
265
        $profile->setIp($data['ip']);
266
        $profile->setMethod($data['method']);
267
        $profile->setUrl($data['url']);
268
        $profile->setTime($data['time']);
269
        $profile->setCollectors(unserialize(base64_decode($data['data'])));
270
271
        return $profile;
272
    }
273
274
    /**
275
     * @param string $dsn
276
     *
277
     * @return null|array Array($server, $database, $collection)
278
     */
279
    private function parseDsn($dsn)
280
    {
281
        if (!preg_match('#^mongodb://(.*)/(.*)/(.*)$#', $dsn, $matches)) {
282
            return null;
283
        }
284
285
        $server = $matches[1];
286
        $database = $matches[2];
287
        $collection = $matches[3];
288
289
        /* FIXME: This adds backwards compatibility but cannot function for modern implementations *
290
        preg_match('#^mongodb://(([^:]+):?(.*)(?=@))?@?([^/]*)(.*)$#', $server, $matchesServer);
291
292
        if ('' == $matchesServer[5] && '' != $matches[2]) {
293
            $server .= '/'.$matches[2];
294
        }
295
        /* */
296
297
        return array($server, $database, $collection);
298
    }
299
}
300