Set the layout file for error action dynamically

2020-04-27 07:05发布

I know there are different ways to choose layout file. It can be done

  • in the configuration
  • with Yii::$app->layout = '...'
  • with Controller::$layout

I have some controllers that use different layout file like this:

class FirstController extends yii\web\Controller {
    public $layout = 'firstLayout';
    ...
}

class SecondController extends yii\web\Controller {
    public $layout = 'secondLayout';
    ...
}

Now I have the error handling that is default in Yii2. So there is the configuration setting which sets 'errorHandler' to 'site/error' (which is a global error handler). And there is the SiteController with

class SiteController extends yii\web\Controller {
    public function actions() {
        return [
            'error'   => [
                'class' => 'yii\web\ErrorAction',
            ],
        ];
    }
    ...
}

My problem is that the error handler uses the layout file of the SiteController by default. But it should be the one from the controller that was actually called. How could I achieve this dynamically?

2条回答
▲ chillily
2楼-- · 2020-04-27 07:42

It could be set in an application event:

$config = [
    'id'              => 'my-app',
    'basePath'        => dirname(__DIR__),
    'params'          => ...
    'bootstrap'       => ['log'],
    'components'      => [
        ...
        'errorHandler' => [
            'errorAction' => 'site/error',
        ],
        ...
    ],

    'on beforeAction' => function (yii\base\ActionEvent $e) {
        if ($e->action->id === 'error' && $e->action->controller->id === 'site') {
            // this is the second step
            if (isset(Yii::$app->params['requested-layout']))
                $e->action->controller->layout = Yii::$app->params['requested-layout'];
        } else {
            // this happens first
            Yii::$app->params['requested-layout'] = $e->action->controller->layout;
        }
    },
];

What happens: Note that beforeAction gets called twice if an exception gets thrown in an action. The second call is caused by the error handler. Now, on each reqular request the current layout gets stored somewhere (I used the globally available Yii params for this). If then for some reason the error handler needs to be called the layout file for the containing error action controller gets adopted to the stored layout file.

Open issues:

  • Modules are not taken into account. Would require some more logic.
  • 'on beforeAction' gets called and Yii::$app->params['requested-layout'] will be set only for existing actions and controllers (and modules) and exceptions that gets thrown within action methods.
  • If the route is invalid or another error occurres the beforeAction handler will be called directly for site/error without executing it before. So the layout file does not gets adopted (could be solved with an implementation for 'on beforeRequest') and the default layout will be used (can be really default or could be set in different ways (e.g. $layout property of the SiteController).
  • More logic could be required if the layout files are not in one single folder (defined in Application::$layoutFiles).

I think the module could be considered as well easily with extending this line (not tested):

Yii::$app->params['requested-layout'] = $e->action->controller->layout ?: 
                                        $this->action->controller->module->layout;

Let me know if this makes sense or is wrong.

查看更多
我命由我不由天
3楼-- · 2020-04-27 07:45

One way could be to set the the layout file for the application in beforeAction in each controller:

public function beforeAction($action) {
    Yii::$app->layout = $this->layout;
    return parent::beforeAction($action);
}

It could be optimized by putting this in an extra controller from which all other controllers inherit.

But I don't really like this solution. Seems to me like a workaround. Also this does not work if the SiteController defines a layout by itself (as $layout property or within a method).

查看更多
登录 后发表回答