Test Setup Failed
Push — master ( 2c3741...78c16b )
by Alex
03:04
created

MongoDbProfilerStorage::parseDsn()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 1
dl 0
loc 19
rs 9.6333
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
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('data' => 0, 'sort' => array('time' => -1), 'limit' => intval($limit))
63
        );
64
65
        $tokens = array();
66
        foreach ($cursor as $profile) {
67
            $tokens[] = $this->getData($profile);
68
        }
69
70
        return $tokens;
71
    }
72
73
    /**
74
     * {@inheritdoc}
75
     */
76
    public function purge()
77
    {
78
        return $this->getMongo()->deleteMany(array());
79
    }
80
81
    /**
82
     * {@inheritdoc}
83
     */
84
    public function read($token)
85
    {
86
        $profile = $this->getMongo()->findOne(array('_id' => $token, 'data' => array('$exists' => true)));
87
88
        if (null !== $profile) {
89
            $profile = $this->createProfileFromData($this->getData($profile));
90
        }
91
92
        return $profile;
93
    }
94
95
    /**
96
     * {@inheritdoc}
97
     */
98
    public function write(Profile $profile)
99
    {
100
        $this->cleanup();
101
102
        $record = array(
103
            '_id' => $profile->getToken(),
104
            'parent' => $profile->getParentToken(),
105
            'data' => base64_encode(serialize($profile->getCollectors())),
106
            'ip' => $profile->getIp(),
107
            'method' => $profile->getMethod(),
108
            'url' => $profile->getUrl(),
109
            'time' => $profile->getTime(),
110
            'status_code' => $profile->getStatusCode(),
111
        );
112
113
        $result = $this->getMongo()->updateOne(array('_id' => $profile->getToken()), array(
114
            '$set' => array_filter($record, function ($v) {
115
                return !empty($v);
116
            }),
117
        ), array('upsert' => true));
118
119
        return $result->isAcknowledged();
120
    }
121
122
    /**
123
     * Internal convenience method that returns the instance of the MongoDB Collection.
124
     *
125
     * @return \MongoDB\Collection
126
     */
127
    protected function getMongo()
128
    {
129
        if (null !== $this->mongo) {
130
            return $this->mongo;
131
        }
132
133
        if (!$parsedDsn = $this->parseDsn($this->dsn)) {
134
            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));
135
        }
136
137
        list($server, $database, $collection) = $parsedDsn;
138
139
        $client = new \MongoDB\Client('mongodb://'.$server.'/'.$database);
140
141
        return $this->mongo = $client->selectCollection($database, $collection);
142
    }
143
144
    /**
145
     * @param array $data
146
     *
147
     * @return Profile
148
     */
149
    protected function createProfileFromData(array $data)
150
    {
151
        $profile = $this->getProfile($data);
152
153
        if ($data['parent']) {
154
            $parent = $this->getMongo()->findOne(array('_id' => $data['parent'], 'data' => array('$exists' => true)));
155
            if ($parent) {
156
                $profile->setParent($this->getProfile($this->getData($parent)));
157
            }
158
        }
159
160
        $profile->setChildren($this->readChildren($data['token']));
161
162
        return $profile;
163
    }
164
165
    /**
166
     * @param string $token
167
     *
168
     * @return Profile[] An array of Profile instances
169
     */
170
    protected function readChildren($token)
171
    {
172
        $profiles = array();
173
174
        $cursor = $this->getMongo()->find(array('parent' => $token, 'data' => array('$exists' => true)));
175
        foreach ($cursor as $d) {
176
            $profiles[] = $this->getProfile($this->getData($d));
177
        }
178
179
        return $profiles;
180
    }
181
182
    protected function cleanup()
183
    {
184
        $this->getMongo()->deleteMany(array('time' => array('$lt' => time() - $this->lifetime)));
185
    }
186
187
    /**
188
     * @param string $ip
189
     * @param string $url
190
     * @param string $method
191
     * @param int    $start
192
     * @param int    $end
193
     * @param int    $statusCode
194
     *
195
     * @return array
196
     */
197
    private function buildQuery($ip, $url, $method, $start, $end, $statusCode)
198
    {
199
        $query = array();
200
201
        if (!empty($ip)) {
202
            $query['ip'] = $ip;
203
        }
204
205
        if (!empty($url)) {
206
            $query['url'] = $url;
207
        }
208
209
        if (!empty($method)) {
210
            $query['method'] = $method;
211
        }
212
213
        if (!empty($start) || !empty($end)) {
214
            $query['time'] = array();
215
        }
216
217
        if (!empty($start)) {
218
            $query['time']['$gte'] = $start;
219
        }
220
221
        if (!empty($end)) {
222
            $query['time']['$lte'] = $end;
223
        }
224
225
        if (!empty($statusCode)) {
226
            $query['status_code'] = $statusCode;
227
        }
228
229
        return $query;
230
    }
231
232
    /**
233
     * @param $data
234
     *
235
     * @return array
236
     */
237
    private function getData($data)
238
    {
239
        $data = $data instanceof \MongoDB\Model\BSONDocument ? $data->getArrayCopy() : $data;
240
        if (!is_array($data)) {
241
            throw new \InvalidArgumentException('$data is not a valid BSONDocument or array.');
242
        }
243
244
        return array(
245
            'token' => $data['_id'],
246
            'parent' => isset($data['parent']) ? $data['parent'] : null,
247
            'ip' => isset($data['ip']) ? $data['ip'] : null,
248
            'method' => isset($data['method']) ? $data['method'] : null,
249
            'url' => isset($data['url']) ? $data['url'] : null,
250
            'time' => isset($data['time']) ? $data['time'] : null,
251
            'data' => isset($data['data']) ? $data['data'] : null,
252
            'status_code' => isset($data['status_code']) ? $data['status_code'] : null,
253
        );
254
    }
255
256
    /**
257
     * @param array $data
258
     *
259
     * @return Profile
260
     */
261
    private function getProfile(array $data)
262
    {
263
        $profile = new Profile($data['token']);
264
        $profile->setIp($data['ip']);
265
        $profile->setMethod($data['method']);
266
        $profile->setUrl($data['url']);
267
        $profile->setTime($data['time']);
268
        $profile->setCollectors(unserialize(base64_decode($data['data'])));
269
270
        return $profile;
271
    }
272
273
    /**
274
     * @param string $dsn
275
     *
276
     * @return null|array Array($server, $database, $collection)
277
     */
278
    private function parseDsn($dsn)
279
    {
280
        if (!preg_match('#^mongodb://(.*)/(.*)/(.*)$#', $dsn, $matches)) {
281
            return;
282
        }
283
284
        $server = $matches[1];
285
        $database = $matches[2];
286
        $collection = $matches[3];
287
288
        /* FIXME: This adds backwards compatibility but cannot function for modern implementations *
289
        preg_match('#^mongodb://(([^:]+):?(.*)(?=@))?@?([^/]*)(.*)$#', $server, $matchesServer);
290
291
        if ('' == $matchesServer[5] && '' != $matches[2]) {
292
            $server .= '/'.$matches[2];
293
        }
294
        /* */
295
296
        return array($server, $database, $collection);
297
    }
298
}
299