- Added abstract PDO profiler storage, updated sqlite storage and added a mysql storage.

- Updated profiler config in framework bundle
This commit is contained in:
Jan Schumann 2011-03-05 13:06:17 +01:00
parent e5fad94482
commit d1ebc8da9f
6 changed files with 276 additions and 125 deletions

View File

@ -7,7 +7,9 @@
<parameters>
<parameter key="profiler.class">Symfony\Component\HttpKernel\Profiler\Profiler</parameter>
<parameter key="profiler.storage.class">Symfony\Component\HttpKernel\Profiler\SqliteProfilerStorage</parameter>
<parameter key="profiler.storage.file">%kernel.cache_dir%/profiler.db</parameter>
<parameter key="profiler.storage.dsn">sqlite:%kernel.cache_dir%/profiler.db</parameter>
<parameter key="profiler.storage.username"></parameter>
<parameter key="profiler.storage.password"></parameter>
<parameter key="profiler.storage.lifetime">86400</parameter>
<parameter key="profiler_listener.class">Symfony\Bundle\FrameworkBundle\Profiler\ProfilerListener</parameter>
<parameter key="profiler_listener.only_exceptions">false</parameter>
@ -20,7 +22,9 @@
</service>
<service id="profiler.storage" class="%profiler.storage.class%" public="false">
<argument>%profiler.storage.file%</argument>
<argument>%profiler.storage.dsn%</argument>
<argument>%profiler.storage.username%</argument>
<argument>%profiler.storage.password%</argument>
<argument>%profiler.storage.lifetime%</argument>
</service>

View File

@ -0,0 +1,45 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\Profiler;
/**
* A ProfilerStorage for Mysql
*
* @author Jan Schumann <js@schumann-it.com>
*/
class MysqlProfilerStorage extends PdoProfilerStorage
{
/**
* {@inheritdoc}
*/
protected function initDb()
{
if (is_null($this->db))
{
if ('mysql' !== substr($this->dsn, 0, 5))
{
throw new \RuntimeException('Please check your configuration. You are trying to use Mysql with a wrong dsn. "' . $this->dsn . '"');
}
if (!class_exists('PDO') || !in_array('mysql', \PDO::getAvailableDrivers(), true)) {
throw new \RuntimeException('You need to enable PDO_Mysql extension for the profiler to run properly.');
}
$db = new \PDO($this->dsn, $this->username, $this->password);
$db->exec('CREATE TABLE IF NOT EXISTS data (token VARCHAR(255) PRIMARY KEY, data LONGTEXT, ip VARCHAR(64), url VARCHAR(255), time INTEGER UNSIGNED, parent VARCHAR(255), created_at INTEGER UNSIGNED, KEY (created_at), KEY (ip), KEY (url), KEY (parent))');
$this->db = $db;
}
return $this->db;
}
}

View File

@ -0,0 +1,194 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpKernel\Profiler;
use Symfony\Component\HttpKernel\DataCollector\DataCollectorInterface;
/**
* Base PDO storage for profiling information in a PDO database.
*
* @author Jan Schumann <js@schumann-it.com>
*/
abstract class PdoProfilerStorage implements ProfilerStorageInterface
{
protected $dsn;
protected $username;
protected $password;
protected $lifetime;
protected $db;
/**
* Constructor.
*
* @param string $dsn A data source name
* @param string $username The username for the database
* @param string $password The password for the database
* @param integer $lifetime The lifetime to use for the purge
*/
public function __construct($dsn, $username = '', $password = '', $lifetime = 86400)
{
$this->dsn = $dsn;
$this->username = $username;
$this->password = $password;
$this->lifetime = (int) $lifetime;
}
/**
* {@inheritdoc}
*/
public function find($ip, $url, $limit)
{
list($criteria, $args) = $this->buildCriteria($ip, $url, $limit);
$criteria = $criteria ? 'WHERE '.implode(' AND ', $criteria) : '';
$db = $this->initDb();
$tokens = $this->fetch($db, 'SELECT token, ip, url, time FROM data '.$criteria.' ORDER BY time DESC LIMIT '.((integer) $limit), $args);
$this->close($db);
return $tokens;
}
protected function buildCriteria($ip, $url, $limit)
{
$criteria = array();
$args = array();
if ($ip = preg_replace('/[^\d\.]/', '', $ip)) {
$criteria[] = 'ip LIKE :ip';
$args[':ip'] = '%'.$ip.'%';
}
if ($url) {
$criteria[] = 'url LIKE :url';
$args[':url'] = '%'.addcslashes($url, '%_\\').'%';
}
return array($criteria, $args);
}
/**
* {@inheritdoc}
*/
public function read($token)
{
$db = $this->initDb();
$args = array(':token' => $token);
$data = $this->fetch($db, 'SELECT data, ip, url, time FROM data WHERE token = :token LIMIT 1', $args);
$this->close($db);
if (isset($data[0]['data'])) {
return array($data[0]['data'], $data[0]['ip'], $data[0]['url'], $data[0]['time']);
}
return false;
}
/**
* {@inheritdoc}
*/
public function write($token, $parent, $data, $ip, $url, $time)
{
$db = $this->initDb();
$args = array(
':token' => $token,
':parent' => $parent,
':data' => $data,
':ip' => $ip,
':url' => $url,
':time' => $time,
':created_at' => time(),
);
try {
$this->exec($db, 'INSERT INTO data (token, parent, data, ip, url, time, created_at) VALUES (:token, :parent, :data, :ip, :url, :time, :created_at)', $args);
$this->cleanup();
$status = true;
} catch (\Exception $e) {
$status = false;
}
$this->close($db);
return $status;
}
/**
* {@inheritdoc}
*/
public function purge()
{
$db = $this->initDb();
$this->exec($db, 'DELETE FROM data');
$this->close($db);
}
protected function cleanup()
{
$db = $this->initDb();
$this->exec($db, 'DELETE FROM data WHERE created_at < :time', array(':time' => time() - $this->lifetime));
$this->close($db);
}
/**
* @throws \RuntimeException When the requeted database driver is not installed
*/
abstract protected function initDb();
protected function exec($db, $query, array $args = array())
{
$stmt = $this->prepareStatement($db, $query);
foreach ($args as $arg => $val) {
$stmt->bindValue($arg, $val, is_int($val) ? \PDO::PARAM_INT : \PDO::PARAM_STR);
}
$success = $stmt->execute();
if (!$success) {
throw new \RuntimeException(sprintf('Error executing query "%s"', $query));
}
}
protected function prepareStatement($db, $query)
{
try {
$stmt = $db->prepare($query);
}
catch (\Exception $e)
{
$stmt = false;
}
if (false === $stmt) {
throw new \RuntimeException('The database cannot successfully prepare the statement');
}
return $stmt;
}
protected function fetch($db, $query, array $args = array())
{
$return = array();
$stmt = $this->prepareStatement($db, $query);
foreach ($args as $arg => $val) {
$stmt->bindValue($arg, $val, is_int($val) ? \PDO::PARAM_INT : \PDO::PARAM_STR);
}
$stmt->execute();
$return = $stmt->fetchAll(\PDO::FETCH_ASSOC);
return $return;
}
protected function close($db)
{
if ($db instanceof \SQLite3) {
$db->close();
}
}
}

View File

