浏览代码

- Added abstract PDO profiler storage, updated sqlite storage and added a mysql storage.
- Updated profiler config in framework bundle

Jan Schumann 14 年之前
父节点
当前提交
d1ebc8da9f

+ 6 - 2
src/Symfony/Bundle/FrameworkBundle/Resources/config/profiling.xml

@@ -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>
 

+ 45 - 0
src/Symfony/Component/HttpKernel/Profiler/MysqlProfilerStorage.php

@@ -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;
+    }
+}

+ 194 - 0
src/Symfony/Component/HttpKernel/Profiler/PdoProfilerStorage.php

@@ -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();
+        }
+    }
+}

+ 28 - 120
src/Symfony/Component/HttpKernel/Profiler/SqliteProfilerStorage.php

@@ -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)');
+            $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 @@ 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;

+ 1 - 1
tests/Symfony/Tests/Component/HttpKernel/Profiler/ProfilerTest.php

@@ -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);

+ 2 - 2
tests/Symfony/Tests/Component/HttpKernel/Profiler/SqliteProfilerStorageTest.php

@@ -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');