Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Getting the controller action before behaviour code runs in Yii2

Tags:

php

yii

yii2

I'm trying to execute some code inside a Yii2 controller as I need some code from the model to be accessible within the behaviors section so I can pass the model as a parameter and avoid running duplicate queries; however I also need to be able to find out what action is being called, but I am not having much luck.

I have tried using beforeAction but it seems this gets run AFTER the behaviours code runs, so that doesn't help me.

I then tried using init, but it seems the action isn't available via $this->action->id at that point.

Some example code:

class MyController extends Controller { 

    public $defaultAction = 'view';

    public function init() {

        // $this->action not available in here

    }

    public function beforeAction() {

        // This is of no use as this runs *after* the 'behaviors' method

    }

    public function behaviors() {
        return [
            'access' => [
                'class' => NewAccessControl::className(),
                'only' => ['view','example1','example2'],
                'rules' => [
                    [
                        'allow' => false,
                        'authManager' => [
                            'model' => $this->model,
                            'other_param' => $foo,
                            'other_param' => $bar,
                        ],
                        'actions' => ['view'],
                    ],
                    // everything else is denied
                ],
            ],
        ];
    }

    public function viewAction() {

        // This is how it is currently instantiated, but we want to instantiate *before* the behavior code is run so we don't need to instantiate it twice
        // but to be able to do that we need to know the action so we can pass in the correct scenario

        $model = new exampleModel(['scenario' => 'view']);

    }

}

authManager is simply a reference to a member variable inside an extension of the AccessRule class.

Is there anyway I can do this?

like image 822
Brett Avatar asked Jun 30 '15 16:06

Brett


1 Answers

Well, if I get you right, you are looking for something like this:

public function behaviors()
{
    $model = MyModel::find()->someQuery();
    $action = Yii::$app->controller->action->id;
    return [
         'someBehavior' => [
             'class' => 'behavior/namespace/class',
             'callback' => function() use ($model, $action) {
                 //some logic here
             }
         ]
    ];
}

Because behaviors() is just a method, you can declare any variables and add any logic that you want in it, the only one convention that you must follow - is that return type must be an array.

If you use your custom behavior, you are able to use events() method where you can bind your behavior's methods to certain events. E.g.

class MyBehavior extends Behavior
{
    public function events()
    {
        return [
            \yii\web\User::EVENT_AFTER_LOGIN => 'myAfterLoginEvent',
        ];
    }

    public function myAfterLoginEvent($event)
    {
        //dealing with event
    }
}

In this example myAfterLoginEvent will be executed after user successfully login into application. $event variable will be passed by framework and depending of event type it will contain different data. Read about event object

UPDATE:

As I can see now my answer was more generic about events and behaviors. And now when you added code, I can suggest to you to override behavior's beforeAction($action) method with the following code:

public function beforeAction($action)
{
    $actionID = $action->id;
    /* @var $rule AccessRule */
    foreach ($this->rules as &$rule) {
        $model = &$rule->authManager['model'];
        //now set model scenario maybe like this
        $model->scenario = $actionID;
    }
    //now call parent implementation 
    parent::beforeAction($action);
}

Also take a look at AccessControl implementation of beforeAction method, it invokes for each rule allows method with passing current action to it as a parameter. So if you have class that extends AccessRule, you can either override allows($action, $user, $request) method or matchCustom($action) method to set appropriate model scenario. Hope this will help.

One more alternative:

override controller's runAction($id, $params = []) method. Here $id is actionID - exactly what you need. Check id, set appropriate model scenario and call parent::runAction($id, $params);

like image 92
Tony Avatar answered Nov 14 '22 22:11

Tony