YII2中controller中的behaviors中的behavior內部是如何被使用的?


1. behaviors方法的調用:

在祖先對象components中有一個ensureBehaviors方法,代碼如下:

/**
     * Makes sure that the behaviors declared in [[behaviors()]] are attached to this component.
     */
    public function ensureBehaviors()
    {
        if ($this->_behaviors === null) {
            $this->_behaviors = [];
            foreach ($this->behaviors() as $name => $behavior) {
                $this->attachBehaviorInternal($name, $behavior);
            }
        }
    }

主要工作是對所有配置的behavior,通過attachBehaviorInternal方法生成對象,並且將當前宿主對象附加到每個behavior實例上,在behavior實例上以$owner變量存儲,並且呢將每個behavior上配置的事件進行綁定,合適的時候好觸發;

也就是三件事:1. 實例化behavior;2.將behavior實例與宿主關聯;3.綁定behavior上配置的事件;而behavior對宿主產生作用,最核心的也是通過綁定的事件來產生。

粗粗看了下,這里綁定的事件都來源於宿主,比如:

[Controller::EVENT_BEFORE_ACTION => 'beforeAction']
[Response::EVENT_BEFORE_SEND => 'beforeSend',]

[
  ActiveRecord::EVENT_BEFORE_UPDATE => 'evaluateAttributes',
  ActiveRecord::EVENT_BEFORE_INSERT => 'evaluateAttributes'
]
等等。系統中不少地方用到。采用事件的方式達到延遲觸發的效果。並且以鈎子的形式將插入正常的執行流程。

ensureBehavior方法在components類中__get,__set,__isset,__unset,__call,canSetProperty,canGetProperty,hasMethod,hasEventHandlers,on,off,trigger,
getBehavior,attachBehavior,attachBehaviors,detachBehavior,detachBehaviors都有調用。也就是說在這些方法或者組件及后代組件的動作中將行為注入,綁定。

這樣就清楚了,在宿主上以上方法中behavior被實例化,綁定到宿主對象,並且對宿主上的一些事件在behavior內部進行監聽,綁定相應的處理handler,方便宿主上事件發生時,被觸發和執行。

2. 我在工作中遇到的問題是:
 
        
public function beforeAction($action)
    {
        $response = Yii::$app->response;

        if (Yii::$app->request->isPost) {
            $data = Yii::$app->request->post();
        } else {
            $data = Yii::$app->request->get();
        }

        if (isset($data['UserId']) && isset($data['TokenId'])) {
            if (!($user = User::findOne(['id' => $data['UserId'], 'ws_api_token' => $data['TokenId']]))) {
                $response->statusCode = 401;
                $response->statusText = "用戶未找到或未授權";
                return false;
            } else {
                Yii::$app->user->setIdentity($user);
            }
        } else {
            $response->statusCode = 401;
            $response->statusText = "缺少必要參數UserId,TokenId";
            return false;
        }

        if (!parent::beforeAction($action)) return false;
    }
 
        

  將父級beforeaction放在后面執行了,而在controller中父級中trigger了EVENT_BEFORE_ACTION事件,而該事件觸發了在contentNegotiator中定義的方法beforeFilter。出現的結果如下:

An Error occurred while handling another error:
yii\base\InvalidArgumentException: Response content must not be an array. in /home/nginx/tianjian_vue/pro-api/vendor/yiisoft/yii2/web/Response.php:1063
Stack trace:
#0 /home/nginx/tianjian_vue/pro-api/vendor/yiisoft/yii2/web/Response.php(337): yii\web\Response->prepare()
#1 /home/nginx/tianjian_vue/pro-api/vendor/yiisoft/yii2/web/ErrorHandler.php(135): yii\web\Response->send()
#2 /home/nginx/tianjian_vue/pro-api/vendor/yiisoft/yii2/base/ErrorHandler.php(111): yii\web\ErrorHandler->renderException(Object(yii\base\InvalidArgumentException))
#3 [internal function]: yii\base\ErrorHandler->handleException(Object(yii\base\InvalidArgumentException))
#4 {main}
Previous exception:
yii\base\InvalidArgumentException: Response content must not be an array. in /home/nginx/tianjian_vue/pro-api/vendor/yiisoft/yii2/web/Response.php:1063
Stack trace:
#0 /home/nginx/tianjian_vue/pro-api/vendor/yiisoft/yii2/web/Response.php(337): yii\web\Response->prepare()
#1 /home/nginx/tianjian_vue/pro-api/vendor/yiisoft/yii2/base/Application.php(392): yii\web\Response->send()
#2 /home/nginx/tianjian_vue/pro-api/web/index.php(13): yii\base\Application->run()
#3 {main}

  錯誤是返回內容未格式化。也就是說:

'contentNegotiator' => [
                'class' => ContentNegotiator::className(),
                'formats' => [
                    'application/json' => Response::FORMAT_JSON
                ]
            ]

  behavior里對response里設置的數據返回格式沒起作用。原因是啥呢?

原因是response的格式化是在behavior-contentNegotiator中方法negotiateContentType中完成的。如下:

foreach ($this->formats as $type => $format) {
            $response->format = $format;
            $response->acceptMimeType = $type;
            $response->acceptParams = [];
            break;
        }
所以呢,把父級的beforeaction放到后面執行的化,就沒有先觸發事件,也就是沒有給response對象設置為返回值為json,返回的格式才是json格式,而默認是html格式的。

再梳理一遍:
在controller中配置了behavior-
contentNegotiator,在controller的父級ensureBehaviors調用中,將behavior實例化,綁定到controller宿主對象,並且設置事件監聽,在controller中主要監聽了
beforeaction事件。而ensureBehaviors在多種場合會發生自動調用,以上已列舉,此處不贅述;尤其是在魔術方法__call的調用中,因為在controller運行中會若干次調用方法。所以behaviors被運行后綁定是毫無問題的。
behavior被確認綁定后,后面在controller及action運行的時候,運行了beforeaction,但是調用父級的beforeaction放后面了,就會導致這之前如果運行結果為false,直接返回,此時response的格式化就是默認的html格式,
所以就會出現上面那樣的返回結果了。所以系統的方法放后面執行,則前面必須手動指定format了,或者手動觸發一次事件EVENT_BEFORE_ACTION。




 
 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM