Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 182a5d3

Browse files
committed
[HttpFoundation] add create table method to pdo session handler
1 parent e79229d commit 182a5d3

File tree

2 files changed

+140
-51
lines changed

2 files changed

+140
-51
lines changed

src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php

Lines changed: 71 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@
2626
*
2727
* Session data is a binary string that can contain non-printable characters like the null byte.
2828
* For this reason it must be saved in a binary column in the database like BLOB in MySQL.
29-
* Saving it in a character column could corrupt the data.
29+
* Saving it in a character column could corrupt the data. You can use createTable()
30+
* to initialize a correctly defined table.
3031
*
3132
* @see http://php.net/sessionhandlerinterface
3233
*
@@ -158,16 +159,58 @@ public function __construct($pdoOrDsn, array $options = array())
158159
$this->connectionOptions = $options['db_connection_options'];
159160
}
160161

162+
/**
163+
* Creates the table to store sessions which can be called once for setup.
164+
*
165+
* Session ID is saved in a VARCHAR(128) column because that is enough even for
166+
* a 512 bit configured session.hash_function like Whirlpool. Session data is
167+
* saved in a BLOB. One could also use a shorter inlined varbinary column
168+
* if one was sure the data fits into it.
169+
*
170+
* @throws \PDOException When the table already exists
171+
* @throws \DomainException When an unsupported PDO driver is used
172+
*/
173+
public function createTable()
174+
{
175+
// connect if we are not yet
176+
$this->getConnection();
177+
178+
switch ($this->driver) {
179+
case 'mysql':
180+
$sql = "CREATE TABLE $this->table ($this->idCol VARCHAR(128) NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol MEDIUMINT NOT NULL, $this->timeCol INTEGER NOT NULL) ENGINE = InnoDB";
181+
break;
182+
case 'sqlite':
183+
$sql = "CREATE TABLE $this->table ($this->idCol VARCHAR(128) NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol MEDIUMINT NOT NULL, $this->timeCol INTEGER NOT NULL)";
184+
break;
185+
case 'pgsql':
186+
$sql = "CREATE TABLE $this->table ($this->idCol VARCHAR(128) NOT NULL PRIMARY KEY, $this->dataCol BYTEA NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)";
187+
break;
188+
case 'oci':
189+
$sql = "CREATE TABLE $this->table ($this->idCol VARCHAR2(128) NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)";
190+
break;
191+
case 'sqlsrv':
192+
$sql = "CREATE TABLE $this->table ($this->idCol VARCHAR(128) NOT NULL PRIMARY KEY, $this->dataCol VARBINARY(MAX) NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)";
193+
break;
194+
default:
195+
throw new \DomainException(sprintf('"%s" does not currently support PDO driver "%s".', __CLASS__, $this->driver));
196+
}
197+
198+
try {
199+
$this->pdo->exec($sql);
200+
} catch (\PDOException $e) {
201+
$this->rollback();
202+
203+
throw $e;
204+
}
205+
}
206+
161207
/**
162208
* {@inheritdoc}
163209
*/
164210
public function open($savePath, $sessionName)
165211
{
166-
$this->gcCalled = false;
167212
if (null === $this->pdo) {
168-
$this->pdo = new \PDO($this->dsn ?: $savePath, $this->username, $this->password, $this->connectionOptions);
169-
$this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
170-
$this->driver = $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME);
213+
$this->connect($this->dsn ?: $savePath);
171214
}
172215

173216
return true;
@@ -315,6 +358,8 @@ public function close()
315358
$this->commit();
316359

317360
if ($this->gcCalled) {
361+
$this->gcCalled = false;
362+
318363
// delete the session records that have expired
319364
$sql = "DELETE FROM $this->table WHERE $this->lifetimeCol + $this->timeCol < :time";
320365

@@ -330,6 +375,18 @@ public function close()
330375
return true;
331376
}
332377

378+
/**
379+
* Lazy-connects to the database.
380+
*
381+
* @param string $dsn DSN string
382+
*/
383+
private function connect($dsn)
384+
{
385+
$this->pdo = new \PDO($dsn, $this->username, $this->password, $this->connectionOptions);
386+
$this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
387+
$this->driver = $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME);
388+
}
389+
333390
/**
334391
* Helper method to begin a transaction.
335392
*
@@ -399,6 +456,8 @@ private function rollback()
399456
* INSERT when not found can result in a deadlock for one connection.
400457
*
401458
* @param string $sessionId Session ID
459+
*
460+
* @throws \DomainException When an unsupported PDO driver is used
402461
*/
403462
private function lockSession($sessionId)
404463
{
@@ -429,8 +488,10 @@ private function lockSession($sessionId)
429488
$stmt->execute();
430489

431490
return;
491+
case 'sqlite':
492+
return; // we already locked when starting transaction
432493
default:
433-
return;
494+
throw new \DomainException(sprintf('"%s" does not currently support PDO driver "%s".', __CLASS__, $this->driver));
434495
}
435496

436497
// We create a DML lock for the session by inserting empty data or updating the row.
@@ -478,6 +539,10 @@ private function getMergeSql()
478539
*/
479540
protected function getConnection()
480541
{
542+
if (null === $this->pdo) {
543+
$this->connect($this->dsn ?: ini_get('session.save_path'));
544+
}
545+
481546
return $this->pdo;
482547
}
483548
}

src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php

Lines changed: 69 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -15,55 +15,78 @@
1515

1616
class PdoSessionHandlerTest extends \PHPUnit_Framework_TestCase
1717
{
18-
private $pdo;
18+
private $dbFile;
1919

2020
protected function setUp()
2121
{
2222
if (!class_exists('PDO') || !in_array('sqlite', \PDO::getAvailableDrivers())) {
2323
$this->markTestSkipped('This test requires SQLite support in your environment');
2424
}
25+
}
26+
27+
protected function tearDown()
28+
{
29+
// make sure the temporary database file is deleted when it has been created (even when a test fails)
30+
if ($this->dbFile) {
31+
@unlink($this->dbFile);
32+
}
33+
}
34+
35+
protected function getPersistentSqliteDsn()
36+
{
37+
$this->dbFile = tempnam(sys_get_temp_dir(), 'sf2_sqlite_sessions');
2538

26-
$this->pdo = new \PDO('sqlite::memory:');
27-
$this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
28-
$sql = 'CREATE TABLE sessions (sess_id VARCHAR(128) PRIMARY KEY, sess_data BLOB, sess_lifetime MEDIUMINT, sess_time INTEGER)';
29-
$this->pdo->exec($sql);
39+
return 'sqlite:' . $this->dbFile;
40+
}
41+
42+
protected function getMemorySqlitePdo()
43+
{
44+
$pdo = new \PDO('sqlite::memory:');
45+
$pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
46+
$storage = new PdoSessionHandler($pdo);
47+
$storage->createTable();
48+
49+
return $pdo;
3050
}
3151

3252
/**
3353
* @expectedException \InvalidArgumentException
3454
*/
3555
public function testWrongPdoErrMode()
3656
{
37-
$this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_SILENT);
57+
$pdo = $this->getMemorySqlitePdo();
58+
$pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_SILENT);
3859

39-
$storage = new PdoSessionHandler($this->pdo);
60+
$storage = new PdoSessionHandler($pdo);
4061
}
4162

4263
/**
4364
* @expectedException \RuntimeException
4465
*/
4566
public function testInexistentTable()
4667
{
47-
$storage = new PdoSessionHandler($this->pdo, array('db_table' => 'inexistent_table'));
68+
$storage = new PdoSessionHandler($this->getMemorySqlitePdo(), array('db_table' => 'inexistent_table'));
4869
$storage->open('', 'sid');
4970
$storage->read('id');
5071
$storage->write('id', 'data');
5172
$storage->close();
5273
}
5374

