在PHP 開發中, session_register_shutdown()函數用於註冊一個會話關閉時自動執行的回調函數。儘管這個函數在某些舊版本的PHP 中存在,但在實際項目中,特別是維護遺留代碼時,可能會遇到需要對這部分代碼進行單元測試的情況。
由於session_register_shutdown()本身是綁定於PHP 會話機制的,直接測試它的回調行為較為困難。本文將介紹如何編寫可測試的代碼結構,並藉助PHP 的單元測試框架,模擬或替代session_register_shutdown()以便進行有效測試。
session_register_shutdown()會在PHP 腳本執行結束時調用註冊的回調函數,典型用法如下:
<?php
session_register_shutdown(function() {
// 關閉會話時的處理邏輯
file_put_contents('session_log.txt', 'Session closed at ' . date('Y-m-d H:i:s'));
});
?>
這個回調函數會在請求結束時自動執行,導致測試環境中無法直接捕獲其執行結果。
建議將真正的業務邏輯封裝在一個單獨的方法中, session_register_shutdown()只負責註冊回調,測試時僅調用業務邏輯方法即可。
<?php
class SessionHandler
{
public function registerShutdown()
{
session_register_shutdown([$this, 'onSessionShutdown']);
}
public function onSessionShutdown()
{
// 業務邏輯
file_put_contents('session_log.txt', 'Session closed at ' . date('Y-m-d H:i:s'));
}
}
?>
測試時,我們不調用registerShutdown() ,而是直接測試onSessionShutdown() 。
示例測試用例如下:
<?php
use PHPUnit\Framework\TestCase;
class SessionHandlerTest extends TestCase
{
protected $handler;
protected $logFile = __DIR__ . '/session_log.txt';
protected function setUp(): void
{
$this->handler = new SessionHandler();
// 確保日誌文件不存在
if (file_exists($this->logFile)) {
unlink($this->logFile);
}
}
public function testOnSessionShutdownCreatesLog()
{
$this->handler->onSessionShutdown();
$this->assertFileExists($this->logFile);
$content = file_get_contents($this->logFile);
$this->assertStringContainsString('Session closed at', $content);
}
protected function tearDown(): void
{
if (file_exists($this->logFile)) {
unlink($this->logFile);
}
}
}
?>
通過直接調用業務邏輯方法,避開了回調機制導致的測試複雜度。
如果希望在測試中確保註冊函數被調用,可以使用函數覆蓋或Mock 技術,但由於session_register_shutdown()是PHP 內置函數,直接Mock 可能較難。推薦的替代方案是:
將註冊函數抽象成可替換的依賴,例如用閉包或接口包裝。
在生產環境調用真實函數,在測試環境替換為空實現。
示例:
<?php
class SessionHandler
{
private $registerShutdownCallback;
public function __construct(callable $registerShutdownCallback = null)
{
$this->registerShutdownCallback = $registerShutdownCallback ?: 'session_register_shutdown';
}
public function registerShutdown()
{
call_user_func($this->registerShutdownCallback, [$this, 'onSessionShutdown']);
}
public function onSessionShutdown()
{
// 業務邏輯
}
}
?>
測試時傳入一個自定義的回調替代session_register_shutdown ,避免副作用。
不建議直接測試session_register_shutdown()的回調執行。
將業務邏輯拆分,單獨測試回調函數體。
通過依賴注入替代內置函數調用,提高測試靈活性。
使用PHPUnit 等測試框架驗證業務邏輯的正確性。
採用上述方法,能有效地對涉及session_register_shutdown()的代碼進行單元測試,提升代碼質量與維護性。
<?php
class SessionHandler
{
private $registerShutdownCallback;
public function __construct(callable $registerShutdownCallback = null)
{
$this->registerShutdownCallback = $registerShutdownCallback ?: 'session_register_shutdown';
}
public function registerShutdown()
{
call_user_func($this->registerShutdownCallback, [$this, 'onSessionShutdown']);
}
public function onSessionShutdown()
{
// 模擬寫日誌
file_put_contents('https://m66.net/session_log.txt', 'Session closed at ' . date('Y-m-d H:i:s'));
}
}
// 生產環境調用
$handler = new SessionHandler();
$handler->registerShutdown();
?>
<?php
use PHPUnit\Framework\TestCase;
class SessionHandlerTest extends TestCase
{
protected $handler;
protected $logFile = __DIR__ . '/session_log.txt';
protected function setUp(): void
{
$this->handler = new SessionHandler(function ($callback) {
// 模擬不執行任何操作,避免副作用
});
if (file_exists($this->logFile)) {
unlink($this->logFile);
}
}
public function testOnSessionShutdownCreatesLog()
{
$this->handler->onSessionShutdown();
$this->assertFileExists('https://m66.net/session_log.txt');
$content = file_get_contents('https://m66.net/session_log.txt');
$this->assertStringContainsString('Session closed at', $content);
}
protected function tearDown(): void
{
if (file_exists('https://m66.net/session_log.txt')) {
unlink('https://m66.net/session_log.txt');
}
}
}
?>