8000 [HttpFoundation] implement lazy connect for pdo session handler · symfony/symfony@251238d · GitHub
[go: up one dir, main page]

Skip to content

Commit 251238d

Browse files
committed
10000
[HttpFoundation] implement lazy connect for pdo session handler
1 parent 7dad54c commit 251238d

File tree

2 files changed

+127
-20
lines changed

2 files changed

+127
-20
lines changed

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

Lines changed: 59 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,15 @@
3737
class PdoSessionHandler implements \SessionHandlerInterface
3838
{
3939
/**
40-
* @var \PDO PDO instance
40+
* @var \PDO|null PDO instance or null when not connected yet
4141
*/
4242
private $pdo;
4343

44+
/**
45+
* @var string|null|false DNS string or null for session.save_path or false when lazy connection disabled
46+
*/
47+
private $dns = false;
48+
4449
/**
4550
* @var string Database driver
4651
*/
@@ -66,6 +71,21 @@ class PdoSessionHandler implements \SessionHandlerInterface
6671
*/
6772
private $timeCol;
6873

74+
/**
75+
* @var string Username when lazy-connect
76+
*/
77+
private $username;
78+
79+
/**
80+
* @var string Password when lazy-connect
81+
*/
82+
private $password;
83+
84+
/**
85+
* @var array Connection options when lazy-connect
86+
*/
87+
private $connectionOptions = array();
88+
6989
/**
7090
* @var bool Whether a transaction is active
7191
*/
@@ -79,37 +99,54 @@ class PdoSessionHandler implements \SessionHandlerInterface
7999
/**
80100
* Constructor.
81101
*
102+
* You can either pass an existing database connection as PDO instance or
103+
* pass a DNS string that will be used to lazy-connect to the database
104+
* when the session is actually used. Furthermore it's possible to pass null
105+
* which will then use the session.save_path ini setting as PDO DNS parameter.
106+
*
82107
* List of available options:
83108
* * db_table: The name of the table [default: sessions]
84109
* * db_id_col: The column where to store the session id [default: sess_id]
85110
* * db_data_col: The column where to store the session data [default: sess_data]
86111
* * db_time_col: The column where to store the timestamp [default: sess_time]
112+
* * db_username: The username when lazy-connect [default: '']
113+
* * db_password: The password when lazy-connect [default: '']
114+
* * db_connection_options: An array of driver-specific connection options [default: array()]
87115
*
88-
* @param \PDO $pdo A \PDO instance
89-
* @param array $options An associative array of DB options
116+
* @param \PDO|string|null $pdoOrDns A \PDO instance or DNS string or null
117+
* @param array $options An associative array of DB options
90118
*
91119
* @throws \InvalidArgumentException When PDO error mode is not PDO::ERRMODE_EXCEPTION
92120
*/
93-
public function __construct(\PDO $pdo, array $options = array())
121+
public function __construct($pdoOrDns, array $options = array())
94122
{
95-
if (\PDO::ERRMODE_EXCEPTION !== $pdo->getAttribute(\PDO::ATTR_ERRMODE)) {
96-
throw new \InvalidArgumentException(sprintf('"%s" requires PDO error mode attribute be set to throw Exceptions (i.e. $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION))', __CLASS__));
97-
}
123+
if ($pdoOrDns instanceof \PDO) {
124+
if (\PDO::ERRMODE_EXCEPTION !== $pdoOrDns->getAttribute(\PDO::ATTR_ERRMODE)) {
125+
throw new \InvalidArgumentException(sprintf('"%s" requires PDO error mode attribute be set to throw Exceptions (i.e. $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION))', __CLASS__));
126+
}
98127

99-
$this->pdo = $pdo;
100-
$this->driver = $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME);
128+
$this->pdo = $pdoOrDns;
129+
} else {
130+
$this->dns = $pdoOrDns;
131+
}
101132

102133
$options = array_replace(array(
103-
'db_table' => 'sessions',
104-
'db_id_col' => 'sess_id',
105-
'db_data_col' => 'sess_data',
106-
'db_time_col' => 'sess_time',
134+
'db_table' => 'sessions',
135+
'db_id_col' => 'sess_id',
136+
'db_data_col' => 'sess_data',
137+
'db_time_col' => 'sess_time',
138+
'db_username' => '',
139+
'db_password' => '',
140+
'db_connection_options' => array()
107141
), $options);
108142

109143
$this->table = $options['db_table'];
110144
$this->idCol = $options['db_id_col'];
111145
$this->dataCol = $options['db_data_col'];
112146
$this->timeCol = $options['db_time_col'];
147+
$this->username = $options['db_username'];
148+
$this->password = $options['db_password'];
149+
$this->connectionOptions = $options['db_connection_options'];
113150
}
114151

115152
/**
@@ -118,6 +155,11 @@ public function __construct(\PDO $pdo, array $options = array())
118155
public function open($savePath, $sessionName)
119156
{
120157
$this->gcCalled = false;
158+
if (null === $this->pdo) {
159+
$this->pdo = new \PDO($this->dns ?: $savePath, $this->username, $this->password, $this->connectionOptions);
160+
$this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
161+
}
162+
$this->driver = $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME);
121163

122164
return true;
123165
}
@@ -270,6 +312,10 @@ public function close()
270312
$stmt->execute();
271313
}
272314

315+
if (false !== $this->dns) {
316+
$this->pdo = null;
317+
}
318+
273319
return true;
274320
}
275321

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

Lines changed: 68 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ protected function setUp()
2525

2626
$this->pdo = new \PDO('sqlite::memory:');
2727
$this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
28-
$sql = 'CREATE TABLE sessions (sess_id VARCHAR(128) PRIMARY KEY, sess_data TEXT, sess_time INTEGER)';
28+
$sql = 'CREATE TABLE sessions (sess_id VARCHAR(128) PRIMARY KEY, sess_data BLOB, sess_time INTEGER)';
2929
$this->pdo->exec($sql);
3030
}
3131

@@ -51,17 +51,74 @@ public function testInexistentTable()
5151
$storage->close();
5252
}
5353

54+
public function testWithLazyDnsConnection()
55+
{
56+
$dbFile = tempnam(sys_get_temp_dir(), 'sf2_sqlite_sessions');
57+
if (file_exists($dbFile)) {
58+
@unlink($dbFile);
59+
}
60+
61+
$pdo = new \PDO('sqlite:' . $dbFile);
62+
$sql = 'CREATE TABLE sessions (sess_id VARCHAR(255) PRIMARY KEY, sess_data BLOB, sess_time INTEGER)';
63+
$pdo->exec($sql);
64+
$pdo = null;
65+
66+
$storage = new PdoSessionHandler('sqlite:' . $dbFile);
67+
$storage->open('', 'sid');
68+
$data = $storage->read('id');
69+
$storage->write('id', 'data');
70+
$storage->close();
71+
$this->assertSame('', $data, 'New session returns empty string data');
72+
73+
$storage->open('', 'sid');
74+
$data = $storage->read('id');
75+
$storage->close();
76+
$this->assertSame('data', $data, 'Written value can be read back correctly');
77+
78+
@unlink($dbFile);
79+
}
80+
81+
public function testWithLazySavePathConnection()
82+
{
83+
$dbFile = tempnam(sys_get_temp_dir(), 'sf2_sqlite_sessions');
84+
if (file_exists($dbFile)) {
85+
@unlink($dbFile);
86+
}
87+
88+
$pdo = new \PDO('sqlite:' . $dbFile);
89+
$sql = 'CREATE TABLE sessions (sess_id VARCHAR(255) PRIMARY KEY, sess_data BLOB, 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
94+
$storage = new PdoSessionHandler(null);
95+
$storage->open('sqlite:' . $dbFile, 'sid');
96+
$data = $storage->read('id');
97+
$storage->write('id', 'data');
98+
$storage->close();
99+
$this->assertSame('', $data, 'New session returns empty string data');
100+
101+
$storage->open('sqlite:' . $dbFile, 'sid');
102+
$data = $storage->read('id');
103+
$storage->close();
104+
$this->assertSame('data', $data, 'Written value can be read back correctly');
105+
106+
@unlink($dbFile);
107+
}
108+
54109
public function testReadWriteRead()
55110
{
56111
$storage = new PdoSessionHandler($this->pdo);
57112
$storage->open('', 'sid');
58-
$this->assertSame('', $storage->read('id'), 'New session returns empty string data');
113+
$data = $storage->read('id');
59114
$storage->write('id', 'data');
60115
$storage->close();
116+
$this->assertSame('', $data, 'New session returns empty string data');
61117

62118
$storage->open('', 'sid');
63-
$this->assertSame('data', $storage->read('id'), 'Written value can be read back correctly');
119+
$data = $storage->read('id');
64120
$storage->close();
121+
$this->assertSame('data', $data, 'Written value can be read back correctly');
65122
}
66123

67124
/**
@@ -77,8 +134,9 @@ public function testWriteDifferentSessionIdThanRead()
77134
$storage->close();
78135

79136
$storage->open('', 'sid');
80-
$this->assertSame('data_of_new_session_id', $storage->read('new_id'), 'Data of regenerated session id is available');
137+
$data = $storage->read('new_id');
81138
$storage->close();
139+
$this->assertSame('data_of_new_session_id', $data, 'Data of regenerated session id is available');
82140
}
83141

84142
/**
@@ -109,8 +167,9 @@ public function testSessionDestroy()
109167
$this->assertEquals(0, $this->pdo->query('SELECT COUNT(*) FROM sessions')->fetchColumn());
110168

111169
$storage->open('', 'sid');
112-
$this->assertSame('', $storage->read('id'), 'Destroyed session returns empty string');
170+
$data = $storage->read('id');
113171
$storage->close();
172+
$this->assertSame('', $data, 'Destroyed session returns empty string');
114173
}
115174

116175
public function testSessionGC()
@@ -125,12 +184,14 @@ public function testSessionGC()
125184
$this->assertEquals(1, $this->pdo->query('SELECT COUNT(*) FROM sessions')->fetchColumn());
126185

127186
$storage->open('', 'sid');
128-
$this->assertSame('', $storage->read('id'), 'Session already considered garbage, so not returning data even if it is not pruned yet');
187+
$data = $storage->read('id');
129188
$storage->gc(0);
130189
$storage->close();
131-
$this->assertEquals(0, $this->pdo->query('SELECT COUNT(*) FROM sessions')->fetchColumn());
132190

133191
ini_set('session.gc_maxlifetime', $previousLifeTime);
192+
193+
$this->assertSame('', $data, 'Session already considered garbage, so not returning data even if it is not pruned yet');
194+
$this->assertEquals(0, $this->pdo->query('SELECT COUNT(*) FROM sessions')->fetchColumn());
134195
}
135196

136197
public function testGetConnection()

0 commit comments

Comments
 (0)
0