54-
public function testWithLazyDnsConnection()
75+
/**
76+
* @expectedException \RuntimeException
77+
*/
78+
public function testCreateTableTwice()
5579
{
56-
$dbFile = tempnam(sys_get_temp_dir(), 'sf2_sqlite_sessions');
57-
if (file_exists($dbFile)) {
58-
@unlink($dbFile);
59-
}
80+
$storage = new PdoSessionHandler($this->getMemorySqlitePdo());
81+
$storage->createTable();
82+
}
6083

61-
$pdo = new \PDO('sqlite:' . $dbFile);
62-
$sql = 'CREATE TABLE sessions (sess_id VARCHAR(128) PRIMARY KEY, sess_data BLOB, sess_lifetime MEDIUMINT, sess_time INTEGER)';
63-
$pdo->exec($sql);
64-
$pdo = null;
84+
public function testWithLazyDsnConnection()
85+
{
86+
$dsn = $this->getPersistentSqliteDsn();
6587

66-
$storage = new PdoSessionHandler('sqlite:' . $dbFile);
88+
$storage = new PdoSessionHandler($dsn);
89+
$storage->createTable();
6790
$storage->open('', 'sid');
6891
$data = $storage->read('id');
6992
$storage->write('id', 'data');
@@ -74,43 +97,32 @@ public function testWithLazyDnsConnection()
7497
$data = $storage->read('id');
7598
$storage->close();
7699
$this->assertSame('data', $data, 'Written value can be read back correctly');
77-
78-
@unlink($dbFile);
79100
}
80101

81102
public function testWithLazySavePathConnection()
82103
{
83-
$dbFile = tempnam(sys_get_temp_dir(), 'sf2_sqlite_sessions');
84-
if (file_exists($dbFile)) {
85-
@unlink($dbFile);
86-
}
104+
$dsn = $this->getPersistentSqliteDsn();
87105

88-
$pdo = new \PDO('sqlite:' . $dbFile);
89-
$sql = 'CREATE TABLE sessions (sess_id VARCHAR(128) PRIMARY KEY, sess_data BLOB, sess_lifetime MEDIUMINT, sess_time INTEGER)';
90-
$pdo->exec($sql);
91-
$pdo = null;
92-
93-
// Open is called with what ini_set('session.save_path', 'sqlite:' . $dbFile) would mean
106+
// Open is called with what ini_set('session.save_path', $dsn) would mean
94107
$storage = new PdoSessionHandler(null);
95-
$storage->open('sqlite:' . $dbFile, 'sid');
108+
$storage->open($dsn, 'sid');
109+
$storage->createTable();
96110
$data = $storage->read('id');
97111
$storage->write('id', 'data');
98112
$storage->close();
99113
$this->assertSame('', $data, 'New session returns empty string data');
100114

101-
$storage->open('sqlite:' . $dbFile, 'sid');
115+
$storage->open($dsn, 'sid');
102116
$data = $storage->read('id');
103117
$storage->close();
104118
$this->assertSame('data', $data, 'Written value can be read back correctly');
105-
106-
@unlink($dbFile);
107119
}
108120

109121
public function testReadWriteReadWithNullByte()
110122
{
111123
$sessionData = 'da' . "\0" . 'ta';
112124

113-
$storage = new PdoSessionHandler($this->pdo);
125+
$storage = new PdoSessionHandler($this->getMemorySqlitePdo());
114126
$storage->open('', 'sid');
115127
$readData = $storage->read('id');
116128
$storage->write('id', $sessionData);
@@ -128,7 +140,7 @@ public function testReadWriteReadWithNullByte()
128140
*/
129141
public function testWriteDifferentSessionIdThanRead()
130142
{
131-
$storage = new PdoSessionHandler($this->pdo);
143+
$storage = new PdoSessionHandler($this->getMemorySqlitePdo());
132144
$storage->open('', 'sid');
133145
$storage->read('id');
134146
$storage->destroy('id');
@@ -139,13 +151,13 @@ public function testWriteDifferentSessionIdThanRead()
139151
$data = $storage->read('new_id');
140152
$storage->close();
141153

142-
$this->assertSame('data_of_new_session_id', $data, 'Data of regenerated session id is available');
154+
$this->assertSame('data_of_new_session_id', $data, 'Data of regenerated session id is available');
143155
}
144156

145157
public function testWrongUsageStillWorks()
146158
{
147159
// wrong method sequence that should no happen, but still works
148-
$storage = new PdoSessionHandler($this->pdo);
160+
$storage = new PdoSessionHandler($this->getMemorySqlitePdo());
149161
$storage->write('id', 'data');
150162
$storage->write('other_id', 'other_data');
151163
$storage->destroy('inexistent');
@@ -160,19 +172,20 @@ public function testWrongUsageStillWorks()
160172

161173
public function testSessionDestroy()
162174
{
163-
$storage = new PdoSessionHandler($this->pdo);
175+
$pdo = $this->getMemorySqlitePdo();
176+
$storage = new PdoSessionHandler($pdo);
164177

165178
$storage->open('', 'sid');
166179
$storage->read('id');
167180
$storage->write('id', 'data');
168181
$storage->close();
169-
$this->assertEquals(1, $this->pdo->query('SELECT COUNT(*) FROM sessions')->fetchColumn());
182+
$this->assertEquals(1, $pdo->query('SELECT COUNT(*) FROM sessions')->fetchColumn());
170183

171184
$storage->open('', 'sid');
172185
$storage->read('id');
173186
$storage->destroy('id');
174187
$storage->close();
175-
$this->assertEquals(0, $this->pdo->query('SELECT COUNT(*) FROM sessions')->fetchColumn());
188+
$this->assertEquals(0, $pdo->query('SELECT COUNT(*) FROM sessions')->fetchColumn());
176189

177190
$storage->open('', 'sid');
178191
$data = $storage->read('id');
@@ -183,7 +196,8 @@ public function testSessionDestroy()
183196
public function testSessionGC()
184197
{
185198
$previousLifeTime = ini_set('session.gc_maxlifetime', 1000);
186-
$storage = new PdoSessionHandler($this->pdo);
199+
$pdo = $this->getMemorySqlitePdo();
200+
$storage = new PdoSessionHandler($pdo);
187201

188202
$storage->open('', 'sid');
189203
$storage->read('id');
@@ -195,7 +209,7 @@ public function testSessionGC()
195209
ini_set('session.gc_maxlifetime', -1); // test that you can set lifetime of a session after it has been read
196210
$storage->write('gc_id', 'data');
197211
$storage->close();
198-
$this->assertEquals(2, $this->pdo->query('SELECT COUNT(*) FROM sessions')->fetchColumn(), 'No session pruned because gc not called');
212+
$this->assertEquals(2, $pdo->query('SELECT COUNT(*) FROM sessions')->fetchColumn(), 'No session pruned because gc not called');
199213

200214
$storage->open('', 'sid');
201215
$data = $storage->read('gc_id');
@@ -205,12 +219,22 @@ public function testSessionGC()
205219
ini_set('session.gc_maxlifetime', $previousLifeTime);
206220

207221
$this->assertSame('', $data, 'Session already considered garbage, so not returning data even if it is not pruned yet');
208-
$this->assertEquals(1, $this->pdo->query('SELECT COUNT(*) FROM sessions')->fetchColumn(), 'Expired session is pruned');
222+
$this->assertEquals(1, $pdo->query('SELECT COUNT(*) FROM sessions')->fetchColumn(), 'Expired session is pruned');
209223
}
210224

211225
public function testGetConnection()
212226
{
213-
$storage = new PdoSessionHandler($this->pdo);
227+
$storage = new PdoSessionHandler($this->getMemorySqlitePdo());
228+
229+
$method = new \ReflectionMethod($storage, 'getConnection');
230+
$method->setAccessible(true);
231+
232+
$this->assertInstanceOf('\PDO', $method->invoke($storage));
233+
}
234+
235+
public function testGetConnectionConnectsIfNeeded()
236+
{
237+
$storage = new PdoSessionHandler('sqlite::memory:');
214238

215239
$method = new \ReflectionMethod($storage, 'getConnection');
216240
$method->setAccessible(true);

0 commit comments

Comments
 (0)