首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

PHP 設計模式:觀察者模式深度實作與錯誤處理應用

     編輯:PHP 綜合知識

觀察者模式:建立靈活可擴展的錯誤處理系統

觀察者模式讓物件間保持鬆耦合:主題不需知道觀察者細節,觀察者自行決定如何回應。本文以「錯誤處理系統」為例,實作多通道日誌記錄 (檔案、Email、Syslog、資料庫)。

核心架構

<?php
// 1. 主題介面 (可被觀察者)
interface SplSubject {
    public function attach(SplObserver $observer): void;
    public function detach(SplObserver $observer): void;
    public function notify(): void;
}

// 2. 觀察者介面
interface SplObserver {
    public function update(SplSubject $subject): void;
}

// 3. 具體主題:錯誤處理器 (單例模式)
class ErrorHandler implements SplSubject {
    private array $observers = [];
    private string $lastError = '';
    private static ?self $instance = null;
    
    private function __construct() {}
    
    public static function getInstance(): self {
        return self::$instance ??= new self();
    }
    
    public function attach(SplObserver $observer): void {
        $this->observers[] = $observer;
    }
    
    public function detach(SplObserver $observer): void {
        $key = array_search($observer, $this->observers, true);
        if ($key !== false) unset($this->observers[$key]);
    }
    
    public function notify(): void {
        foreach ($this->observers as $observer) {
            $observer->update($this);
        }
    }
    
    public function triggerError(string $message, int $level = E_USER_ERROR): void {
        $this->lastError = $message;
        $this->notify();
        // 可選:觸發 PHP 原生錯誤
        trigger_error($message, $level);
    }
    
    public function getLastError(): string {
        return $this->lastError;
    }
}
?>

具體觀察者實作

<?php
// 1. 檔案日誌觀察者
class FileErrorLogger implements SplObserver {
    private $fileHandle;
    
    public function __construct($fileHandle) {
        $this->fileHandle = $fileHandle;
    }
    
    public function update(SplSubject $subject): void {
        if ($subject instanceof ErrorHandler) {
            $msg = "[" . date('Y-m-d H:i:s') . "] {$subject->getLastError()}\n";
            fwrite($this->fileHandle, $msg);
        }
    }
}

// 2. Email 通知觀察者
class EmailErrorLogger implements SplObserver {
    private string $email;
    
    public function __construct(string $email) {
        $this->email = $email;
    }
    
    public function update(SplSubject $subject): void {
        if ($subject instanceof ErrorHandler) {
            $subject = "系統錯誤通知";
            $body = "時間: " . date('Y-m-d H:i:s') . "\n錯誤: {$subject->getLastError()}";
            mail($this->email, $subject, $body);
        }
    }
}

// 3. Syslog 觀察者
class SyslogErrorLogger implements SplObserver {
    public function update(SplSubject $subject): void {
        if ($subject instanceof ErrorHandler) {
            syslog(LOG_ERR, $subject->getLastError());
        }
    }
}

// 4. 資料庫觀察者 (PDO)
class DatabaseErrorLogger implements SplObserver {
    private PDO $pdo;
    
    public function __construct(PDO $pdo) {
        $this->pdo = $pdo;
    }
    
    public function update(SplSubject $subject): void {
        if ($subject instanceof ErrorHandler) {
            $stmt = $this->pdo->prepare(
                "INSERT INTO error_log (message, created_at) VALUES (?, NOW())"
            );
            $stmt->execute([$subject->getLastError()]);
        }
    }
}
?>

整合使用:取代預設錯誤處理器

<?php
// bootstrap.php 或初始化檔案
require_once 'ErrorHandler.php';
require_once 'FileErrorLogger.php';
require_once 'EmailErrorLogger.php';
// require_once 'DatabaseErrorLogger.php';

$handler = ErrorHandler::getInstance();

// 註冊觀察者
$logFile = fopen(__DIR__ . '/logs/error.log', 'a');
$handler->attach(new FileErrorLogger($logFile));
$handler->attach(new EmailErrorLogger('admin@example.com'));
// $handler->attach(new DatabaseErrorLogger($pdo));

// 設定為 PHP 預設錯誤處理器
set_error_handler(function($severity, $message, $file, $line) use ($handler) {
    $handler->triggerError("[$severity] $message in $file:$line");
    return true; // 防止 PHP 內建處理
});

// 設定例外處理器
set_exception_handler(function(Throwable $e) use ($handler) {
    $handler->triggerError("Uncaught Exception: {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}");
});

// 設定致命錯誤捕獲 (PHP 7+)
register_shutdown_function(function() use ($handler) {
    $error = error_get_last();
    if ($error && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
        $handler->triggerError("Fatal Error: {$error['message']} in {$error['file']}:{$error['line']}");
    }
});

// 測試
trigger_error("這是一個測試警告", E_USER_WARNING);
throw new RuntimeException("測試例外");
?>

單元測試範例 (PHPUnit)

<?php
use PHPUnit\Framework\TestCase;

class ErrorHandlerTest extends TestCase {
    public function testObserversNotified() {
        $handler = ErrorHandler::getInstance();
        // 清理之前的觀察者 (測試隔離)
        $reflection = new ReflectionClass($handler);
        $prop = $reflection->getProperty('observers');
        $prop->setAccessible(true);
        $prop->setValue($handler, []);
        
        $mockLogger = $this->createMock(SplObserver::class);
        $mockLogger->expects($this->once())
                   ->method('update')
                   ->with($this->identicalTo($handler));
        
        $handler->attach($mockLogger);
        $handler->triggerError('Test error');
    }
    
    public function testFileLoggerWritesCorrectFormat() {
        $tempFile = sys_get_temp_dir() . '/test_error_' . uniqid() . '.log';
        $fh = fopen($tempFile, 'w');
        
        $logger = new FileErrorLogger($fh);
        $handler = ErrorHandler::getInstance();
        // ... 同理測試檔案寫入格式
        
        fclose($fh);
        unlink($tempFile);
    }
}
?>

進階:分級錯誤處理

<?php
class LeveledErrorHandler extends ErrorHandler {
    private array $levelObservers = []; // level => observers[]
    
    public function attach(SplObserver $observer, int $level = E_ALL): void {
        $this->levelObservers[$level][] = $observer;
    }
    
    public function notify(int $level = E_ALL): void {
        // 通知完全匹配等級 + E_ALL 觀察者
        foreach ([$level, E_ALL] as $lvl) {
            foreach ($this->levelObservers[$lvl] ?? [] as $observer) {
                $observer->update($this);
            }
        }
    }
    
    public function triggerError(string $message, int $level = E_USER_ERROR): void {
        $this->lastError = $message;
        $this->notify($level);
        trigger_error($message, $level);
    }
}

// 使用:只有嚴重錯誤發 Email
$handler = LeveledErrorHandler::getInstance();
$handler->attach(new FileErrorLogger($fh), E_ALL);        // 所有錯誤寫檔
$handler->attach(new EmailErrorLogger('admin@site.com'), E_USER_ERROR | E_ERROR); // 只發嚴重
?>

模式比較:觀察者 vs 發布/訂閱

特性 觀察者模式 發布/訂閱
耦合度 主題直接持有觀察者引用 透過事件總線/中介者解耦
通訊方式 同步、直接呼叫 通常非同步、佇列
適用範圍 單一應用程式內 跨服務、微服務架構
PHP 實作 SplSubject/SplObserver Symfony EventDispatcher、Laravel Events
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved