1. 程式人生 > >yii2 controller behavior函式的beforeAction實現原理

yii2 controller behavior函式的beforeAction實現原理

首先寫一個例子,在siteController中覆蓋behaviors函式

    public function behaviors()
    {
        return [
            'access' => [
                'class'=>AuthFilter::className(),
                'only'=>['index'],
              ],
            'access11' => [
                'class'=>AuthFilter::className(),
                'only'=>['index'],
            ],
        ];
    }

AuthFilter的定義
class AuthFilter extends ActionFilter
{
    public function beforeAction($action)
    {
        echo 'this is echo when before Action';
        return parent::beforeAction($action); // TODO: Change the autogenerated stub
    }

    public function afterAction($action, $result)
    {
        echo 'this is echo when after action';
        return parent::afterAction($action, $result); // TODO: Change the autogenerated stub
    }
}


我們知道controller的攔截器是在behavior中進行定義的,而controller的攔截器的函式必須定義beforeAction  和afterAction,這是為什麼呢,因為這是當前Controller的祖父類定義的!!!

我的SiteController的父類是web的Controller,該類的父類是yii\base\controller,每次 一個請求的到來,都會執行祖父類Controller的runAction

 public function runAction($id, $params = [])
    {
        $action = $this->createAction($id);
        if ($action === null) {
            throw new InvalidRouteException('Unable to resolve the request: ' . $this->getUniqueId() . '/' . $id);
        }

        Yii::trace('Route to run: ' . $action->getUniqueId(), __METHOD__);

        if (Yii::$app->requestedAction === null) {
            Yii::$app->requestedAction = $action;
        }

        $oldAction = $this->action;
        $this->action = $action;

        $modules = [];
        $runAction = true;

        // call beforeAction on modules
        foreach ($this->getModules() as $module) {
            if ($module->beforeAction($action)) {    //這裡執行了一次,呼叫點是yii\web\application
                array_unshift($modules, $module);
            } else {
                $runAction = false;
                break;
            }
        }

        $result = null;

        if ($runAction && $this->beforeAction($action)) {  //可以看到,執行beforeAction之後,並且返回true,之後參會進行下一步操作
            // run the action
            $result = $action->runWithParams($params);

            $result = $this->afterAction($action, $result);

            // call afterAction on modules
            foreach ($modules as $module) {
                /* @var $module Module */
                $result = $module->afterAction($action, $result);
            }
        }

        $this->action = $oldAction;

        return $result;
    }

祖父類執行了beforeAction,但是大家需要注意的是beforeAction執行了兩次,一次是web\application執行beforeAction,這樣是不會執行我們controller中的behavior中的beforeAction函式的,而當執行$this->beforeAction的時候其定義如下:

    public function beforeAction($action)
    {
        $event = new ActionEvent($action);
        $this->trigger(self::EVENT_BEFORE_ACTION, $event);
        return $event->isValid;
    }
這樣就觸發了trigger函式,其函式定義如下:
  public function trigger($name, Event $event = null)
    {
        $this->ensureBehaviors();
        if (!empty($this->_events[$name])) {
            if ($event === null) {
                $event = new Event;
            }
            if ($event->sender === null) {
                $event->sender = $this;
            }
            $event->handled = false;
            $event->name = $name;

            foreach ($this->_events[$name] as $handler) {
                $event->data = $handler[1];
                call_user_func($handler[0], $event);//此處進行beforeAction的呼叫
                // stop further handling if the event is handled
                if ($event->handled) {
                    return;
                }
            }
        }
        // invoke class-level attached handlers
        Event::trigger($this, $name, $event);
    }
因為我們在$this 也就是siteController中定義了behaviors函式,在ensureBahaviors中就將behavior放在了component中的_behaviors陣列當中,同時將對應的資料寫在了_events當中,其$name 也是beforeAction,這樣就會執行call_user_func函式,呼叫我們的介面

只是此時$name 是beforeAction,而 $handler是一個數組,第一個元素是定義類的物件,第二個是要執行的函式名稱"beforeFilter",該函式的定義是在ActionFilter類中定義的

    /**
     * @param ActionEvent $event
     */
    public function beforeFilter($event)
    {
        if (!$this->isActive($event->action)) {
            return;
        }

        $event->isValid = $this->beforeAction($event->action);
        if ($event->isValid) {
            // call afterFilter only if beforeFilter succeeds
            // beforeFilter and afterFilter should be properly nested
            $this->owner->on(Controller::EVENT_AFTER_ACTION, [$this, 'afterFilter'], null, false);
        } else {
            $event->handled = true;
        }
    }

由該函式轉調子類的beforeAction,這樣就完成了我們的整個操作,需要注意的是如果有多個behavior的時候,則會根據behaviors陣列的順序,依次進行呼叫!!!