2011-08-17 32 views
2

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

我想要一个硬重定向,将只发送一个头和没有页面内容。什么才是触发这个最好的方法?比方说,我有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()); 
    } 
} 

或者这样:

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会更有意义吗?另外,消息呢?在这一点上,这只是成为无关的信息吗?

+0

不会触及实际问题,但是如果您想以这种方式使用重定向,则最好对['headers_sent()'](http://php.net/headers_sent)进行一些检查。您的异常处理程序可能会在创建页面的任何阶段启动,并且重定向仅在发生任何输出之前发生异常时才起作用。否则,你只会得到“标题已发送”的警告,不会发生重定向。比较安全的用法是:if(headers_sent()){...输出一个重定向+ javascript document.location块...} else {header('Location:...'); }' –

+0

感谢marc的提示,这对我来说是一个全新的东西,但是,出于好奇,为什么要做meta重定向+ JS块?只是为了更安全一面? – Kumar

+0

@Kumar:是的,更安全的一面。如果您的异常必须执行重定向,那么添加多个重定向尝试层会使重定向发生的可能性更大。 –

回答

0

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

  • 重定向后总是死()。
  • 使用异常消息来保存URL不是很符合OOP。如果你想使用异常,你应该创建一个可以包含URL的自定义异常。

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

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

+0

不使用'die()'的意思是处理头不可能重定向的情况。由于帖子已经很长,我不想让额外的逻辑陷入代码示例。尽管我已经展示了使用header(),但我也在考虑显示一个页面的选项,该页面显示“在10秒内重定向到X”,然后为支持JavaScript的浏览器进行倒计时更新。 – Koviko

2

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

理由

问:为什么会有人使用异常在所有重定向时,你可以使用一个headerdie声明做一样容易?

答:RFC2616具有这样说,状态代码重定向301:

除非请求方法是HEAD,响应应该包含与超链接到新的URI短超文本注释的实体( S)。

因此,您实际上需要一些代码才能正确实现重定向。最好实施一次,并使其易于重复使用。问:但你可以很容易地实现一个redirect方法,你不需要一个例外。

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

比较

你举的例子#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()); 
    } 
} 

结论

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

+0

您的关于它允许清理代码运行的参数尽可能使用重定向方法。 –

+0

这是正确的。这可以通过将清理代码放入重定向方法来完成,但这需要业务逻辑出现在方法中。或者,您可以使用回调函数,但这需要簿记。 #3也将允许调用代码取消重定向(比较,最后一段)。 – jornane