From 24f4e3126a9f6562a78ea55ad058f9c155ef09af Mon Sep 17 00:00:00 2001 From: Alexander Date: Thu, 8 Feb 2018 22:11:45 +0500 Subject: [PATCH] Bug #15523: `yii\web\Session` settings could now be configured after session is started (StalkAlex, rob006, daniel1302, samdark) Co-authored-by: Alexander Makarov Co-authored-by: Robert Korulczyk Co-authored-by: daniel.1302 --- framework/CHANGELOG.md | 2 + framework/web/DbSession.php | 9 +++-- framework/web/Session.php | 39 +++++++++++++++++++ .../web/session/AbstractDbSessionTest.php | 21 ++++++++++ tests/framework/web/session/SessionTest.php | 38 ++++++++++++++++++ 5 files changed, 106 insertions(+), 3 deletions(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 0bfd34435f..462946a89f 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -3,6 +3,8 @@ Yii Framework 2 Change Log 2.0.14 under development ------------------------ + +- Bug #15523: `yii\web\Session` settings could now be configured after session is started (StalkAlex, rob006, daniel1302, samdark) - Enh #15216: Added `yii\web\ErrorHandler::$traceLine` to allow opening file at line clicked in IDE (vladis84) - Enh #14488: Added support for X-Forwarded-Host to `yii\web\Request`, fixed `getServerPort()` usage (si294r, samdark) - Enh #13019: Support JSON in SchemaBuilderTrait (zhukovra, undefinedor) diff --git a/framework/web/DbSession.php b/framework/web/DbSession.php index af23cc4c19..df10d0ec8d 100644 --- a/framework/web/DbSession.php +++ b/framework/web/DbSession.php @@ -13,6 +13,7 @@ use yii\db\Connection; use yii\db\PdoValue; use yii\db\Query; use yii\di\Instance; +use yii\helpers\ArrayHelper; /** * DbSession extends [[Session]] by using database as session data storage. @@ -81,10 +82,12 @@ class DbSession extends MultiFieldSession * This method will initialize the [[db]] property to make sure it refers to a valid DB connection. * @throws InvalidConfigException if [[db]] is invalid. */ - public function init() + public function __construct(array $config = []) { - parent::init(); - $this->db = Instance::ensure($this->db, Connection::className()); + // db component should be initialized before configuring DbSession component + // @see https://github.com/yiisoft/yii2/pull/15523#discussion_r166701079 + $this->db = Instance::ensure(ArrayHelper::remove($config, 'db', $this->db), Connection::className()); + parent::__construct($config); } /** diff --git a/framework/web/Session.php b/framework/web/Session.php index ac2c8be43c..d770f282c3 100644 --- a/framework/web/Session.php +++ b/framework/web/Session.php @@ -89,6 +89,11 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co */ private $_cookieParams = ['httponly' => true]; + /** + * @var $frozenSessionData array|null is used for saving session between recreations due to session parameters update. + */ + private $frozenSessionData; + /** * Initializes the application component. @@ -413,6 +418,7 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co */ public function setUseCookies($value) { + $this->freeze(); if ($value === false) { ini_set('session.use_cookies', '0'); ini_set('session.use_only_cookies', '0'); @@ -423,6 +429,7 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co ini_set('session.use_cookies', '1'); ini_set('session.use_only_cookies', '0'); } + $this->unfreeze(); } /** @@ -439,6 +446,7 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co */ public function setGCProbability($value) { + $this->freeze(); if ($value >= 0 && $value <= 100) { // percent * 21474837 / 2147483647 ≈ percent * 0.01 ini_set('session.gc_probability', floor($value * 21474836.47)); @@ -446,6 +454,7 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co } else { throw new InvalidParamException('GCProbability must be a value between 0 and 100.'); } + $this->unfreeze(); } /** @@ -461,7 +470,9 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co */ public function setUseTransparentSessionID($value) { + $this->freeze(); ini_set('session.use_trans_sid', $value ? '1' : '0'); + $this->unfreeze(); } /** @@ -478,7 +489,9 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co */ public function setTimeout($value) { + $this->freeze(); ini_set('session.gc_maxlifetime', $value); + $this->unfreeze(); } /** @@ -902,4 +915,30 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co $this->open(); unset($_SESSION[$offset]); } + + /** + * If session is started it's not possible to edit session ini settings. In PHP7.2+ it throws exception. + * This function saves session data to temporary variable and stop session. + * @since 2.0.14 + */ + protected function freeze() + { + if (isset($_SESSION) && $this->getIsActive()) { + $this->frozenSessionData = $_SESSION; + } + $this->close(); + } + + /** + * Starts session and restores data from temporary variable + * @since 2.0.14 + */ + protected function unfreeze() + { + $this->open(); + if (null !== $this->frozenSessionData) { + $_SESSION = $this->frozenSessionData; + $this->frozenSessionData = null; + } + } } diff --git a/tests/framework/web/session/AbstractDbSessionTest.php b/tests/framework/web/session/AbstractDbSessionTest.php index 37d67d58fd..e1381a7230 100644 --- a/tests/framework/web/session/AbstractDbSessionTest.php +++ b/tests/framework/web/session/AbstractDbSessionTest.php @@ -197,4 +197,25 @@ abstract class AbstractDbSessionTest extends TestCase $this->assertEquals(['base'], $history); $this->createTableSession(); } + + public function testInstantiate() + { + $oldTimeout = ini_get('session.gc_maxlifetime'); + // unset Yii::$app->db to make sure that all queries are made against sessionDb + Yii::$app->set('sessionDb', Yii::$app->db); + Yii::$app->set('db', null); + + $session = new DbSession([ + 'timeout' => 300, + 'db' => 'sessionDb', + ]); + + $this->assertSame(Yii::$app->sessionDb, $session->db); + $this->assertSame(300, $session->timeout); + $session->close(); + + Yii::$app->set('db', Yii::$app->sessionDb); + Yii::$app->set('sessionDb', null); + ini_set('session.gc_maxlifetime', $oldTimeout); + } } diff --git a/tests/framework/web/session/SessionTest.php b/tests/framework/web/session/SessionTest.php index 96f7fed4b8..a75a250333 100644 --- a/tests/framework/web/session/SessionTest.php +++ b/tests/framework/web/session/SessionTest.php @@ -32,4 +32,42 @@ class SessionTest extends TestCase $this->assertNotEmpty($newSessionId); $this->assertEquals($oldSessionId, $newSessionId); } + + /** + * Test to prove that after Session::open changing session parameters will not throw exceptions + * and its values will be changed as expected. + */ + public function testParamsAfterSessionStart() + { + $session = new Session(); + $session->open(); + + $oldUseTransparentSession = $session->getUseTransparentSessionID(); + $session->setUseTransparentSessionID(true); + $newUseTransparentSession = $session->getUseTransparentSessionID(); + $this->assertNotEquals($oldUseTransparentSession, $newUseTransparentSession); + $this->assertTrue($newUseTransparentSession); + //without this line phpunit will complain about risky tests due to unclosed buffer + $session->setUseTransparentSessionID(false); + + $oldTimeout = $session->getTimeout(); + $session->setTimeout(600); + $newTimeout = $session->getTimeout(); + $this->assertNotEquals($oldTimeout, $newTimeout); + $this->assertEquals(600, $newTimeout); + + $oldUseCookies = $session->getUseCookies(); + $session->setUseCookies(false); + $newUseCookies = $session->getUseCookies(); + if (null !== $newUseCookies) { + $this->assertNotEquals($oldUseCookies, $newUseCookies); + $this->assertFalse($newUseCookies); + } + + $oldGcProbability = $session->getGCProbability(); + $session->setGCProbability(100); + $newGcProbability = $session->getGCProbability(); + $this->assertNotEquals($oldGcProbability, $newGcProbability); + $this->assertEquals(100, $newGcProbability); + } }