4 * This file is part of the Symfony package.
6 * (c) Fabien Potencier <fabien@symfony.com>
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
12 namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler;
14 use PHPUnit\Framework\TestCase;
15 use Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler;
18 * @requires extension pdo_sqlite
19 * @group time-sensitive
21 class PdoSessionHandlerTest extends TestCase
25 protected function tearDown()
27 // make sure the temporary database file is deleted when it has been created (even when a test fails)
29 @unlink($this->dbFile);
34 protected function getPersistentSqliteDsn()
36 $this->dbFile = tempnam(sys_get_temp_dir(), 'sf2_sqlite_sessions');
38 return 'sqlite:'.$this->dbFile;
41 protected function getMemorySqlitePdo()
43 $pdo = new \PDO('sqlite::memory:');
44 $pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
45 $storage = new PdoSessionHandler($pdo);
46 $storage->createTable();
52 * @expectedException \InvalidArgumentException
54 public function testWrongPdoErrMode()
56 $pdo = $this->getMemorySqlitePdo();
57 $pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_SILENT);
59 $storage = new PdoSessionHandler($pdo);
63 * @expectedException \RuntimeException
65 public function testInexistentTable()
67 $storage = new PdoSessionHandler($this->getMemorySqlitePdo(), array('db_table' => 'inexistent_table'));
68 $storage->open('', 'sid');
70 $storage->write('id', 'data');
75 * @expectedException \RuntimeException
77 public function testCreateTableTwice()
79 $storage = new PdoSessionHandler($this->getMemorySqlitePdo());
80 $storage->createTable();
83 public function testWithLazyDsnConnection()
85 $dsn = $this->getPersistentSqliteDsn();
87 $storage = new PdoSessionHandler($dsn);
88 $storage->createTable();
89 $storage->open('', 'sid');
90 $data = $storage->read('id');
91 $storage->write('id', 'data');
93 $this->assertSame('', $data, 'New session returns empty string data');
95 $storage->open('', 'sid');
96 $data = $storage->read('id');
98 $this->assertSame('data', $data, 'Written value can be read back correctly');
101 public function testWithLazySavePathConnection()
103 $dsn = $this->getPersistentSqliteDsn();
105 // Open is called with what ini_set('session.save_path', $dsn) would mean
106 $storage = new PdoSessionHandler(null);
107 $storage->open($dsn, 'sid');
108 $storage->createTable();
109 $data = $storage->read('id');
110 $storage->write('id', 'data');
112 $this->assertSame('', $data, 'New session returns empty string data');
114 $storage->open($dsn, 'sid');
115 $data = $storage->read('id');
117 $this->assertSame('data', $data, 'Written value can be read back correctly');
120 public function testReadWriteReadWithNullByte()
122 $sessionData = 'da'."\0".'ta';
124 $storage = new PdoSessionHandler($this->getMemorySqlitePdo());
125 $storage->open('', 'sid');
126 $readData = $storage->read('id');
127 $storage->write('id', $sessionData);
129 $this->assertSame('', $readData, 'New session returns empty string data');
131 $storage->open('', 'sid');
132 $readData = $storage->read('id');
134 $this->assertSame($sessionData, $readData, 'Written value can be read back correctly');
137 public function testReadConvertsStreamToString()
139 if (defined('HHVM_VERSION')) {
140 $this->markTestSkipped('PHPUnit_MockObject cannot mock the PDOStatement class on HHVM. See https://github.com/sebastianbergmann/phpunit-mock-objects/pull/289');
143 $pdo = new MockPdo('pgsql');
144 $pdo->prepareResult = $this->getMockBuilder('PDOStatement')->getMock();
147 $stream = $this->createStream($content);
149 $pdo->prepareResult->expects($this->once())->method('fetchAll')
150 ->will($this->returnValue(array(array($stream, 42, time()))));
152 $storage = new PdoSessionHandler($pdo);
153 $result = $storage->read('foo');
155 $this->assertSame($content, $result);
158 public function testReadLockedConvertsStreamToString()
160 if (defined('HHVM_VERSION')) {
161 $this->markTestSkipped('PHPUnit_MockObject cannot mock the PDOStatement class on HHVM. See https://github.com/sebastianbergmann/phpunit-mock-objects/pull/289');
164 $pdo = new MockPdo('pgsql');
165 $selectStmt = $this->getMockBuilder('PDOStatement')->getMock();
166 $insertStmt = $this->getMockBuilder('PDOStatement')->getMock();
168 $pdo->prepareResult = function ($statement) use ($selectStmt, $insertStmt) {
169 return 0 === strpos($statement, 'INSERT') ? $insertStmt : $selectStmt;
173 $stream = $this->createStream($content);
176 $selectStmt->expects($this->atLeast(2))->method('fetchAll')
177 ->will($this->returnCallback(function () use (&$exception, $stream) {
178 return $exception ? array(array($stream, 42, time())) : array();
181 $insertStmt->expects($this->once())->method('execute')
182 ->will($this->returnCallback(function () use (&$exception) {
183 throw $exception = new \PDOException('', '23');
186 $storage = new PdoSessionHandler($pdo);
187 $result = $storage->read('foo');
189 $this->assertSame($content, $result);
192 public function testReadingRequiresExactlySameId()
194 $storage = new PdoSessionHandler($this->getMemorySqlitePdo());
195 $storage->open('', 'sid');
196 $storage->write('id', 'data');
197 $storage->write('test', 'data');
198 $storage->write('space ', 'data');
201 $storage->open('', 'sid');
202 $readDataCaseSensitive = $storage->read('ID');
203 $readDataNoCharFolding = $storage->read('tést');
204 $readDataKeepSpace = $storage->read('space ');
205 $readDataExtraSpace = $storage->read('space ');
208 $this->assertSame('', $readDataCaseSensitive, 'Retrieval by ID should be case-sensitive (collation setting)');
209 $this->assertSame('', $readDataNoCharFolding, 'Retrieval by ID should not do character folding (collation setting)');
210 $this->assertSame('data', $readDataKeepSpace, 'Retrieval by ID requires spaces as-is');
211 $this->assertSame('', $readDataExtraSpace, 'Retrieval by ID requires spaces as-is');
215 * Simulates session_regenerate_id(true) which will require an INSERT or UPDATE (replace).
217 public function testWriteDifferentSessionIdThanRead()
219 $storage = new PdoSessionHandler($this->getMemorySqlitePdo());
220 $storage->open('', 'sid');
221 $storage->read('id');
222 $storage->destroy('id');
223 $storage->write('new_id', 'data_of_new_session_id');
226 $storage->open('', 'sid');
227 $data = $storage->read('new_id');
230 $this->assertSame('data_of_new_session_id', $data, 'Data of regenerated session id is available');
233 public function testWrongUsageStillWorks()
235 // wrong method sequence that should no happen, but still works
236 $storage = new PdoSessionHandler($this->getMemorySqlitePdo());
237 $storage->write('id', 'data');
238 $storage->write('other_id', 'other_data');
239 $storage->destroy('inexistent');
240 $storage->open('', 'sid');
241 $data = $storage->read('id');
242 $otherData = $storage->read('other_id');
245 $this->assertSame('data', $data);
246 $this->assertSame('other_data', $otherData);
249 public function testSessionDestroy()
251 $pdo = $this->getMemorySqlitePdo();
252 $storage = new PdoSessionHandler($pdo);
254 $storage->open('', 'sid');
255 $storage->read('id');
256 $storage->write('id', 'data');
258 $this->assertEquals(1, $pdo->query('SELECT COUNT(*) FROM sessions')->fetchColumn());
260 $storage->open('', 'sid');
261 $storage->read('id');
262 $storage->destroy('id');
264 $this->assertEquals(0, $pdo->query('SELECT COUNT(*) FROM sessions')->fetchColumn());
266 $storage->open('', 'sid');
267 $data = $storage->read('id');
269 $this->assertSame('', $data, 'Destroyed session returns empty string');
272 public function testSessionGC()
274 $previousLifeTime = ini_set('session.gc_maxlifetime', 1000);
275 $pdo = $this->getMemorySqlitePdo();
276 $storage = new PdoSessionHandler($pdo);
278 $storage->open('', 'sid');
279 $storage->read('id');
280 $storage->write('id', 'data');
283 $storage->open('', 'sid');
284 $storage->read('gc_id');
285 ini_set('session.gc_maxlifetime', -1); // test that you can set lifetime of a session after it has been read
286 $storage->write('gc_id', 'data');
288 $this->assertEquals(2, $pdo->query('SELECT COUNT(*) FROM sessions')->fetchColumn(), 'No session pruned because gc not called');
290 $storage->open('', 'sid');
291 $data = $storage->read('gc_id');
295 ini_set('session.gc_maxlifetime', $previousLifeTime);
297 $this->assertSame('', $data, 'Session already considered garbage, so not returning data even if it is not pruned yet');
298 $this->assertEquals(1, $pdo->query('SELECT COUNT(*) FROM sessions')->fetchColumn(), 'Expired session is pruned');
301 public function testGetConnection()
303 $storage = new PdoSessionHandler($this->getMemorySqlitePdo());
305 $method = new \ReflectionMethod($storage, 'getConnection');
306 $method->setAccessible(true);
308 $this->assertInstanceOf('\PDO', $method->invoke($storage));
311 public function testGetConnectionConnectsIfNeeded()
313 $storage = new PdoSessionHandler('sqlite::memory:');
315 $method = new \ReflectionMethod($storage, 'getConnection');
316 $method->setAccessible(true);
318 $this->assertInstanceOf('\PDO', $method->invoke($storage));
321 private function createStream($content)
324 fwrite($stream, $content);
331 class MockPdo extends \PDO
333 public $prepareResult;
337 public function __construct($driverName = null, $errorMode = null)
339 $this->driverName = $driverName;
340 $this->errorMode = null !== $errorMode ?: \PDO::ERRMODE_EXCEPTION;
343 public function getAttribute($attribute)
345 if (\PDO::ATTR_ERRMODE === $attribute) {
346 return $this->errorMode;
349 if (\PDO::ATTR_DRIVER_NAME === $attribute) {
350 return $this->driverName;
353 return parent::getAttribute($attribute);
356 public function prepare($statement, $driverOptions = array())
358 return is_callable($this->prepareResult)
359 ? call_user_func($this->prepareResult, $statement, $driverOptions)
360 : $this->prepareResult;
363 public function beginTransaction()
367 public function rollBack()