В ветке 3.3 продолжилось дальнейшее развитие классов, участвующих в выполнении запросов (Request
, Response
, Controller
, HTTP
и т.д.). Так как поменялись некоторые важные нюансы, решил выделить изменения в отдельную статью.
Выполнение экшена контроллера
#4377. В версии 3.2 запуском экшенов управлял объект Request
(а точнее, метод execute_request()
класса Request_Client_Internal
). Он запускал before()
, затем проверял существование нужного экшена и выполнял его, и под конец запускал метод after()
. Концепция удобная, но расположение внутри класса Request_Client_Internal
затрудняло внесение изменений в данный процесс. Даже если подкорректировать алгоритм через класс-пустышку, то это затронет абсолютно все внутренние запросы проекта.
В версии 3.3 предложен более удобный способ. Базовый класс Controller
имеет метод execute()
, внутри которого и расположился описанный выше алгоритм. В результате мы можем легко поменять его под свои нужды, в том числе для отдельной группы контроллеров. Очень удобно. Также можно ловить исключения для целой группы экшенов в одном месте.
Метод
execute()
должен возвращать объектResponse
!
HMVC
Переработана идеология работы подзапросов. Теперь они изолированы от родительского запроса. В частности, брошенное исключение или редирект не повлияют на родительский запрос. В любом случае вернется объект класса Response
.
В версии 3.2 после брошенного исключения возвращался текст ответа (т.е. строка), а редирект в дочернем подзапросе приводил к остановке выполнения запросов и перенаправлению на новый адрес. Теперь же можно спокойно анализировать результат работы запроса, в частности его статус (с помощью метода status()
):
// пример обработки HMVC в 3.3 $response = Request::factory('foo/bar')->execute(); switch ($response->status()) { case 404: // обрабатываем 404 break; ... case 200: // все ок, можно показывать результат break; } |
Коды HTTP-ответов
Обработка HTTP-статусов теперь облегчена по сравнению с 3.2, не надо устанавливать и потом анализировать $this->response->status()
, достаточно просто выбросить HTTP_Exception
:
// отправляем погуглить ;) throw HTTP_Exception::factory(302, 'http://google.com'); |
Обработчик исключений сгенерирует Response
с соответствующим статусом, заполнит его тело (body) и заголовки (headers) если надо.
HTTP_Exception
Изменения в обработке исключений связаны с изменениями в HMVC. Теперь каждое исключение должно отдавать объект Response
в качестве результата работы. Причем механизм различен для семейства исключений HTTP и всех остальных.
Исключения класса HTTP_Exception
В Kohana существует целый ряд исключений для различных HTTP-кодов. Но если в Kohana 3.2 они принципиально ничем не отличались от остальных исключений (разве что для них были заранее заготовлены тексты сообщений в свойстве Response::$messages
), то в 3.3 для них можно легко реализовать красивые страницы сообщений. Все, что нужно сделать — написать свой метод get_response()
в соответствующем классе.
Например, вот так осуществляется вывод по умолчанию (метод в классе HTTP_Exception
):
public function get_response() { return Kohana_Exception::response($this); } |
Это вызов стандартного обработчика, который всегда используется со всеми остальными исключениями (см. ниже). Никто не мешает нам вывести специальную страницу для кода 404 (страница не найдена):
// в классе HTTP_Exception_404 public function get_response() { Kohana_Exception::log($this); return Response::factory() ->status(404) ->body(View::factory('http/404')); } |
Обработчик очень простой — сообщение об ошибке логируется (стандартный для исключений метод Kohana_Exception::log()
), создается объект Response
, в который передается статус 404 и специальная вьюшка для отображения страницы Not Found.
Обработчик по умолчанию
Для всех исключений, кроме HTTP_Exception
с собственным методом get_response()
, будет применен метод Kohana_Exception::response($exception)
. В нем проводится сбор данных об исключении (код, текст ошибки, трассировка и т.д.) и передается в шаблон для генерации стандартной страницы ошибки (она ничем не отличается от предыдущих версий). Имя используемого шаблона конфигурируется, для этого надо изменить свойство Kohana_Exception::$error_view
(по умолчанию ‘kohana/error‘).
Редирект
Выше я уже отметил, что редирект в версии 3.3 не ведет к немедленному прекращению выполнения запроса. Он теперь всего лишь выбрасывает HTTP_Exception
с переданным кодом и URI назначения. А тот, в свою очередь, должен вернуть объект Response
со статусом и заголовком Location
. Если запрос является основным, то объект Response
отошлет заголовки, и редирект все же произойдет. А вот HMVC-запросы ломать работу основного не будут, и вместо неожиданных редиректов скромно вернут ответ со статусом 30x.
Помимо общих принципов, изменилось и собственно API для осуществления редиректов. Метод redirect($url = '', $code = 302)
переместился из класса Request
в классы Controller
и HTTP
, причем статический (!) метод в контроллере (Controller::redirect()
) является оберткой вокруг метода HTTP::redirect()
.
HTTP-кеширование
В версии 3.2 обработка заголовков HTTP-кеширования велась с помощью метода check_cache($etag = NULL, Request $request = NULL)
класса Response
. В ветке 3.3 данный метод переместился в класс Controller
, и нужды в передаче параметра $request
уже нет (передается $this->request
).
Для тех, кто не знает, зачем это нужно, предлагаю посмотреть кусок кода из Userguide, метод отвечает за отображение медиафайлов (css, js и т.д.):
if ($file = Kohana::find_file('media/guide', $file, $ext)) { // Check if the browser sent an "if-none-match: <etag>" header, and tell if the file hasn't changed $this->check_cache(sha1($this->request->uri()).filemtime($file)); // Send the file content as the response $this->response->body(file_get_contents($file)); // Set the proper headers to allow caching $this->response->headers('content-type', File::mime_by_ext($ext)); $this->response->headers('last-modified', date('r', filemtime($file))); } |
Etag вычисляется на основе переданного URI и даты последнего изменения запрашиваемого файла. Если браузер передал заголовок if-none-match с совпавшим идентификатором etag, то до file_get_contents()
выполнение не дойдет. От сервера вернется ответ с кодом 304 Not Modified.
Заключение
В данной статье я описал изменения, влияющие на основную задачу ядра — обработку запросов (request flow). Отдельные изменения, затронувшие менее важные задачи (в основном это изменения хэлперов и библиотек) будут описаны в следующей статье.
А как скачать 3.3? Тут скачивается без core, а в core нет тега 3.3 (
https://github.com/kohana/core/tree/3.3/develop ?
Да, уже скачал, после того, как написал комментарий )
Проще через Git )))
Спасибо большое за статью.
Как по мне изменения очень правильные, и нужные. В верном направлении двигаются товарищи =)
Вроде не изменился: https://github.com/kohana/core/blob/3.3/develop/classes/Kohana/Controller.php#L125
И зачем было делать для редиректа в контроллере именно статический метод?!
@SVat
Да, меня странно переклинило Порядок менялся, но в другую сторону, и в итоге он остался прежним. Спасибо, поправил статью.
По поводу его «статичности», вот тут интересное обсуждение. Есть подозрение, что этот метод еще поменяет свое содержание, или вообще будет удален из класса Controller.
Иван, подскажи, пожалуйста, как в 3.3 получить объект текущего Response извне контроллера? В 3.2 это выглядело как Request::current()->response()
В 3.3 объект Response не хранится внутри Request. Он генерируется во время execute(), и отдается как результат его работы (https://github.com/kohana/core/blob/3.3/develop/classes/Kohana/Request/Client.php#L144, https://github.com/kohana/core/blob/3.3/develop/classes/Kohana/Request.php#L989). Т.е. на результат работы запроса извне повлиять нельзя (ИМХО, это вполне логично).
Как-то так.
Жаль. Просто я написал хелпер, чтобы вместо $this->response->body($view->render()) писать просто render($view);