PdoProfilerStorage::exec()   A
last analyzed

Complexity

Conditions 4
Paths 4

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 6
nc 4
nop 3
dl 0
loc 10
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 PDO storage for profiling information in a PDO database.
18
 *
19
 * Class PdoProfilerStorage
20
 *
21
 * @author Fabien Potencier <[email protected]>
22
 * @author Jan Schumann <[email protected]>
23
 */
24
abstract class PdoProfilerStorage implements ProfilerStorageInterface
25
{
26
    protected $dsn;
27
    protected $username;
28
    protected $password;
29
    protected $lifetime;
30
    protected $db;
31
32
    /**
33
     * Constructor.
34
     *
35
     * @param string $dsn      A data source name
36
     * @param string $username The username for the database
37
     * @param string $password The password for the database
38
     * @param int    $lifetime The lifetime to use for the purge
39
     */
40
    public function __construct($dsn, $username = '', $password = '', $lifetime = 86400)
41
    {
42
        $this->dsn = $dsn;
43
        $this->username = $username;
44
        $this->password = $password;
45
        $this->lifetime = (int) $lifetime;
46
    }
47
48
    /**
49
     * {@inheritdoc}
50
     */
51
    public function find($ip, $url, $limit, $method, $start = null, $end = null)
52
    {
53
        if (null === $start) {
54
            $start = 0;
55
        }
56
57
        if (null === $end) {
58
            $end = time();
59
        }
60
61
        list($criteria, $args) = $this->buildCriteria($ip, $url, $start, $end, $limit, $method);
62
63
        $criteria = $criteria ? 'WHERE '.implode(' AND ', $criteria) : '';
64
65
        $db = $this->initDb();
66
        $tokens = $this->fetch($db, 'SELECT token, ip, method, url, time, parent, status_code FROM sf_profiler_data '.$criteria.' ORDER BY time DESC LIMIT '.((int) $limit), $args);
67
        $this->close($db);
68
69
        return $tokens;
70
    }
71
72
    /**
73
     * {@inheritdoc}
74
     */
75
    public function read($token)
76
    {
77
        $db = $this->initDb();
78
        $args = array(':token' => $token);
79
        $data = $this->fetch($db, 'SELECT data, parent, ip, method, url, time FROM sf_profiler_data WHERE token = :token LIMIT 1', $args);
80
        $this->close($db);
81
        if (isset($data[0]['data'])) {
82
            return $this->createProfileFromData($token, $data[0]);
83
        }
84
    }
85
86
    /**
87
     * {@inheritdoc}
88
     */
89
    public function write(Profile $profile)
90
    {
91
        $db = $this->initDb();
92
        $args = array(
93
            ':token' => $profile->getToken(),
94
            ':parent' => $profile->getParentToken(),
95
            ':data' => base64_encode(serialize($profile->getCollectors())),
96
            ':ip' => $profile->getIp(),
97
            ':method' => $profile->getMethod(),
98
            ':url' => $profile->getUrl(),
99
            ':time' => $profile->getTime(),
100
            ':created_at' => time(),
101
            ':status_code' => $profile->getStatusCode(),
102
        );
103
104
        try {
105
            if ($this->has($profile->getToken())) {
106
                $this->exec($db, 'UPDATE sf_profiler_data SET parent = :parent, data = :data, ip = :ip, method = :method, url = :url, time = :time, created_at = :created_at, status_code = :status_code WHERE token = :token', $args);
107
            } else {
108
                $this->exec($db, 'INSERT INTO sf_profiler_data (token, parent, data, ip, method, url, time, created_at, status_code) VALUES (:token, :parent, :data, :ip, :method, :url, :time, :created_at, :status_code)', $args);
109
            }
110
            $this->cleanup();
111
            $status = true;
112
        } catch (\Exception $e) {
113
            $status = false;
114
        }
115
116
        $this->close($db);
117
118
        return $status;
119
    }
120
121
    /**
122
     * {@inheritdoc}
123
     */
124
    public function purge()
125
    {
126
        $db = $this->initDb();
127
        $this->exec($db, 'DELETE FROM sf_profiler_data');
128
        $this->close($db);
129
    }
130
131
    /**
132
     * Build SQL criteria to fetch records by ip and url.
133
     *
134
     * @param string $ip     The IP
135
     * @param string $url    The URL
136
     * @param string $start  The start period to search from
137
     * @param string $end    The end period to search to
138
     * @param string $limit  The maximum number of tokens to return
139
     * @param string $method The request method
140
     *
141
     * @return array An array with (criteria, args)
142
     */
143
    abstract protected function buildCriteria($ip, $url, $start, $end, $limit, $method);
144
145
    /**
146
     * Initializes the database.
147
     *
148
     * @throws \RuntimeException When the requested database driver is not installed
149
     */
150
    abstract protected function initDb();
151
152
    protected function cleanup()
153
    {
154
        $db = $this->initDb();
155
        $this->exec($db, 'DELETE FROM sf_profiler_data WHERE created_at < :time', array(':time' => time() - $this->lifetime));
156
        $this->close($db);
157
    }
158
159
    protected function exec($db, $query, array $args = array())
160
    {
161
        $stmt = $this->prepareStatement($db, $query);
162
163
        foreach ($args as $arg => $val) {
164
            $stmt->bindValue($arg, $val, is_int($val) ? \PDO::PARAM_INT : \PDO::PARAM_STR);
165
        }
166
        $success = $stmt->execute();
167
        if (!$success) {
168
            throw new \RuntimeException(sprintf('Error executing query "%s"', $query));
169
        }
170
    }
171
172
    protected function prepareStatement($db, $query)
173
    {
174
        try {
175
            $stmt = $db->prepare($query);
176
        } catch (\Exception $e) {
177
            $stmt = false;
178
        }
179
180
        if (false === $stmt) {
181
            throw new \RuntimeException('The database cannot successfully prepare the statement');
182
        }
183
184
        return $stmt;
185
    }
186
187
    protected function fetch($db, $query, array $args = array())
188
    {
189
        $stmt = $this->prepareStatement($db, $query);
190
191
        foreach ($args as $arg => $val) {
192
            $stmt->bindValue($arg, $val, is_int($val) ? \PDO::PARAM_INT : \PDO::PARAM_STR);
193
        }
194
        $stmt->execute();
195
196
        return $stmt->fetchAll(\PDO::FETCH_ASSOC);
197
    }
198
199
    protected function close($db)
200
    {
201
    }
202
203
    protected function createProfileFromData($token, $data, $parent = null)
204
    {
205
        $profile = new Profile($token);
206
        $profile->setIp($data['ip']);
207
        $profile->setMethod($data['method']);
208
        $profile->setUrl($data['url']);
209
        $profile->setTime($data['time']);
210
        $profile->setCollectors(unserialize(base64_decode($data['data'])));
211
212
        if (!$parent && !empty($data['parent'])) {
213
            $parent = $this->read($data['parent']);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $parent is correct as $this->read($data['parent']) targeting Sitetheory\Bundle\Profil...ProfilerStorage::read() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
214
        }
215
216
        if ($parent) {
217
            $profile->setParent($parent);
218
        }
219
220
        $profile->setChildren($this->readChildren($token, $profile));
0 ignored issues
show
Bug introduced by
$profile of type Symfony\Component\HttpKernel\Profiler\Profile is incompatible with the type string expected by parameter $parent of Sitetheory\Bundle\Profil...Storage::readChildren(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

220
        $profile->setChildren($this->readChildren($token, /** @scrutinizer ignore-type */ $profile));
Loading history...
221
222
        return $profile;
223
    }
224
225
    /**
226
     * Reads the child profiles for the given token.
227
     *
228
     * @param string $token  The parent token
229
     * @param string $parent The parent instance
230
     *
231
     * @return Profile[] An array of Profile instance
232
     */
233
    protected function readChildren($token, $parent)
234
    {
235
        $db = $this->initDb();
236
        $data = $this->fetch($db, 'SELECT token, data, ip, method, url, time FROM sf_profiler_data WHERE parent = :token', array(':token' => $token));
237
        $this->close($db);
238
239
        if (!$data) {
240
            return array();
241
        }
242
243
        $profiles = array();
244
        foreach ($data as $d) {
245
            $profiles[] = $this->createProfileFromData($d['token'], $d, $parent);
246
        }
247
248
        return $profiles;
249
    }
250
251
    /**
252
     * Returns whether data for the given token already exists in storage.
253
     *
254
     * @param string $token The profile token
255
     *
256
     * @return string
257
     */
258
    protected function has($token)
259
    {
260
        $db = $this->initDb();
261
        $tokenExists = $this->fetch($db, 'SELECT 1 FROM sf_profiler_data WHERE token = :token LIMIT 1', array(':token' => $token));
262
        $this->close($db);
263
264
        return !empty($tokenExists);
0 ignored issues
show
Bug Best Practice introduced by
The expression return ! empty($tokenExists) returns the type boolean which is incompatible with the documented return type string.
Loading history...
265
    }
266
}
267