From 9531a2b3f1458e8836dd9d2fef11af471bf1b607 Mon Sep 17 00:00:00 2001 From: Eugene Leonovich Date: Mon, 6 Oct 2014 00:36:25 +0200 Subject: [PATCH] [HttpFoundation] Fix PdoSessionHandler to work properly with streams --- .../Storage/Handler/PdoSessionHandler.php | 10 +- .../Storage/Handler/PdoSessionHandlerTest.php | 93 +++++++++++++++++++ 2 files changed, 100 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php index 26c5f8bc0e..32b73968db 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php @@ -521,11 +521,11 @@ class PdoSessionHandler implements \SessionHandlerInterface return ''; } - return $sessionRows[0][0]; + return is_resource($sessionRows[0][0]) ? stream_get_contents($sessionRows[0][0]) : $sessionRows[0][0]; } if (self::LOCK_TRANSACTIONAL === $this->lockMode && 'sqlite' !== $this->driver) { - // Exlusive-reading of non-existent rows does not block, so we need to do an insert to block + // Exclusive-reading of non-existent rows does not block, so we need to do an insert to block // until other connections to the session are committed. try { $insertStmt = $this->pdo->prepare( @@ -546,7 +546,11 @@ class PdoSessionHandler implements \SessionHandlerInterface $selectStmt->execute(); $sessionRows = $selectStmt->fetchAll(\PDO::FETCH_NUM); - return $sessionRows ? $sessionRows[0][0] : ''; + if ($sessionRows) { + return is_resource($sessionRows[0][0]) ? stream_get_contents($sessionRows[0][0]) : $sessionRows[0][0]; + } + + return ''; } throw $e; diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php index e4b8d76778..ba06fccff9 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php @@ -135,6 +135,53 @@ class PdoSessionHandlerTest extends \PHPUnit_Framework_TestCase $this->assertSame($sessionData, $readData, 'Written value can be read back correctly'); } + public function testReadConvertsStreamToString() + { + $pdo = new MockPdo('pgsql'); + $pdo->prepareResult = $this->getMock('PDOStatement'); + + $content = 'foobar'; + $stream = $this->createStream($content); + + $pdo->prepareResult->expects($this->once())->method('fetchAll') + ->will($this->returnValue(array(array($stream, 42, time())))); + + $storage = new PdoSessionHandler($pdo); + $result = $storage->read('foo'); + + $this->assertSame($content, $result); + } + + public function testReadLockedConvertsStreamToString() + { + $pdo = new MockPdo('pgsql'); + $selectStmt = $this->getMock('PDOStatement'); + $insertStmt = $this->getMock('PDOStatement'); + + $pdo->prepareResult = function ($statement) use ($selectStmt, $insertStmt) { + return 0 === strpos($statement, 'INSERT') ? $insertStmt : $selectStmt; + }; + + $content = 'foobar'; + $stream = $this->createStream($content); + $exception = null; + + $selectStmt->expects($this->atLeast(2))->method('fetchAll') + ->will($this->returnCallback(function () use (&$exception, $stream) { + return $exception ? array(array($stream, 42, time())) : array(); + })); + + $insertStmt->expects($this->once())->method('execute') + ->will($this->returnCallback(function () use (&$exception) { + throw $exception = new \PDOException('', '23'); + })); + + $storage = new PdoSessionHandler($pdo); + $result = $storage->read('foo'); + + $this->assertSame($content, $result); + } + public function testReadingRequiresExactlySameId() { $storage = new PdoSessionHandler($this->getMemorySqlitePdo()); @@ -263,4 +310,50 @@ class PdoSessionHandlerTest extends \PHPUnit_Framework_TestCase $this->assertInstanceOf('\PDO', $method->invoke($storage)); } + + private function createStream($content) + { + $stream = tmpfile(); + fwrite($stream, $content); + fseek($stream, 0); + + return $stream; + } +} + +class MockPdo extends \PDO +{ + public $prepareResult; + private $driverName; + private $errorMode; + + public function __construct($driverName = null, $errorMode = null) + { + $this->driverName = $driverName; + $this->errorMode = null !== $errorMode ?: \PDO::ERRMODE_EXCEPTION; + } + + public function getAttribute($attribute) + { + if (\PDO::ATTR_ERRMODE === $attribute) { + return $this->errorMode; + } + + if (\PDO::ATTR_DRIVER_NAME === $attribute) { + return $this->driverName; + } + + return parent::getAttribute($attribute); + } + + public function prepare($statement, $driverOptions = array()) + { + return is_callable($this->prepareResult) + ? call_user_func($this->prepareResult, $statement, $driverOptions) + : $this->prepareResult; + } + + public function beginTransaction() + { + } }