Меня всегда учили, что использование исключений в программировании допускает абстрагирование ошибок от объектов, которые вызывают ошибки. Глядя на руководство 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()); } }
Или вот так:
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-адрес является обязательным элементом. Однако исключение, подобное этому, будет служить только одной цели. Было бы разумнее создать RequestException, который обрабатывает все коды состояния 3XX, 4XX и 5XX? Плюс, как насчет сообщения? Это просто становится посторонней информацией на данный момент?
Я тоже с этим занимаюсь. Я поделюсь своими мыслями по этому вопросу.
В: Почему кто-то использует исключения для перенаправления вообще, когда вы можете сделать это так же просто, используя инструкцию header
и die
?
A: RFC2616 имеет это сказать о перенаправлении с кодом статуса 301:
Если метод запроса не был HEAD, сущность ответа СЛЕДУЕТ содержать короткую гипертекстовую ноту с гиперссылкой на новый URI (ы).
Из-за этого вам действительно нужен код для правильной реализации перенаправления. Лучше реализовать это один раз и сделать его доступным для повторного использования.
Q: Но тогда вы могли бы просто реализовать метод redirect
, вам не понадобилось бы исключение.
A: Когда вы перенаправляете, как вы можете знать, что «безопасно» убить скрипт PHP с помощью die
? Возможно, есть какой-то код в стек, который ждет вас, чтобы он мог вернуться, поэтому он может запускать некоторые операции очистки. Выбрасывая исключение, код вниз по стеку может поймать это исключение и очистку.
Ваш пример # 1 и # 3 на самом деле тот же, с той разницей, что в # 1 вы злоупотребляете общим классом Exception
. Имя исключения должно сказать что-то о том, что он делает ( RedirectException
), а не атрибут ( getCode() == 301
), тем более, что нигде не определено, что код в исключении должен соответствовать коду состояния HTTP. Кроме того, код, который хочет перехватить переадресацию в ситуации # 1, не может просто сделать catch (RedirectException $re)
но ему нужно будет проверить результат getCode()
. Это лишние накладные расходы.
Наиболее важным отличием между # 2 и # 3 является то, какой контроль вы предоставляете классам, получающим исключение. В # 2 вы в значительной степени говорите: «Приходит перенаправление, это происходит», блок catch не имеет надежного способа предотвратить перенаправление. В то время как в # 3 вы говорите: «Я хочу перенаправить, если у вас нет лучшей идеи», блок catch остановит («поймать») перенаправление, и это произойдет не до тех пор, пока исключение не будет сброшено дальше по стеку.
Это зависит от того, сколько элементов управления вы хотите передать в стек. Я лично считаю, что код в стеке должен иметь возможность отменить перенаправление, что сделало бы # 3 лучшим выбором. Типичным примером использования этого является перенаправление на страницу входа в систему: Представьте метод, который будет выполнять некоторую операцию для текущего пользователя, или перенаправить на страницу входа в систему, если никто не войдет в систему. Этот метод можно вызвать со страницы, которая не требуют, чтобы пользователь вошел в систему, но предоставит дополнительную функциональность, если есть. Просто поймать исключение намного проще, чем писать код вокруг метода, просто чтобы проверить, действительно ли пользователь вошел в систему.
Некоторые программисты могут выбрать # 2, потому что они думают, что если какой-то код инициирует перенаправление, он ожидает, что это перенаправление действительно произойдет. Позволяя перехватывать перенаправление и делать что-то еще, система будет менее предсказуемой. Однако я склонен думать, что это то, о чем говорят исключения; если у некоторого кода есть способ справиться с этим исключением, происходит операция, связанная с исключением. Эта операция обычно является отображением сообщения об ошибке, но это может быть что-то другое, например, перенаправление.
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 также позволяет коду стека реагировать, но он также позволит тому же коду предотвратить переадресацию.
Первый метод – лучший из трех методов. Сообщение, сделанное Marc B, имеет очень действительную точку, но у вас может быть решение для этого, не упомянутое в коде.
Примечание. Поскольку вы должны умереть после перенаправления, вы можете увидеть, почему примеры 2 и 3 странны.
Объяснение: Если вы запрашиваете страницу, первое, что сервер отправляет вам, это заголовок. Заголовок содержит информацию о веб-странице, которую вы собираетесь получить. Первый оператор в вашем php-коде, который генерирует вывод или первый HTML-код на вашем сайте, запускает заголовок, который нужно отправить. Если вы помещаете оператор определения заголовка в свой код, заголовок изменяется, чтобы сообщить браузеру немедленно перенаправить указанный URL. Так что почти все выполнение после инструкции заголовка бесполезно и пропущено.