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

Yii2 核心原始碼深度解析:Action、ActionEvent 與 ActionFilter 行為鏈機制

     編輯:PHP 相關知識

Yii2 控制器動作執行流程:Action 基類、事件參數與過濾器鏈

Yii2 將控制器動作封裝為獨立 Action 類別,支援獨立複用、參數綁定、事件觸發與過濾器攔截。本文基於原始碼解析 yii\base\Action、ActionEvent、ActionFilter 三大核心類別的設計與協作機制。

Action 基類:動作封裝與參數綁定

<?php
namespace yii\base;

class Action extends Component
{
    public $id;           // 動作 ID (如 'create')
    public $controller;   // 所屬控制器實例
    
    public function __construct($id, $controller, $config = [])
    {
        $this->id = $id;
        $this->controller = $controller;
        parent::__construct($config);
    }
    
    // 唯一 ID:模組/控制器/動作
    public function getUniqueId()
    {
        return $this->controller->getUniqueId() . '/' . $this->id;
    }
    
    // 核心執行入口
    public function runWithParams($params)
    {
        // 1. 檢查 run() 方法存在
        if (!method_exists($this, 'run')) {
            throw new InvalidConfigException(get_class($this) . ' must define a "run()" method.');
        }
        
        // 2. 參數綁定:控制器負責將 $params 映射到 run() 參數
        $args = $this->controller->bindActionParams($this, $params);
        
        // 3. 記錄請求參數供應用層存取
        if (Yii::$app->requestedParams === null) {
            Yii::$app->requestedParams = $args;
        }
        
        // 4. 執行前置/後置鉤子
        if ($this->beforeRun()) {
            $result = call_user_func_array([$this, 'run'], $args);
            $this->afterRun();
            return $result;
        }
        return null;
    }
    
    // 可覆寫:返回 false 取消執行
    protected function beforeRun() { return true; }
    protected function afterRun() { }
}

參數綁定機制 (Controller::bindActionParams)

  • 利用 ReflectionFunction 分析 run() 簽名。
  • 從 $params (來自 UrlManager 解析) 按參數名匹配值。
  • 支援預設值、型別提示 (PHP 7+)、可變參數 ...$args。
  • 缺少必要參數拋出 BadRequestHttpException。

ActionEvent:動作事件參數物件

<?php
namespace yii\base;

class ActionEvent extends Event
{
    public $action;      // 正在執行的 Action 實例
    public $result;      // 動作執行結果,可被事件處理器修改
    public $isValid = true; // 是否繼續執行,EVENT_BEFORE_ACTION 可設為 false 取消
    
    public function __construct($action, $config = [])
    {
        $this->action = $action;
        parent::__construct($config);
    }
}

事件流程

  1. 控制器觸發 EVENT_BEFORE_ACTION,傳遞 ActionEvent。
  2. 過濾器/行為監聽器可設定 $event->isValid = false 取消後續執行。
  3. 若 isValid 為 true,執行 Action::runWithParams()。
  4. 執行完成觸發 EVENT_AFTER_ACTION,可修改 $event->result。

ActionFilter:過濾器基類與行為鏈

<?php
namespace yii\base;

class ActionFilter extends Behavior
{
    public $only = [];    // 僅套用的動作 ID 列表
    public $except = [];  // 排除的動作 ID 列表
    
    // 綁定事件
    public function attach($owner)
    {
        $this->owner = $owner;
        $owner->on(Controller::EVENT_BEFORE_ACTION, [$this, 'beforeFilter']);
    }
    
    public function detach()
    {
        if ($this->owner) {
            $owner->off(Controller::EVENT_BEFORE_ACTION, [$this, 'beforeFilter']);
            $owner->off(Controller::EVENT_AFTER_ACTION, [$this, 'afterFilter']);
            $this->owner = null;
        }
    }
    
    // EVENT_BEFORE_ACTION 處理器
    public function beforeFilter($event)
    {
        if (!$this->isActive($event->action)) return;
        
        $event->isValid = $this->beforeAction($event->action);
        
        if ($event->isValid) {
            // 成功才掛載 afterFilter (確保配對)
            $this->owner->on(Controller::EVENT_AFTER_ACTION, [$this, 'afterFilter'], null, false);
        } else {
            $event->handled = true; // 停止事件傳播
        }
    }
    
    // EVENT_AFTER_ACTION 處理器
    public function afterFilter($event)
    {
        $event->result = $this->afterAction($event->action, $event->result);
        $this->owner->off(Controller::EVENT_AFTER_ACTION, [$this, 'afterFilter']);
    }
    
    // 可覆寫:返回 false 取消動作
    public function beforeAction($action) { return true; }
    
    // 可覆寫:處理結果
    public function afterAction($action, $result) { return $result; }
    
    // 判斷是否套用於該動作
    protected function isActive($action)
    {
        $id = $action->id;
        if ($this->owner instanceof Module) {
            // 模組層級:轉換為相對模組的 ID
            $mid = $this->owner->getUniqueId();
            $uid = $action->getUniqueId();
            if ($mid && strpos($uid, $mid) === 0) {
                $id = substr($uid, strlen($mid) + 1);
            }
        }
        return !in_array($id, $this->except, true) && (empty($this->only) || in_array($id, $this->only, true));
    }
}

內建過濾器實作範例

過濾器用途關鍵邏輯
AccessControlRBAC/角色存取控制beforeAction 檢查規則,失敗拋 ForbiddenHttpException
PageCache整頁快取beforeAction 檢查快取命中,命中直接返回、設 isValid=false;afterAction 存入快取
HttpCacheHTTP 條件快取 (ETag/Last-Modified)設定回應標頭,beforeAction 檢查 If-None-Match 返回 304
VerbFilterHTTP 方法限制beforeAction 驗證 Yii::$app->request->method
RateLimiterAPI 速率限制實作 RateLimitInterface,beforeAction 檢查配額

自定義過濾器範例:操作日誌記錄

<?php
namespace app\components;

use yii\base\ActionFilter;
use Yii;

class ActionLogFilter extends ActionFilter
{
    public function beforeAction($action)
    {
        // 記錄開始時間
        Yii::info("Action started: {$action->getUniqueId()}", __METHOD__);
        return true;
    }
    
    public function afterAction($action, $result)
    {
        $duration = microtime(true) - YII_BEGIN_TIME;
        $userId = Yii::$app->user->isGuest ? 'guest' : Yii::$app->user->id;
        $ip = Yii::$app->request->userIP;
        
        Yii::info(
            "Action finished: {$action->getUniqueId()} | User: $userId | IP: $ip | Duration: {$duration}s | Result: " . json_encode($result),
            __METHOD__
        );
        return $result;
    }
}

控制器掛載過濾器

<?php
namespace app\controllers;

use yii\web\Controller;
use app\components\ActionLogFilter;
use yii\filters\AccessControl;

class PostController extends Controller
{
    public function behaviors()
    {
        return [
            'access' => [
                'class' => AccessControl::class,
                'rules' => [
                    ['allow' => true, 'actions' => ['index', 'view'], 'roles' => ['?']],
                    ['allow' => true, 'actions' => ['create', 'update', 'delete'], 'roles' => ['@']],
                ],
            ],
            'log' => [
                'class' => ActionLogFilter::class,
                'only' => ['create', 'update', 'delete'], // 僅寫入操作記錄
            ],
        ];
    }
    
    public function actionCreate()
    {
        // ...
    }
}

執行順序總結

  1. 控制器 runAction() 建立 Action 實例。
  2. 觸發 EVENT_BEFORE_ACTION → 所有掛載過濾器的 beforeFilter() 執行 (按 behaviors() 順序)。
  3. 若所有過濾器 isValid=true,執行 Action::runWithParams() → run()。
  4. 觸發 EVENT_AFTER_ACTION → 過濾器 afterFilter() 反向執行 (後掛載先執行)。
  5. 返回結果。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved