diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/profiling.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/profiling.xml index 5671cf3a5d20..478068602a1d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/profiling.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/profiling.xml @@ -7,7 +7,9 @@ Symfony\Component\HttpKernel\Profiler\Profiler Symfony\Component\HttpKernel\Profiler\SqliteProfilerStorage - %kernel.cache_dir%/profiler.db + sqlite:%kernel.cache_dir%/profiler.db + + 86400 Symfony\Bundle\FrameworkBundle\Profiler\ProfilerListener false @@ -20,7 +22,9 @@ - %profiler.storage.file% + %profiler.storage.dsn% + %profiler.storage.username% + %profiler.storage.password% %profiler.storage.lifetime% diff --git a/src/Symfony/Component/HttpKernel/Profiler/MysqlProfilerStorage.php b/src/Symfony/Component/HttpKernel/Profiler/MysqlProfilerStorage.php new file mode 100644 index 000000000000..e5dfa6a20596 --- /dev/null +++ b/src/Symfony/Component/HttpKernel/Profiler/MysqlProfilerStorage.php @@ -0,0 +1,45 @@ + + * + * 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 + */ +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; + } +} diff --git a/src/Symfony/Component/HttpKernel/Profiler/PdoProfilerStorage.php b/src/Symfony/Component/HttpKernel/Profiler/PdoProfilerStorage.php new file mode 100644 index 000000000000..5f4a6b12b657 --- /dev/null +++ b/src/Symfony/Component/HttpKernel/Profiler/PdoProfilerStorage.php @@ -0,0 +1,194 @@ + + * + * 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 + */ +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(); + } + } +} diff --git a/src/Symfony/Component/HttpKernel/Profiler/SqliteProfilerStorage.php b/src/Symfony/Component/HttpKernel/Profiler/SqliteProfilerStorage.php index 32bdc66aadd0..f1f1b6a15405 100644 --- a/src/Symfony/Component/HttpKernel/Profiler/SqliteProfilerStorage.php +++ b/src/Symfony/Component/HttpKernel/Profiler/SqliteProfilerStorage.php @@ -18,27 +18,9 @@ * * @author Fabien Potencier */ -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 @@ public function find($ip, $url, $limit) $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 @@ protected function cleanup() */ 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)'); + $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; + $this->db = $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 @@ protected function exec($db, $query, array $args = array()) } $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 @@ protected function fetch($db, $query, array $args = array()) $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; diff --git a/tests/Symfony/Tests/Component/HttpKernel/Profiler/ProfilerTest.php b/tests/Symfony/Tests/Component/HttpKernel/Profiler/ProfilerTest.php index f257e8e145e1..88d1f3a03920 100644 --- a/tests/Symfony/Tests/Component/HttpKernel/Profiler/ProfilerTest.php +++ b/tests/Symfony/Tests/Component/HttpKernel/Profiler/ProfilerTest.php @@ -30,7 +30,7 @@ public function testCollect() if (file_exists($tmp)) { @unlink($tmp); } - $storage = new SqliteProfilerStorage($tmp); + $storage = new SqliteProfilerStorage('sqlite:' . $tmp); $storage->purge(); $profiler = new Profiler($storage); diff --git a/tests/Symfony/Tests/Component/HttpKernel/Profiler/SqliteProfilerStorageTest.php b/tests/Symfony/Tests/Component/HttpKernel/Profiler/SqliteProfilerStorageTest.php index 7e531cfa5465..556d510ae456 100644 --- a/tests/Symfony/Tests/Component/HttpKernel/Profiler/SqliteProfilerStorageTest.php +++ b/tests/Symfony/Tests/Component/HttpKernel/Profiler/SqliteProfilerStorageTest.php @@ -24,7 +24,7 @@ public static function setUpBeforeClass() 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 @@ public function testStoreSpecialCharsInUrl() 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');