@ -18,27 +18,9 @@ use Symfony\Component\HttpKernel\DataCollector\DataCollectorInterface;
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class SqliteProfilerStorage implements ProfilerStorageInterface
class SqliteProfilerStorage extends PdoProfilerStorage
{
protected $store;
protected $lifetime;
/**
* Constructor.
*
* @param string $store The path to the SQLite DB
* @param integer $lifetime The lifetime to use for the purge
*/
public function __construct($store, $lifetime = 86400)
{
$this->store = $store;
$this->lifetime = (int) $lifetime;
}
/**
* {@inheritdoc}
*/
public function find($ip, $url, $limit)
protected function buildCriteria($ip, $url, $limit)
{
$criteria = array();
$args = array();
@ -53,73 +35,7 @@ class SqliteProfilerStorage implements ProfilerStorageInterface
$args[':url'] = '%'.addcslashes($url, '%_\\').'%';
}
$criteria = $criteria ? 'WHERE '.implode(' AND ', $criteria) : '';
$db = $this->initDb();
$tokens = $this->fetch($db, 'SELECT token, ip, url, time FROM data '.$criteria.' ORDER BY time DESC LIMIT '.((integer) $limit), $args);
$this->close($db);
return $tokens;
}
/**
* {@inheritdoc}
*/
public function read($token)
{
$db = $this->initDb();
$args = array(':token' => $token);
$data = $this->fetch($db, 'SELECT data, ip, url, time FROM data WHERE token = :token LIMIT 1', $args);
$this->close($db);
if (isset($data[0]['data'])) {
return array($data[0]['data'], $data[0]['ip'], $data[0]['url'], $data[0]['time']);
}
return false;
}
/**
* {@inheritdoc}
*/
public function write($token, $parent, $data, $ip, $url, $time)
{
$db = $this->initDb();
$args = array(
':token' => $token,
':parent' => $parent,
':data' => $data,
':ip' => $ip,
':url' => $url,
':time' => $time,
':created_at' => time(),
);
try {
$this->exec($db, 'INSERT INTO data (token, parent, data, ip, url, time, created_at) VALUES (:token, :parent, :data, :ip, :url, :time, :created_at)', $args);
$this->cleanup();
$status = true;
} catch (\Exception $e) {
$status = false;
}
$this->close($db);
return $status;
}
/**
* {@inheritdoc}
*/
public function purge()
{
$db = $this->initDb();
$this->exec($db, 'DELETE FROM data');
$this->close($db);
}
protected function cleanup()
{
$db = $this->initDb();
$this->exec($db, 'DELETE FROM data WHERE created_at < :time', array(':time' => time() - $this->lifetime));
$this->close($db);
return array($criteria, $args);
}
/**
@ -127,33 +43,35 @@ class SqliteProfilerStorage implements ProfilerStorageInterface
*/
protected function initDb()
{
if (class_exists('SQLite3')) {
$db = new \SQLite3($this->store, \SQLITE3_OPEN_READWRITE | \SQLITE3_OPEN_CREATE);
} elseif (class_exists('PDO') && in_array('sqlite', \PDO::getAvailableDrivers(), true)) {
$db = new \PDO('sqlite:'.$this->store);
} else {
throw new \RuntimeException('You need to enable either the SQLite or PDO_SQLite extension for the profiler to run properly.');
if (is_null($this->db) || $this->db instanceof \SQLite3) {
if ('sqlite' !== substr($this->dsn, 0, 6 )) {
throw new \RuntimeException('You are trying to use Sqlite with a wrong dsn. "' . $this->dsn . '"');
}
if (class_exists('SQLite3')) {
$db = new \SQLite3(substr($this->dsn, 7, strlen($this->dsn)), \SQLITE3_OPEN_READWRITE | \SQLITE3_OPEN_CREATE);
} elseif (class_exists('PDO') && in_array('sqlite', \PDO::getAvailableDrivers(), true)) {
$db = new \PDO($this->dsn);
} else {
throw new \RuntimeException('You need to enable either the SQLite or PDO_SQLite extension for the profiler to run properly.');
}
$db->exec('CREATE TABLE IF NOT EXISTS data (token STRING, data STRING, ip STRING, url STRING, time INTEGER, parent STRING, created_at INTEGER)');
$db->exec('CREATE INDEX IF NOT EXISTS data_created_at ON data (created_at)');
$db->exec('CREATE INDEX IF NOT EXISTS data_ip ON data (ip)');
$db->exec('CREATE INDEX IF NOT EXISTS data_url ON data (url)');
$db->exec('CREATE INDEX IF NOT EXISTS data_parent ON data (parent)');
$db->exec('CREATE UNIQUE INDEX IF NOT EXISTS data_token ON data (token)');
$this->db = $db;
}
$db->exec('CREATE TABLE IF NOT EXISTS data (token STRING, data STRING, ip STRING, url STRING, time INTEGER, parent STRING, created_at INTEGER)');
$db->exec('CREATE INDEX IF NOT EXISTS data_created_at ON data (created_at)');
$db->exec('CREATE INDEX IF NOT EXISTS data_ip ON data (ip)');
$db->exec('CREATE INDEX IF NOT EXISTS data_url ON data (url)');
$db->exec('CREATE INDEX IF NOT EXISTS data_parent ON data (parent)');
$db->exec('CREATE UNIQUE INDEX IF NOT EXISTS data_token ON data (token)');
return $db;
return $this->db;
}
protected function exec($db, $query, array $args = array())
{
$stmt = $db->prepare($query);
if (false === $stmt) {
throw new \RuntimeException('The database cannot successfully prepare the statement');
}
if ($db instanceof \SQLite3) {
$stmt = $this->prepareStatement($db, $query);
foreach ($args as $arg => $val) {
$stmt->bindValue($arg, $val, is_int($val) ? \SQLITE3_INTEGER : \SQLITE3_TEXT);
}
@ -164,22 +82,16 @@ class SqliteProfilerStorage implements ProfilerStorageInterface
}
$res->finalize();
} else {
foreach ($args as $arg => $val) {
$stmt->bindValue($arg, $val, is_int($val) ? \PDO::PARAM_INT : \PDO::PARAM_STR);
}
$success = $stmt->execute();
if (!$success) {
throw new \RuntimeException(sprintf('Error executing SQLite query "%s"', $query));
}
parent::exec($db, $query, $args);
}
}
protected function fetch($db, $query, array $args = array())
{
$return = array();
$stmt = $db->prepare($query);
if ($db instanceof \SQLite3) {
$stmt = $this->prepareStatement($db, $query, true);
foreach ($args as $arg => $val) {
$stmt->bindValue($arg, $val, is_int($val) ? \SQLITE3_INTEGER : \SQLITE3_TEXT);
}
@ -190,11 +102,7 @@ class SqliteProfilerStorage implements ProfilerStorageInterface
$res->finalize();
$stmt->close();
} else {
foreach ($args as $arg => $val) {
$stmt->bindValue($arg, $val, is_int($val) ? \PDO::PARAM_INT : \PDO::PARAM_STR);
}
$stmt->execute();
$return = $stmt->fetchAll(\PDO::FETCH_ASSOC);
$return = parent::fetch($db, $query, $args);
}
return $return;

View File

@ -30,7 +30,7 @@ class ProfilerTest extends \PHPUnit_Framework_TestCase
if (file_exists($tmp)) {
@unlink($tmp);
}
$storage = new SqliteProfilerStorage($tmp);
$storage = new SqliteProfilerStorage('sqlite:' . $tmp);
$storage->purge();
$profiler = new Profiler($storage);

View File

@ -24,7 +24,7 @@ class SqliteProfilerStorageTest extends \PHPUnit_Framework_TestCase
if (file_exists(self::$dbFile)) {
@unlink(self::$dbFile);
}
self::$storage = new SqliteProfilerStorage(self::$dbFile);
self::$storage = new SqliteProfilerStorage('sqlite:' . self::$dbFile);
}
public static function tearDownAfterClass()
@ -52,7 +52,7 @@ class SqliteProfilerStorageTest extends \PHPUnit_Framework_TestCase
self::$storage->write('simple_quote', '', 'data', '127.0.0.1', 'http://foo.bar/\'', time());
self::$storage->write('double_quote', '', 'data', '127.0.0.1', 'http://foo.bar/"', time());
self::$storage->write('backslash', '', 'data', '127.0.0.1', 'http://foo.bar/\\', time());
$this->assertTrue(false !== self::$storage->read('simple_quote'), '->write() accepts single quotes in URL');
$this->assertTrue(false !== self::$storage->read('double_quote'), '->write() accepts double quotes in URL');
$this->assertTrue(false !== self::$storage->read('backslash'), '->write() accpets backslash in URL');