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

PHP 觀察者模式實戰:構建可擴展的異常捕獲與通知系統

     編輯:PHP 相關知識

PHP 觀察者模式實戰:構建可擴展的異常捕獲與通知系統

異常信息的捕獲、記錄與通知是系統穩定性的關鍵。利用 觀察者模式 將「異常拋出」(主題)與「具體處理邏輯」(觀察者:郵件、日誌、钉钉、Slack 等)解耦,實現單一職責、開閉原則。本文通過完整代碼示例演示如何在 PHP 中落地該模式。

一、核心角色定義

1. 觀察者接口 Exception_Observer

<?php
interface Exception_Observer {
    /** 當主題通知時調用
     * @param Observer_Exception $e 異常對象本身
     */
    public function update(Observer_Exception $e);
}

2. 主題基類 Observer_Exception(繼承自 Exception)

<?php
class Observer_Exception extends Exception {
    /** 靜態容器:存放所有掛載的觀察者實例 */
    private static $_observers = [];

    /** 掛載觀察者 */
    public static function attach(Exception_Observer $observer) {
        self::$_observers[] = $observer;
    }

    public function __construct($message = null, $code = 0) {
        parent::__construct($message, $code);
        // 構造時自動通知所有觀察者
        $this->notify();
    }

    /** 遍歷通知 */
    private function notify() {
        foreach (self::$_observers as $observer) {
            // 關鍵點:$this 即當前異常實例,傳遞給觀察者
            $observer->update($this);
        }
    }
}

為什麼用靜態 $_observers?
異常拋出時創建的是新實例(new MyException()),若非靜態,每個實例擁有獨立的觀察者列表,導致在 try-catch 外部掛載的觀察者無法被內部新實例感知。靜態變量實現「全局共享觀察者列表」,甚至子類繼承也共享。

二、具體觀察者實現

1. 郵件通知觀察者

<?php
class Emailing_Exception_Observer implements Exception_Observer {
    private $_email = 'admin@example.com';

    public function __construct($email = null) {
        if ($email !== null && filter_var($email, FILTER_VALIDATE_EMAIL)) {
            $this->_email = $email;
        }
    }

    public function update(Observer_Exception $e) {
        $msg  = "時間: " . date('Y-m-d H:i:s') . PHP_EOL;
        $msg .= "信息: " . $e->getMessage() . PHP_EOL;
        $msg .= "追蹤: " . $e->getTraceAsString() . PHP_EOL;
        $msg .= "文件: " . $e->getFile() . PHP_EOL;
        $msg .= "行號: " . $e->getLine() . PHP_EOL;

        // error_log 第 2 參數 1 表示發送郵件(需 php.ini 配置 SMTP)
        error_log($msg, 1, $this->_email);
    }
}

2. 文件日誌觀察者

<?php
class Logging_Exception_Observer implements Exception_Observer {
    private $_filename = '/var/log/app_exceptions.log';

    public function __construct($filename = null) {
        if ($filename !== null && is_string($filename)) {
            $this->_filename = $filename;
        }
    }

    public function update(Observer_Exception $e) {
        $msg  = "時間: " . date('Y-m-d H:i:s') . PHP_EOL;
        $msg .= "信息: " . $e->getMessage() . PHP_EOL;
        $msg .= "追蹤: " . $e->getTraceAsString() . PHP_EOL;
        $msg .= "文件: " . $e->getFile() . PHP_EOL;
        $msg .= "行號: " . $e->getLine() . PHP_EOL;

        // error_log 第 2 參數 3 表示寫入文件
        error_log($msg, 3, $this->_filename);
    }
}

三、業務代碼接入示例

<?php
require 'Exception_Observer.php';
require 'Observer_Exception.php';
require 'Logging_Exception_Observer.php';
require 'Emailing_Exception_Observer.php';

// 1. 啟動階段:掛載觀察者(通常在 bootstrap / 入口文件)
Observer_Exception::attach(new Logging_Exception_Observer());
Observer_Exception::attach(new Emailing_Exception_Observer('devops@company.com'));

// 2. 定義業務異常(可擴展自定義方法)
class MyException extends Observer_Exception {
    public function test() { echo 'this is a test'; }
    public function handleMe() { echo "自定義異常處理邏輯"; }
}

// 3. 業務邏輯中拋出異常
try {
    // 模擬業務錯誤
    throw new MyException("訂單支付回調簽名驗證失敗", 4001);
} catch (MyException $e) {
    // 此處僅做頁面友好提示,實際記錄/通知已在構造函數自動完成
    echo $e->getMessage();
    echo "<hr>";
}

四、模式優勢總結

傳統 try-catch 硬編碼觀察者模式
每個 catch 塊寫死 error_log、mail()異常拋出自動觸發所有掛載的處理器
新增通知渠道需修改所有 catch新增觀察者類,一行 attach() 接入,零侵入業務代碼
單元測試難以驗證副作用可 Mock 觀察者斷言 update() 被調用
異常處理邏輯分散、不一致統一入口,格式標準化,便於接入 ELK、Sentry 等平台

五、擴展建議

  • 異常分級:觀察者內部根據 $e->getCode() 或自定義屬性決定是否發送郵件(如僅 5xx 級別發郵件)。
  • 異步化:觀察者 update() 內將任務推入消息隊列,避免阻塞主流程。
  • 整合 Monolog/Sentry:觀察者內調用成熟庫,獲得結構化日誌、去重、告警能力。

觀察者模式讓「發生什麼」與「怎麼處理」徹底分離。在異常處理場景,它是通向可維護、可觀測系統的基石。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved