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

时间:2011-08-17 14:23:23

标签: php http exception

我一直被告知在编程中使用异常允许从抛出错误的对象中抽象出错误处理。看看PHP manual,似乎PHP有一个Exception类和一个ErrorException类,表明并非所有异常都有是错误。所以,我想用它们来帮助重定向页面。

我希望有一个硬重定向,只会发送标题而不会发送页面内容。什么是触发这个的最好方法?假设我有Controllerredirect()方法。

该方法应如下所示:

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());
    }
}

或者像这样:

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
    }
}

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

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());
}

虽然我觉得所有这些都能奏效,但他们中没有一个对我好。最后一个似乎是最接近的,因为它清楚地表明URL是必需的成员。但是,像这样的例外仅用于一个目的。构建处理所有3XX,4XX和5XX状态代码的RequestException会更有意义吗?另外,消息呢?这只是在这一点上变成了无关的信息吗?

2 个答案:

答案 0 :(得分:3)

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

原理

问:为什么有人会使用例外重定向,当您使用headerdie语句时可以轻松完成?

答:RFC2616有关于重定向状态代码301的说法:

  

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

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

问:但是,您可以轻松实现redirect方法,您也不需要例外。

答:当你重定向时,你怎么知道它是"安全"使用die杀死PHP脚本?也许堆栈中有一些等待你返回的代码,所以它可以运行一些清理操作。通过抛出异常,堆栈中的代码可以捕获此异常并进行清理。

比较

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

#2和#3之间最重要的区别是你对接收异常的类有多少控制权。在#2中你几乎要说"重定向即将到来,这种情况正在发生",catch块没有可靠的方法来阻止重定向的发生。在#3中,你说"我想重定向,除非你有更好的想法",一个catch块将停止(" 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());
    }
}

结论

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

答案 1 :(得分:0)

第一种方法是3种方法中最好的。 Marc B提出的帖子有一个非常有效的观点,但你可能已经有了解决方案,但代码中没有提到。

  • 重定向后总是死掉(<)。
  • 使用异常消息来保存URL并不是非常OOP。如果要使用例外,则应创建可包含URL的自定义例外。

注意:因为您应该在重定向后死亡,您可能会看到为什么示例2和3很奇怪。

说明:如果您请求页面,服务器发送给您的第一件事就是标题。标题包含有关您即将收到的网页的信息。您的PHP代码中生成输出或网站中第一个HTML代码的第一个语句会触发要发送的标头。如果将标题位置语句放在代码中,则会更改标题以告知浏览器立即重定向到提到的URL。所以在header语句之后的所有执行都是无用的并且被跳过。