8000 merged branch schmittjoh/dbalSessionStorage (PR #2182) · fh-github/symfony@b25a876 · GitHub
[go: up one dir, main page]

Skip to content

Commit b25a876

Browse files
committed
merged branch schmittjoh/dbalSessionStorage (PR symfony#2182)
Commits ------- 3f8e8c9 fixes a session max lifetime handling 3abb7f3 fixed some conflicts with garbage collection a1888b2 added a dbal session storage Discussion ---------- Dbal session storage Adds a session storage based on Doctrine DBAL. --------------------------------------------------------------------------- by lsmith77 at 2011/09/14 13:39:28 -0700 guess it would be nice to then provide a service inside the DoctrineBundle that reuses a global DoctrineDBAL connection, guess the connection to use would then need to be configured via the doctrine app config. --------------------------------------------------------------------------- by schmittjoh at 2011/09/14 13:42:34 -0700 I haven't found a sane way to provide automatic configuration that's why I left this to the user to implement. It's also relatively easy: ```yml services: dbal_session_storage: class: Symfony\Bridge\Doctrine\HttpFoundation\DbalSessionStorage arguments: [@database_connection] framework: session: { storage_id: dbal_session_storage } --------------------------------------------------------------------------- by stof at 2011/09/14 13:57:48 -0700 @lsmith77 There is an issue about reusing another DBAL connection: if the transaction is aborted by the ORM, the session could be aborted too as you cannot have 2 independent transactions AFAIK --------------------------------------------------------------------------- by lsmith77 at 2011/09/14 13:59:57 -0700 not sure how this is relevant. i mean why does the transaction need to be left open? just begin, write, commit .. --------------------------------------------------------------------------- by stof at 2011/09/14 14:02:39 -0700 and what if the transaction of the ORM is still opened (let's say you use SimpleThingsTransactionalBundle) ? --------------------------------------------------------------------------- by lsmith77 at 2011/09/14 14:06:12 -0700 well thats a bit of an edge case imho. also i wonder if SimpleThingsTransactionalBundle shouldn't make sure that its transaction is closed before the session is written. --------------------------------------------------------------------------- by schmittjoh at 2011/09/14 14:06:56 -0700 It closes them. On Wed, Sep 14, 2011 at 11:06 PM, Lukas Kahwe Smith < reply@reply.github.com>wrote: > well thats a bit of an edge case imho. also i wonder if > SimpleThingsTransactionalBundle shouldn't make sure that its transaction is > closed before the session is written. > > -- > Reply to this email directly or view it on GitHub: > symfony#2182 (comment) > --------------------------------------------------------------------------- by stof at 2011/09/14 14:15:02 -0700 @schmittjoh Does it close them **before** writing the session ? --------------------------------------------------------------------------- by schmittjoh at 2011/09/14 14:44:15 -0700 I think either commit, or rollback is called, but @beberlei can probably answer that better. Anyway, it is not really related to this PR because it is up to the user which connection is used. --------------------------------------------------------------------------- by stof at 2011/09/14 14:58:48 -0700 I know that one of them is called. But if they are called after the session is persisted to the DB, a rollback is an issue as it will rollback the session persistence as well. The PR is indeed fine. What need to be changed is the doc about how to use it, to advocate using a separate connection instead of the default one (which is used in your example) --------------------------------------------------------------------------- by schmittjoh at 2011/09/15 02:57:34 -0700 There is no doc yet, but lets see what @fabpot thinks of all of this first. --------------------------------------------------------------------------- by fabpot at 2011/09/15 04:57:57 -0700 Any benefits over using the PDO session storage? --------------------------------------------------------------------------- by lsmith77 at 2011/09/15 05:00:50 -0700 cleaner code, potentially better support for niche RDBMS, centralized logging and finally afaik DoctrineDBAL has emulation for nested transactions. --------------------------------------------------------------------------- by schmittjoh at 2011/09/15 05:11:00 -0700 The benefits (for me) are: - logging queries - better interoperability with existing build processes (migrations) - better database interoperability - re-using existing connection (I don't have the problem that Stof mentioned above) --------------------------------------------------------------------------- by beberlei at 2011/09/15 06:18:22 -0700 The nested transactions is the problem here as @stof already said. If you reuse the default connection and use nested transactions that fail then this will make your session not save. That is why the docs should recommend you create a second connection for a dbal based session storage, even if it is using the same database. The PDO session storage would be a second connection besides DBAL aswell. I like the migrations/schema hook though to create the table automatically through schema commands. --------------------------------------------------------------------------- by fabpot at 2011/09/15 10:45:31 -0700 ok, looks good to me. Can you add some documentation about its usage (like the possible keys for options)? Is it possible to add some tests too? --------------------------------------------------------------------------- by jdreesen at 2011/09/22 06:34:11 -0700 why did you close this? --------------------------------------------------------------------------- by schmittjoh at 2011/09/30 06:26:12 -0700 I can't put more time into this PR, however I'm using it for some time already, and there shouldn't be any major issues as it is basically copy/paste from the PDO session.
2 parents 89fd965 + 3f8e8c9 commit b25a876

File tree

2 files changed

+244
-0
lines changed

2 files changed

+244
-0
lines changed
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
<?php
2+
3+
namespace Symfony\Bridge\Doctrine\HttpFoundation;
4+
5+
use Doctrine\DBAL\Platforms\MySqlPlatform;
6+
use Symfony\Component\HttpFoundation\SessionStorage\NativeSessionStorage;
7+
use Doctrine\DBAL\Driver\Connection;
8+
9+
/**
10+
* DBAL based session storage.
11+
*
12+
* @author Fabien Potencier <fabien@symfony.com>
13+
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
14+
*/
15+
class DbalSessionStorage extends NativeSessionStorage
16+
{
17+
private $con;
18+
private $tableName;
19+
20+
public function __construct(Connection $con, $tableName = 'sessions', array $options = array())
21+
{
22+
parent::__construct($options);
23+
24+
$this->con = $con;
25+
$this->tableName = $tableName;
26+
}
27+
28+
/**
29+
* Starts the session.
30+
*/
31+
public function start()
32+
{
33+
if (self::$sessionStarted) {
34+
return;
35+
}
36+
37+
// use this object as the session handler
38+
session_set_save_handler(
39+
array($this, 'sessionOpen'),
40+
array($this, 'sessionClose'),
41+
array($this, 'sessionRead'),
42+
array($this, 'sessionWrite'),
43+
array($this, 'sessionDestroy'),
44+
array($this, 'sessionGC')
45+
);
46+
47+
parent::start();
48+
}
49+
50+
/**
51+
* Opens a session.
52+
*
53+
* @param string $path (ignored)
54+
* @param string $name (ignored)
55+
*
56+
* @return Boolean true, if the session was opened, otherwise an exception is thrown
57+
*/
58+
public function sessionOpen($path = null, $name = null)
59+
{
60+
return true;
61+
}
62+
63+
/**
64+
* Closes a session.
65+
*
66+
* @return Boolean true, if the session was closed, otherwise false
67+
*/
68+
public function sessionClose()
69+
{
70+
// do nothing
71+
return true;
72+
}
73+
74+
/**
75+
* Destroys a session.
76+
*
77+
* @param string $id A session ID
78+
*
79+
* @return Boolean true, if the session was destroyed, otherwise an exception is thrown
80+
*
81+
* @throws \RuntimeException If the session cannot be destroyed
82+
*/
83+
public function sessionDestroy($id)
84+
{
85+
try {
86+
$this->con->executeQuery("DELETE FROM {$this->tableName} WHERE sess_id = :id", array(
87+
'id' => $id,
88+
));
89+
} catch (\PDOException $e) {
90+
throw new \RuntimeException(sprintf('PDOException was thrown when trying to manipulate session data: %s', $e->getMessage()), 0, $e);
91+
}
92+
93+
return true;
94+
}
95+
96+
/**
97+
* Cleans up old sessions.
98+
*
99+
* @param int $lifetime The lifetime of a session
100+
*
101+
* @return Boolean true, if old sessions have been cleaned, otherwise an exception is thrown
102+
*
103+
* @throws \RuntimeException If any old sessions cannot be cleaned
104+
*/
105+
public function sessionGC($lifetime)
106+
{
107+
try {
108+
$this->con->executeQuery("DELETE FROM {$this->tableName} WHERE sess_time < :time", array(
109+
'time' => time() - $lifetime,
110+
));
111+
} catch (\PDOException $e) {
112+
throw new \RuntimeException(sprintf('PDOException was thrown when trying to manipulate session data: %s', $e->getMessage()), 0, $e);
113+
}
114+
115+
return true;
116+
}
117+
118+
/**
119+
* Reads a session.
120+
*
121+
* @param string $id A session ID
122+
*
123+
* @return string The session data if the session was read or created, otherwise an exception is thrown
124+
*
125+
* @throws \RuntimeException If the session cannot be read
126+
*/
127+
public function sessionRead($id)
128+
{
129+
try {
130+
$data = $this->con->executeQuery("SELECT sess_data FROM {$this->tableName} WHERE sess_id = :id", array(
131+
'id' => $id,
132+
))->fetchColumn();
133+
134+
if (false !== $data) {
135+
return $data;
136+
}
137+
138+
// session does not exist, create it
139+
$this->createNewSession($id);
140+
141+
return '';
142+
} catch (\PDOException $e) {
143+
throw new \RuntimeException(sprintf('PDOException was thrown when trying to manipulate session data: %s', $e->getMessage()), 0, $e);
144+
}
145+
}
146+
147+
/**
148+
* Writes session data.
149+
*
150+
* @param string $id A session ID
151+
* @param string $data A serialized chunk of session data
152+
*
153+
* @return Boolean true, if the session was written, otherwise an exception is thrown
154+
*
155+
* @throws \RuntimeException If the session data cannot be written
156+
*/
157+
public function sessionWrite($id, $data)
158+
{
159+
$platform = $this->con->getDatabasePlatform();
160+
161+
// this should maybe be abstracted in Doctrine DBAL
162+
if ($platform instanceof MySqlPlatform) {
163+
$sql = "INSERT INTO {$this->tableName} (sess_id, sess_data, sess_time) VALUES (%1\$s, %2\$s, %3\$d) "
164+
."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";
165+
} else {
166+
$sql = "UPDATE {$this->tableName} SET sess_data = %2\$s, sess_time = %3\$d WHERE sess_id = %1\$s";
167+
}
168+
169+
try {
170+
$rowCount = $this->con->exec(sprintf(
171+
$sql,
172+
$this->con->quote($id),
173+
$this->con->quote($data),
174+
time()
175+
));
176+
177+
if (!$rowCount) {
178+
// No session exists in the database to update. This happens when we have called
179+
// session_regenerate_id()
180+
$this->createNewSession($id, $data);
181+
}
182+
} catch (\PDOException $e) {
183+
throw new \RuntimeException(sprintf('PDOException was thrown when trying to manipulate session data: %s', $e->getMessage()), 0, $e);
184+
}
185+
186+
return true;
187+
}
188+
189+
/**
190+
* Creates a new session with the given $id and $data
191+
*
192+
* @param string $id
193+
* @param string $data
194+
*/
195+
private function createNewSession($id, $data = '')
196+
{
197+
$this->con->exec(sprintf("INSERT INTO {$this->tableName} (sess_id, sess_data, sess_time) VALUES (%s, %s, %d)",
198+
$this->con->quote($id),
199+
$this->con->quote($data),
200+
time()
201+
));
202+
203+
return true;
204+
}
205+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
namespace Symfony\Bridge\Doctrine\HttpFoundation;
4+
5+
use Doctrine\DBAL\Schema\Schema;
6+
7+
/**
8+
* DBAL Session Storage Schema.
9+
*
10+
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
11+
*/
12+
final class DbalSessionStorageSchema extends Schema
13+
{
14+
private $tableName;
15+
16+
public function __construct($tableName = 'sessions')
17+
{
18+
parent::__construct();
19+
20+
$this->tableName = $tableName;
21+
$this->addSessionTable();
22+
}
23+
24+
public function addToSchema(Schema $schema)
25+
{
26+
foreach ($this->getTables() as $table) {
27+
$schema->_addTable($table);
28+
}
29+
}
30+
31+
private function addSessionTable()
32+
{
33+
$table = $this->createTable($this->tableName);
34+
$table->addColumn('sess_id', 'string');
35+
$table->addColumn('sess_data', 'text')->setNotNull(true);
36+
$table->addColumn('sess_time', 'integer')->setNotNull(true)->setUnsigned(true);
37+
$table->setPrimaryKey(array('sess_id'));
38+
}
39+
}

0 commit comments

Comments
 (0)
0