我一直被教导,在编程中使用异常可以使错误处理从引发错误的对象中抽象出来.从PHP manual来看,PHP似乎具有Exception类和ErrorException类,表明并非所有异常都必须是错误.因此,我想用它们来帮助页面重定向.
我想要一个硬重定向,该重定向将仅发送标头,而没有页面内容.触发此操作的最佳方法是什么?假设我有一个带有redirect()方法的Controller类.
该方法应如下所示:
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有意义吗?另外,该消息呢?这是否只是成为无关紧要的信息?
解决方法:
我也一直在玩这个游戏.我会分享我对此事的想法.
基本原理
问:当您可以使用标头和die语句轻松进行重定向时,为什么有人会使用异常进行重定向?
答:RFC2616所说的关于使用状态码301进行重定向:
Unless the request method was HEAD, the entity of the response SHOULD contain a short hypertext note with a hyperlink to the new URI(s).
因此,您实际上需要一些代码才能正确实现重定向.最好一次执行一次,使其易于重用.
问:但是您可以轻松实现重定向方法,就不需要Exception.
答:重定向时,如何知道以死方式杀死PHP脚本是“安全的”?也许堆栈中有一些代码正在等待您返回,因此它可以运行一些清理操作.通过抛出一个异常,堆栈中的代码可以捕获此异常并进行清理.
比较方式
您的示例#1和#3实际上是相同的,不同之处在于在#1中您正在滥用泛型Exception类.异常的名称应说明其功能(RedirectException),而不是属性(getCode()== 301),尤其是因为没有定义异常中的代码应与HTTP状态代码匹配的地方.另外,想要在情况#1中捕获重定向的代码不能简单地捕获(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都是很好的解决方案,具体取决于您的要求. Example#2将允许堆栈中的代码对重定向做出反应,但无法防止这种情况的发生. Example#3还将允许堆栈中的代码作出反应,但也将启用相同的代码以防止发生重定向.
标签:exception,http,php
来源: https://codeday.me/bug/20191208/2088476.html