當前位置: 首頁> 最新文章列表> 如何單元測試涉及session_register_shutdown() 的代碼?

如何單元測試涉及session_register_shutdown() 的代碼?

M66 2025-06-02

在PHP 開發中, session_register_shutdown()函數用於註冊一個會話關閉時自動執行的回調函數。儘管這個函數在某些舊版本的PHP 中存在,但在實際項目中,特別是維護遺留代碼時,可能會遇到需要對這部分代碼進行單元測試的情況。

由於session_register_shutdown()本身是綁定於PHP 會話機制的,直接測試它的回調行為較為困難。本文將介紹如何編寫可測試的代碼結構,並藉助PHP 的單元測試框架,模擬或替代session_register_shutdown()以便進行有效測試。


1. 了解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'));
});
?>

這個回調函數會在請求結束時自動執行,導致測試環境中無法直接捕獲其執行結果。


2. 分離業務邏輯與回調註冊,方便單元測試

建議將真正的業務邏輯封裝在一個單獨的方法中, 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()


3. 使用PHPUnit 進行單元測試

示例測試用例如下:

 <?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);
        }
    }
}
?>

通過直接調用業務邏輯方法,避開了回調機制導致的測試複雜度。


4. 模擬session_register_shutdown()

如果希望在測試中確保註冊函數被調用,可以使用函數覆蓋或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 ,避免副作用。


5. 總結

  • 不建議直接測試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');
        }
    }
}
?>