From a1888b2f01745fb0f31ccff5fec3440e5f6c8252 Mon Sep 17 00:00:00 2001 From: Johannes Schmitt Date: Wed, 14 Sep 2011 15:07:01 +0200 Subject: [PATCH 1/3] added a dbal session storage --- .../HttpFoundation/DbalSessionStorage.php | 205 ++++++++++++++++++ .../DbalSessionStorageSchema.php | 39 ++++ 2 files changed, 244 insertions(+) create mode 100644 src/Symfony/Bridge/Doctrine/HttpFoundation/DbalSessionStorage.php create mode 100644 src/Symfony/Bridge/Doctrine/HttpFoundation/DbalSessionStorageSchema.php diff --git a/src/Symfony/Bridge/Doctrine/HttpFoundation/DbalSessionStorage.php b/src/Symfony/Bridge/Doctrine/HttpFoundation/DbalSessionStorage.php new file mode 100644 index 0000000000000..912ddf8059f6a --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/HttpFoundation/DbalSessionStorage.php @@ -0,0 +1,205 @@ + + * @author Johannes M. Schmitt + */ +class DbalSessionStorage extends NativeSessionStorage +{ + private $con; + private $tableName; + + public function __construct(Connection $con, $tableName = 'sessions', array $options = array()) + { + parent::__construct($options); + + $this->con = $con; + $this->tableName = $tableName; + } + + /** + * Starts the session. + */ + public function start() + { + if (self::$sessionStarted) { + return; + } + + // use this object as the session handler + session_set_save_handler( + array($this, 'sessionOpen'), + array($this, 'sessionClose'), + array($this, 'sessionRead'), + array($this, 'sessionWrite'), + array($this, 'sessionDestroy'), + array($this, 'sessionGC') + ); + + parent::start(); + } + + /** + * Opens a session. + * + * @param string $path (ignored) + * @param string $name (ignored) + * + * @return Boolean true, if the session was opened, otherwise an exception is thrown + */ + public function sessionOpen($path = null, $name = null) + { + return true; + } + + /** + * Closes a session. + * + * @return Boolean true, if the session was closed, otherwise false + */ + public function sessionClose() + { + // do nothing + return true; + } + + /** + * Destroys a session. + * + * @param string $id A session ID + * + * @return Boolean true, if the session was destroyed, otherwise an exception is thrown + * + * @throws \RuntimeException If the session cannot be destroyed + */ + public function sessionDestroy($id) + { + try { + $this->con->executeQuery("DELETE FROM {$this->tableName} WHERE sess_id = :id", array( + 'id' => $id, + )); + } catch (\PDOException $e) { + throw new \RuntimeException(sprintf('PDOException was thrown when trying to manipulate session data: %s', $e->getMessage()), 0, $e); + } + + return true; + } + + /** + * Cleans up old sessions. + * + * @param int $lifetime The lifetime of a session + * + * @return Boolean true, if old sessions have been cleaned, otherwise an exception is thrown + * + * @throws \RuntimeException If any old sessions cannot be cleaned + */ + public function sessionGC($lifetime) + { + try { + $this->con->executeQuery("DELETE FROM {$this->tableName} WHERE sess_time < :time", array( + 'time' => time() - $this->options['lifetime'], + )); + } catch (\PDOException $e) { + throw new \RuntimeException(sprintf('PDOException was thrown when trying to manipulate session data: %s', $e->getMessage()), 0, $e); + } + + return true; + } + + /** + * Reads a session. + * + * @param string $id A session ID + * + * @return string The session data if the session was read or created, otherwise an exception is thrown + * + * @throws \RuntimeException If the session cannot be read + */ + public function sessionRead($id) + { + try { + $data = $this->con->executeQuery("SELECT sess_data FROM {$this->tableName} WHERE sess_id = :id", array( + 'id' => $id, + ))->fetchColumn(); + + if (false !== $data) { + return $data; + } + + // session does not exist, create it + $this->createNewSession($id); + + return ''; + } catch (\PDOException $e) { + throw new \RuntimeException(sprintf('PDOException was thrown when trying to manipulate session data: %s', $e->getMessage()), 0, $e); + } + } + + /** + * Writes session data. + * + * @param string $id A session ID + * @param string $data A serialized chunk of session data + * + * @return Boolean true, if the session was written, otherwise an exception is thrown + * + * @throws \RuntimeException If the session data cannot be written + */ + public function sessionWrite($id, $data) + { + $platform = $this->con->getDatabasePlatform(); + + // this should maybe be abstracted in Doctrine DBAL + if ($platform instanceof MySqlPlatform) { + $sql = "INSERT INTO {$this->tableName} (sess_id, sess_data, sess_time) VALUES (:id, :data, :time) " + ."ON DUPLICATE KEY UPDATE sess_data = VALUES(sess_data), sess_time = CASE WHEN sess_time = :time THEN (VALUES(sess_time) + 1) ELSE VALUES(sess_time) END"; + } else { + $sql = "UPDATE {$this->tableName} SET sess_data = :data, sess_time = :time WHERE sess_id = :id"; + } + + try { + $stmt = $this->con->executeQuery($sql, array( + 'id' => $id, + 'data' => $data, + 'time' => time(), + )); + + if (!$stmt->rowCount()) { + // No session exists in the database to update. This happens when we have called + // session_regenerate_id() + $this->createNewSession($id, $data); + } + } catch (\PDOException $e) { + throw new \RuntimeException(sprintf('PDOException was thrown when trying to manipulate session data: %s', $e->getMessage()), 0, $e); + } + + return true; + } + + /** + * Creates a new session with the given $id and $data + * + * @param string $id + * @param string $data + */ + private function createNewSession($id, $data = '') + { + $this->con->executeQuery("INSERT INTO {$this->tableName} (sess_id, sess_data, sess_time) VALUES (:id, :data, :time)", array( + 'id' => $id, + 'data' => $data, + 'time' => time(), + )); + + return true; + } +} \ No newline at end of file diff --git a/src/Symfony/Bridge/Doctrine/HttpFoundation/DbalSessionStorageSchema.php b/src/Symfony/Bridge/Doctrine/HttpFoundation/DbalSessionStorageSchema.php new file mode 100644 index 0000000000000..09882a4fff660 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/HttpFoundation/DbalSessionStorageSchema.php @@ -0,0 +1,39 @@ + + */ +final class DbalSessionStorageSchema extends Schema +{ + private $tableName; + + public function __construct($tableName = 'sessions') + { + parent::__construct(); + + $this->tableName = $tableName; + $this->addSessionTable(); + } + + public function addToSchema(Schema $schema) + { + foreach ($this->getTables() as $table) { + $schema->_addTable($table); + } + } + + private function addSessionTable() + { + $table = $this->createTable($this->tableName); + $table->addColumn('sess_id', 'string'); + $table->addColumn('sess_data', 'text')->setNotNull(true); + $table->addColumn('sess_time', 'integer')->setNotNull(true)->setUnsigned(true); + $table->setPrimaryKey(array('sess_id')); + } +} \ No newline at end of file From 3abb7f3a69a8e21599b302e6ded730b388a7426a Mon Sep 17 00:00:00 2001 From: Johannes Schmitt Date: Wed, 14 Sep 2011 16:51:40 +0200 Subject: [PATCH 2/3] fixed some conflicts with garbage collection --- .../HttpFoundation/DbalSessionStorage.php | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/Symfony/Bridge/Doctrine/HttpFoundation/DbalSessionStorage.php b/src/Symfony/Bridge/Doctrine/HttpFoundation/DbalSessionStorage.php index 912ddf8059f6a..95310ac34fb28 100644 --- a/src/Symfony/Bridge/Doctrine/HttpFoundation/DbalSessionStorage.php +++ b/src/Symfony/Bridge/Doctrine/HttpFoundation/DbalSessionStorage.php @@ -161,20 +161,21 @@ public function sessionWrite($id, $data) // this should maybe be abstracted in Doctrine DBAL if ($platform instanceof MySqlPlatform) { - $sql = "INSERT INTO {$this->tableName} (sess_id, sess_data, sess_time) VALUES (:id, :data, :time) " - ."ON DUPLICATE KEY UPDATE sess_data = VALUES(sess_data), sess_time = CASE WHEN sess_time = :time THEN (VALUES(sess_time) + 1) ELSE VALUES(sess_time) END"; + $sql = "INSERT INTO {$this->tableName} (sess_id, sess_data, sess_time) VALUES (%1\$s, %2\$s, %3\$d) " + ."ON DUPLICATE KEY UPDATE sess_data = VALUES(sess_data), sess_time = CASE WHEN sess_time = %3\$d THEN (VALUES(sess_time) + 1) ELSE VALUES(sess_time) END"; } else { - $sql = "UPDATE {$this->tableName} SET sess_data = :data, sess_time = :time WHERE sess_id = :id"; + $sql = "UPDATE {$this->tableName} SET sess_data = %2\$s, sess_time = %3\$d WHERE sess_id = %1\$s"; } try { - $stmt = $this->con->executeQuery($sql, array( - 'id' => $id, - 'data' => $data, - 'time' => time(), + $rowCount = $this->con->exec(sprintf( + $sql, + $this->con->quote($id), + $this->con->quote($data), + time() )); - if (!$stmt->rowCount()) { + if (!$rowCount) { // No session exists in the database to update. This happens when we have called // session_regenerate_id() $this->createNewSession($id, $data); @@ -194,10 +195,10 @@ public function sessionWrite($id, $data) */ private function createNewSession($id, $data = '') { - $this->con->executeQuery("INSERT INTO {$this->tableName} (sess_id, sess_data, sess_time) VALUES (:id, :data, :time)", array( - 'id' => $id, - 'data' => $data, - 'time' => time(), + $this->con->exec(sprintf("INSERT INTO {$this->tableName} (sess_id, sess_data, sess_time) VALUES (%s, %s, %d)", + $this->con->quote($id), + $this->con->quote($data), + time() )); return true; From 3f8e8c9fbda9e6b64b3dc87d7c34ee8a13b2a965 Mon Sep 17 00:00:00 2001 From: Johannes Schmitt Date: Mon, 19 Sep 2011 23:16:57 +0200 Subject: [PATCH 3/3] fixes a session max lifetime handling --- .../Bridge/Doctrine/HttpFoundation/DbalSessionStorage.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Symfony/Bridge/Doctrine/HttpFoundation/DbalSessionStorage.php b/src/Symfony/Bridge/Doctrine/HttpFoundation/DbalSessionStorage.php index 95310ac34fb28..8faf528a7cbef 100644 --- a/src/Symfony/Bridge/Doctrine/HttpFoundation/DbalSessionStorage.php +++ b/src/Symfony/Bridge/Doctrine/HttpFoundation/DbalSessionStorage.php @@ -3,7 +3,6 @@ namespace Symfony\Bridge\Doctrine\HttpFoundation; use Doctrine\DBAL\Platforms\MySqlPlatform; - use Symfony\Component\HttpFoundation\SessionStorage\NativeSessionStorage; use Doctrine\DBAL\Driver\Connection; @@ -107,7 +106,7 @@ public function sessionGC($lifetime) { try { $this->con->executeQuery("DELETE FROM {$this->tableName} WHERE sess_time < :time", array( - 'time' => time() - $this->options['lifetime'], + 'time' => time() - $lifetime, )); } catch (\PDOException $e) { throw new \RuntimeException(sprintf('PDOException was thrown when trying to manipulate session data: %s', $e->getMessage()), 0, $e);