From a1888b2f01745fb0f31ccff5fec3440e5f6c8252 Mon Sep 17 00:00:00 2001 From: Johannes Schmitt Date: Wed, 14 Sep 2011 15:07:01 +0200 Subject: [PATCH] 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 0000000000..912ddf8059 --- /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 0000000000..09882a4fff --- /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