如何使用 PHP 异常来定义重定向?

KOV*_*IKO 5 php exception http

我一直被教导在编程中使用异常可以从引发错误的对象中抽象出错误处理。看PHP手册,好像PHP有一个Exception类和一个ErrorException类,说明并不是所有的异常都一定是错误。因此,我想使用它们来帮助进行页面重定向。

我想要一个硬重定向,只发送标题而不发送页面内容。触发此操作的最佳方式是什么?假设我有一个Controller带有方法的类redirect()

该方法应该如下所示:

class Controller {
    public function redirect($path) {
        throw new Exception($path, 301);
    }
}

...

try {
    $controller->redirect('http://domain.tld/redirected');
} catch (Exception $e) {
    if ($e->getCode() == 301) {
        header('Location: ' . $e->getMessage());
    }
}
Run Code Online (Sandbox Code Playgroud)

或者像这样:

class Controller {
    public function redirect($path) {
        header('Location: ' . $path);
        throw new Exception('The page is being redirected', 301);
    }
}

...

try {
    $controller->redirect('http://domain.tld/redirected');
} catch (Exception $e) {
    if ($e->getCode() == 301) {
        // Output nothing
    }
}
Run Code Online (Sandbox Code Playgroud)

或者我应该创建一个像这样的新类型的异常:

class RedirectException extends Exception {
    protected $url;

    public function __construct($url) {
        parent::__construct('The redirects are coming!', 301);
        $this->url = (string)$url;
    }

    public function getURL() {
        return $this->url;
    }
}

...

class Controller {
    public function redirect($path) {
        throw new RedirectException($path);
    }
}

...

try {
    $controller->redirect('http://domain.tld/redirected');
} catch (RedirectException $e) {
    header('Location: ' . $e->getURL());
}
Run Code Online (Sandbox Code Playgroud)

虽然我觉得所有这些都会起作用,但没有一个对我来说是正确的。最后一个似乎是最接近的,因为它清楚地表明 URL 是必需的成员。然而,这样的例外只能达到一个目的。构建一个处理所有 3XX、4XX 和 5XX 状态代码的 RequestException 是否更有意义?另外,消息怎么样?此时,这会成为无关的信息吗?

jor*_*ane 4

我自己也一直在玩这个。我将分享我对此事的想法。

基本原理

header问:当使用anddie语句可以轻松完成时,为什么有人会使用异​​常来重定向?

答:RFC2616 对于状态码 301 的重定向有这样的规定:

除非请求方法是 HEAD,否则响应实体应该包含一个简短的超文本注释,其中包含指向新 URI 的超链接。

因此,您实际上需要一些代码来正确实现重定向。最好实现一次并使其易于重用。

问:但是你可以很容易地实现一个redirect方法,你不需要异常。

答:当您重定向时,您如何知道使用 终止 PHP 脚本是“安全”的die?也许堆栈中有一些代码正在等待您返回,因此它可以运行一些清理操作。通过抛出异常,堆栈中的代码可以捕获此异常并进行清理。

比较

你的例子#1和#3实际上是相同的,不同之处在于在#1中你滥用了泛型Exception类。异常的名称应该说明它的作用 ( RedirectException),而不是属性 ( getCode() == 301),特别是因为没有任何地方定义异常中的代码应与 HTTP 状态代码匹配。此外,想要捕获情况 #1 中的重定向的代码不能简单地执行,catch (RedirectException $re)而是需要检查getCode(). 这是不必要的开销。

#2 和 #3 之间最重要的区别是您对接收异常的类给予了多少控制权。在#2 中,您几乎会说“重定向即将到来,这正在发生”,catch 块没有可靠的方法来阻止重定向发生。在 #3 中,您说“我想重定向,除非您有更好的主意”,catch 块将停止(“捕获”)重定向,并且直到异常被进一步抛出堆栈时才会发生。

选择哪个

这取决于您想要为堆栈中的代码提供多少控制权。我个人认为堆栈中的代码应该能够取消重定向,这将使 #3 成为更好的选择。一个典型的用例是重定向到登录页面:想象一个方法将为当前用户执行某些操作,或者在没有人登录的情况下重定向到登录页面。可以从不登录的页面调用此方法。要求用户登录,但如果有的话将提供额外的功能。仅捕获异常比围绕方法编写代码(仅检查用户是否实际登录)要干净得多。

一些程序员可能会选择#2,因为他们认为如果某些代码启动重定向,那么它预计该重定向实际上会发生。允许拦截重定向并执行其他操作将使框架变得更不可预测。然而,我倾向于认为这就是例外的含义;除非某些代码有办法处理异常,否则就会发生与异常相关的操作。此操作通常是显示错误消息,但也可以是其他操作,例如重定向。

#3 的示例实现

class RedirectException extends Exception {
    const PERMANENT = 301;
    const FOUND = 302;
    const SEE_OTHER = 303;
    const PROXY = 305;
    const TEMPORARY = 307;

    private static $messages = array(
        301 => 'Moved Permanently',
        302 => 'Found',
        303 => 'See Other',
        305 => 'Use Proxy',
        307 => 'Temporary Redirect',
    );

    protected $url;

    public function __construct($url, $code = 301, $message =   NULL) {
        parent::__construct($message
            ? (string)$message
            : static::$messages[$code], (int)$code
            );
        if (strpos($url, '/') === 0) {
            $this->url = static::getBaseURL() . $this->url;
        }
        $this->url = (string)$url;
    }

    public function getURL() {
        return $this->url;
    }

    public function run() {
        header('Location: ' . $this->url, true, $this->getCode());
    }
}
Run Code Online (Sandbox Code Playgroud)

结论

示例#1 和#3 几乎相同,但#3 的设计更好。#2 和 #3 都是很好的解决方案,具体取决于您的要求。示例 #2 将允许堆栈中的代码对重定向做出反应,但无法阻止这种情况的发生。示例 #3 还将允许堆栈中的代码做出反应,但它也将启用相同的代码以防止发生重定向。