Laravel 允许我们在控制器中返回一个 Illuminate\Http\RedirectResponse 的实例来执行重定向。根据文档,我们可以通过 redirect() 帮助函数或者 Redirect Facade 来生成实例,并且可以方便得重定向至命名路由、控制器行为等。

但如果我们想要重定向至自定义 URL 呢?

对于这样的需求,Laravel 官方的文档是完全没有提及的(至少到目前 5.3 是这样),所以估计很多人都是自己实现了一个使用 header() 或者 <meta http-equiv='Refresh' content=''> 的重定向函数(包括以前的我)。

但是刚才在做 Blessing Skin Server 的国际化的时候,要求重定向至之前的页面。不得不说 Laravel 的 back() 函数有时候鸡肋得不行,因为它是通过在 session 里维护一个 _previous 数组来实现跳转到之前的页面的。

然而这个 _previous 会在每次 Laravel 收到请求的时候更新。没错,包括验证码,包括由 PHP 生成的预览图等等请求。这样的话访问一个带有上述请求的页面时,你是无法使用 back() 的,因为 Laravel 会把你重定向到那些 URL 上而不是用户看到的页面上。

好了回到正题。

这个回到上一个页面的功能也是可以用 HTTP_REFERER 这个 HTTP 头来实现的,并且里面存储了一个完整的 URL。然而,我们 Laravel 的文档里并没有写如何使用完整的 URL 生成重定向响应。

但是 Laravel 的 Response 可是基于 Symfony HttpFoundation 组件的,我可不信 SF 恁大个全栈框架会没有想到这种需求 (=゚ω゚)= 所以自己动手丰衣足食,让我们来往下找找看看这些类有没有暴♂露出什么方法来让我们调用吧。

首先我们使用 dd() 看一下 redirect('/') 所返回的对象到底长什么样:

瞧我们发现了什么!一只野生的 targetUrl 属性!

好吧,看来 Laravel 就是靠着这个属性来执行具体的重定向的,那我们具体得怎么修改这个属性呢?

虽然查看 Illuminate\Http\RedirectResponse 的类定义后没有找到什么有用的方法,但是我们发现了它是继承自 Symfony\Component\HttpFoundation\RedirectResponse 的,我们继续往下挖:

<?php

namespace Symfony\Component\HttpFoundation;

/**
 * RedirectResponse represents an HTTP response doing a redirect.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class RedirectResponse extends Response  
{
    protected $targetUrl;

    /* 省略 */

    public function setTargetUrl($url)
    {
        if (empty($url)) {
            throw new \InvalidArgumentException('Cannot redirect to an empty URL.');
        }
        // 耶!
        $this->targetUrl = $url;

        $this->setContent(/* Content */);

        $this->headers->set('Location', $url);

        return $this;
    }
}

可以看到 Symfony 的 RedirectResponse 类给我们暴露出了一个 setTargetUrl 方法,那么一切都好办了~

现在,我们只需要在 Laravel 控制器中像这样写:

return redirect('/')->setTargetUrl($_SERVER['HTTP_REFERER'])->withCookie('locale', $lang);  

即可返回一个带着自定义 URL 的 Illuminate\Http\RedirectResponse 实例,并且 Laravel 也会根据这个乖乖的帮我们重定向到指定页面去。

所以说文档有时候也是不全面的,如果真碰到啥奇怪的问题还是要自己去看代码呀(想起了我为了在 Laravel 外调用它的功能而到处翻源码并手动注册了一堆 Service Provider 的惨痛经历 ゚(つд`゚)

除另有声明外,本博客文章均采用 知识共享(Creative Commons) 署名-非商业性使用-相同方式共享 3.0 中国大陆许可协议 进行许可。