Yii2: Controller::redirect() does not work from ErrorHandler::errorAction

Created on 8 Mar 2016  ·  6Comments  ·  Source: yiisoft/yii2

Задача:
в проекте есть много мест, где для неавторизованных пользователей вызывается throw HttpException(403). Нужно только гостей редиректить на страницу аутентификации. Решено было таким образом:

в конфиге подключаем errorHandler

        'errorHandler' => [
            'errorAction' => 'site/error',
        ],

В SiteController такой код:

    public function actionError()
    {
        if(($exception = Yii::$app->getErrorHandler()->exception) === null)
            $exception = new HttpException(404, Yii::t('yii', 'Page not found.'));

        if($exception instanceof HttpException)
            $code = $exception->statusCode;
        else
            $code = $exception->getCode();

        if($code == 403 && Yii::$app->user->isGuest)
            return $this->redirect(['/user/account/login']);

        ...

В одной из предыдущих версий это работало нормально, но сейчас страница стала получать код статуса 403, вместо 302 и редиректа не происходит:

redirect

Если это не ошибка, то подскажите как правильно разрулить ситуацию.

Спасибо и сорри за русский.

under discussion

Most helpful comment

Yii::$app->getResponse()->redirect($url)->send();
return;

All 6 comments

На первый взгляд все правильно. Вы порождаете HttpException со статус-кодом 403, а потом еще довешиваете редирект.

подскажите как правильно разрулить ситуацию

Я бы исправил ваш код таким образом:

        if($code == 403 && Yii::$app->user->isGuest) {
            return $this->redirect(['/user/account/login'], 302);
        }

В одной из предыдущих версий это работало нормально

Точного ответа дать не могу, можете попробовать найти коммит, изменивший поведение с помощью git bisect и я постараюсь это прокомментировать.

Пока задачу закрываю, но если что - высказывайте свои мысли.

Хм, что-то я фигню посоветовал :)

Если посмотреть на ErrorHandler.php:120, можно увидеть, что статус-код будет все равно установлен из исключения.

Попробуйте

        if($code == 403 && Yii::$app->user->isGuest) {
            $exception->statusCode = 302;
            return $this->redirect(['/user/account/login']);
        }

На первый взгляд все правильно. Вы порождаете HttpException со статус-кодом 403, а потом еще довешиваете редирект.

И в чем криминал-то? Кто сказал, что исключение должно обязательно всплыть на самый верх? Ведь ErrorHandler и создан, чтобы его перехватывать и обрабатывать как-то более цивилизованно, чем просто вывести "404 Page not found". Или я не прав?

Решение с $exception->statusCode = 302; не проходит, т.к. переменная $exception внутренняя для метода внутри ErrorHandler::renderException(), из SiteController::actionError() ее изменить нельзя. Потому тикет и создал.

Заменить во всем коде все вызовы HttpException на что-то типа
if(Yii::$app->user->isGuest) return $this->redirect(...); else throw HttpException(403) конечно можно, но как-то это коряво.

Я пока решил так: создал потомка ErrorHandler, подменив в нем метод renderException(), изменив в нем завершающий код:

было:

   if ($exception instanceof HttpException) 
            $response->setStatusCode($exception->statusCode);
   else
            $response->setStatusCode(500);

   $response->send();

стало:

    if($response->statusCode != 302)
    {
        if($exception instanceof HttpException)
            $response->setStatusCode($exception->statusCode);
        else
            $response->setStatusCode(500);
    }

    $response->send();

Для моего случая работает. Имхо такое поведение (редиректы пропускаем, иначе возвращаем статус, порожденный исключением) более логично. Может стоит внести в фреймворк?

Yii::$app->getResponse()->redirect($url)->send();
return;

Обработка ошибок — особый случай.

This note saved my life. Thank you. I can now use the below in my config

    'on beforeAction' => function ($event){

        if(Yii::$app->user->isGuest && Yii::$app->getRequest()->url !== Url::to(Yii::$app->getUser()->loginUrl))
        {
            Yii::$app->getResponse()->redirect(Url::to(\Yii::$app->getUser()->loginUrl))->send(); 
            return;
        }

    },
Was this page helpful?
0 / 5 - 0 ratings