Skip to content

Commit

Permalink
Fix #20140: Fix compatibility with PHP 8.4: calling `session_set_save…
Browse files Browse the repository at this point in the history
…_handler()`
  • Loading branch information
Izumi-kun authored Dec 6, 2024
1 parent 5df412d commit 65e3369
Show file tree
Hide file tree
Showing 7 changed files with 106 additions and 31 deletions.
1 change: 1 addition & 0 deletions framework/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Yii Framework 2 Change Log
- New #20279: Add to the `\yii\web\Request` CSRF validation by custom HTTP header (olegbaturin)
- Enh #20279: Add to the `\yii\web\Request` `csrfHeader` property to configure a custom HTTP header for CSRF validation (olegbaturin)
- Enh #20279: Add to the `\yii\web\Request` `csrfTokenSafeMethods` property to configure a custom safe HTTP methods list (olegbaturin)
- Bug #20140: Fix compatibility with PHP 8.4: calling `session_set_save_handler()` (Izumi-kun)

2.0.51 July 18, 2024
--------------------
Expand Down
7 changes: 7 additions & 0 deletions framework/UPGRADE.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,13 @@ if you want to upgrade from version A to version C and there is
version B between A and C, you need to follow the instructions
for both A and B.

Upgrade from Yii 2.0.51
-----------------------

* The function signature for `yii\web\Session::readSession()` and `yii\web\Session::gcSession()` have been changed.
They now have the same return types as `\SessionHandlerInterface::read()` and `\SessionHandlerInterface::gc()` respectively.
In case those methods have overwritten you will need to update your child classes accordingly.

Upgrade from Yii 2.0.50
-----------------------

Expand Down
2 changes: 1 addition & 1 deletion framework/web/CacheSession.php
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ public function openSession($savePath, $sessionName)
* Session read handler.
* @internal Do not call this method directly.
* @param string $id session ID
* @return string the session data
* @return string|false the session data, or false on failure
*/
public function readSession($id)
{
Expand Down
8 changes: 3 additions & 5 deletions framework/web/DbSession.php
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ public function close()
* Session read handler.
* @internal Do not call this method directly.
* @param string $id session ID
* @return string the session data
* @return string|false the session data, or false on failure
*/
public function readSession($id)
{
Expand Down Expand Up @@ -247,15 +247,13 @@ public function destroySession($id)
* Session GC (garbage collection) handler.
* @internal Do not call this method directly.
* @param int $maxLifetime the number of seconds after which data will be seen as 'garbage' and cleaned up.
* @return bool whether session is GCed successfully
* @return int|false the number of deleted sessions on success, or false on failure
*/
public function gcSession($maxLifetime)
{
$this->db->createCommand()
return $this->db->createCommand()
->delete($this->sessionTable, '[[expire]]<:expire', [':expire' => time()])
->execute();

return true;
}

/**
Expand Down
37 changes: 13 additions & 24 deletions framework/web/Session.php
Original file line number Diff line number Diff line change
Expand Up @@ -175,34 +175,23 @@ protected function registerSessionHandler()
static::$_originalSessionModule = $sessionModuleName;
}

if ($this->handler === null && $this->getUseCustomStorage()) {
$this->handler = Yii::createObject(
[
'__class' => SessionHandler::class,
'__construct()' => [$this],
]
);
}

if ($this->handler !== null) {
if (!is_object($this->handler)) {
if (is_array($this->handler)) {
$this->handler = Yii::createObject($this->handler);
}
if (!$this->handler instanceof \SessionHandlerInterface) {
throw new InvalidConfigException('"' . get_class($this) . '::handler" must implement the SessionHandlerInterface.');
}
YII_DEBUG ? session_set_save_handler($this->handler, false) : @session_set_save_handler($this->handler, false);
} elseif ($this->getUseCustomStorage()) {
if (YII_DEBUG) {
session_set_save_handler(
[$this, 'openSession'],
[$this, 'closeSession'],
[$this, 'readSession'],
[$this, 'writeSession'],
[$this, 'destroySession'],
[$this, 'gcSession']
);
} else {
@session_set_save_handler(
[$this, 'openSession'],
[$this, 'closeSession'],
[$this, 'readSession'],
[$this, 'writeSession'],
[$this, 'destroySession'],
[$this, 'gcSession']
);
}
} elseif (
$sessionModuleName !== static::$_originalSessionModule
&& static::$_originalSessionModule !== null
Expand Down Expand Up @@ -610,7 +599,7 @@ public function closeSession()
* This method should be overridden if [[useCustomStorage]] returns true.
* @internal Do not call this method directly.
* @param string $id session ID
* @return string the session data
* @return string|false the session data, or false on failure
*/
public function readSession($id)
{
Expand Down Expand Up @@ -647,11 +636,11 @@ public function destroySession($id)
* This method should be overridden if [[useCustomStorage]] returns true.
* @internal Do not call this method directly.
* @param int $maxLifetime the number of seconds after which data will be seen as 'garbage' and cleaned up.
* @return bool whether session is GCed successfully
* @return int|false the number of deleted sessions on success, or false on failure
*/
public function gcSession($maxLifetime)
{
return true;
return 0;
}

/**
Expand Down
79 changes: 79 additions & 0 deletions framework/web/SessionHandler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?php
/**
* @link https://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license https://www.yiiframework.com/license/
*/

namespace yii\web;

use SessionHandlerInterface;

/**
* SessionHandler implements an [[\SessionHandlerInterface]] for handling [[Session]] with custom session storage.
*
* @author Viktor Khokhryakov <[email protected]>
* @since 2.0.52
*/
class SessionHandler implements SessionHandlerInterface
{
/**
* @var Session
*/
private $_session;

public function __construct(Session $session)
{
$this->_session = $session;
}

/**
* @inheritDoc
*/
public function close(): bool
{
return $this->_session->closeSession();
}

/**
* @inheritDoc
*/
public function destroy($id): bool
{
return $this->_session->destroySession($id);
}

/**
* @inheritDoc
*/
#[\ReturnTypeWillChange]
public function gc($max_lifetime)
{
return $this->_session->gcSession($max_lifetime);
}

/**
* @inheritDoc
*/
public function open($path, $name): bool
{
return $this->_session->openSession($path, $name);
}

/**
* @inheritDoc
*/
#[\ReturnTypeWillChange]
public function read($id)
{
return $this->_session->readSession($id);
}

/**
* @inheritDoc
*/
public function write($id, $data): bool
{
return $this->_session->writeSession($id, $data);
}
}
3 changes: 2 additions & 1 deletion tests/framework/web/session/AbstractDbSessionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,9 @@ public function testGarbageCollection()
$session->db->createCommand()
->update('session', ['expire' => time() - 100], 'id = :id', ['id' => 'expire'])
->execute();
$session->gcSession(1);
$deleted = $session->gcSession(1);

$this->assertEquals(1, $deleted);
$this->assertEquals('', $session->readSession('expire'));
$this->assertEquals('new data', $session->readSession('new'));
}
Expand Down

0 comments on commit 65e3369

Please sign in to comment.