Фреймворк — это хорошо, это здорово, это возможность сэкономить кучу времени на раздумьях над архитектурой будущего приложения, но… Фреймворк как таковой — это каркас. И, на примере Kohana 3.0, о которой в данной статье пойдет речь, каркас этот надо, в той или иной степени, допиливать.
Теперь давайте по-порядку, чем мы сейчас займемся:
Итак, начнем…
Предполагается, что мы уже сделали пару простейших действий — создали файл .htaccess по образу и подобию example.htaccess, поставив в нем необходимый путь до нашей рабочей директории; изменили параметры инициализации в методе Kohana::init (также прописали путь к рабочей директории и выставили (по вкусу) 'index_file' => FALSE; подключили необходимые для последующей работы модули…
Теперь смотрим в конце bootstrap.php туда, где устанавливается роут default — сколько обычно в приложении роутов? Десять? Двадцать? В моем последнем приложении на Ko3.0 их было порядка 30. Дофига, в общем, для хранения прямо здесь, вперемешку с теми данными, которые, собственно, хранятся в bootstrap.php. Выход? Выносим их все в отдельный файл и инклудим. Допустим, так:
Создаем новый файл в папке application с названием routes.php и переносим туда весь Route::set('default')… а на его бывшем месте в bootstrap.php просто пишем require_once APPPATH.'routes.php';
Теперь давайте вспомним, что у класса контроллера (того, что Kohana_Controller) есть замечательные методы before() и after(), которые выполняются, соответственно, до и после «тела» контроллера. Теперь вспомним о том, что в большинстве приложений мы будем подключать модуль авторизации (auth) — как минимум, для административного входа в систему. К чему это я?
А давайте ка переопределим базовый Kohana_Controller и прямо там начнем работу с (возможным) пользователем.
И сохраняем это добро в папке application/classes под именем controller.php
Теперь подумаем, что еще нам может потребоваться в абсолютно любом месте любого контроллера в будущем? Редирект! Даже два! Собственно — редирект на главную и редирект назад. Возможные ситуации необходимости описывать не буду, просто если Вы согласны с необходимостью редиректов — читаем про них дальше:
Редирект на главную подразумевает редирект пользователя по адресу, который соответствует дефолтному роуту без параметров. Редирект назад подразумевает редирект по адресу из строки реферера, если это вообще ссылка. Если же нет — делаем ре��ирект на главную при помощи описанного выше редиректа. Также у нас может быть 2 типа редиректа (пока мне это ни разу не пригодилось, но возможность, мне кажется, полезная) — редирект текущего запроса (HMVC и все такое...) и редирект основного запроса. По-умолчанию будем выполнять редирект основного запроса. Собственно, реализацию описанного выше я предлагаю следующую (пишем все в том же application/classes/controller.php после метода before()):
Немного поясню методы:
в методе go_home() мы получаем URL дефолтного роута и вызываем метод go()
в методе go_back() мы проверяем на валидность URL из Request::$referrer и если он не проходит проверку — выполняем метод go_home() на котором выполнение и прерывается
в методе go() мы определяем, какой запрос редиректить (по-умолчанию — Request::instance() — основной запрос, но можно и $this->request)
Теперь, раз уж зашла речь о различных возможных запросах (HMVC/простой) — давайте устраним небольшое упущение в классе Request. Упущение состоит в том, что в Kohana 3.0, в отличии от Kohana 3.1, нет метода для определения принадлежности реквеста — только Request::$is_ajax.
Создаем файл application/classes/request.php и пишем:
Так как Request::current() возвращает экземпляр основного запроса (Singletone), то достаточно проверить, является ли таковым текущий объект Request — $this. Этот метод пригодится нам в дальнейшем.
Дальше на очереди у нас расширение простого контроллера — контроллер, который будет не просто выполнять какие-то действия, но и работать с View. Назовем его Controller_Front (хотя Kohana предлагает нечто отдаленно похожее под названием Controller_Template — это дело вкуса, от названия ничего не зависит — лишь то, от какого класса Вы будете наследовать остальные контроллеры).
Controller_Front у нас будет обеспечивать разбиение всего View на «обертку» и на «контент». Обертка — это стандартная разметка, характерная для всех страниц нашего проекта — там будет доктайп, подключение стилей и все-все-все, что должно быть на всех страницах. Контент — это результат работы конкретного контроллера. Давайте немного отвлечемся от нашего Controller_Front и сразу их создадим, чтобы понимать, о чем, собственно, речь:
Вот такая у нас будет обертка, в которую будет вкладываться контент ($content), полученный в результате работы контроллера.
Сохраним это как application/views/index.php
Теперь давайте создадим контент-вью для роута, предлагаемого Kohana по-умолчанию, welcome/index
и сохраним это как application/views/welcome/index.php
Собственно, к этой строке статьи Вы уже должны понять, если по каким-то причинам не получалось раньше, что есть обертка, а что — контент.
Но вернемся к нашему Controller_Front. Давайте по-умолчанию будем рендерить все в обертку index, если в контроллере имя обертки не было переопределено (мало-ли какая логика работы Вашего приложения).
В этом контроллере я, надеюсь, все понятно из комментариев, кроме работы с каким-то View::$view_path — что это такое? Это то, что позволит нам не писать в пределах одного контроллера в разных экшнах строки типа $this->content = View::factory('user/edit'); а заменить их на более компактные $this->content = View::factory('edit');
Давайте, кстати, сам View расширим — иначе как мы это реализуем?
Думаю, что после того, как была расписана работа с этим измененным классом из Controller_Front, останавливаться тут будет излишне, так что сохраняем этот файл как application/classes/view.php, сохраняем Controller_Front как application/classes/controller/front.php и «едем дальше».
Открываем контроллер welcome (application/classes/controller/welcome.php) и меняем extends Controller на extends Controller_Front, а в методе index заменяем строку $this->request->response = 'hello, world!'; на $this->content = View::factory('index');
Теперь, если все сделано верно, открываем браузер, заходим по адресу проекта и видим наш Views/welcome/index.php View-файл, обрамленный Views/index.php лэйоутом.
Статья пролежала в папке «дописать и опубликовать» без малого месяц, но наконец у меня дошли руки хоть как-то упорядочить информацию в ней и, собственно, опубликовать. Если Вы прочитали ее и какие-то моменты остались для вас непонятными, или я допустил ошибки в коде, или вы все сделали по статье, но ничего не заработало — незамедлительно пишите в комментариях, ��твечу при первой возможности. ;)
P.S.: думаю, когда-нибудь у меня дойдут руки и до остальных статей из папки «дописать и опубликовать», поэтому… Эта статья — только начало, дальше сделаем простенькое приложение (пишите пожелания в комментариях) на базе того, что мы успели сделать в этой статье — это позволит оценить удобства, которые мы только что создали.
UPD:
View-файл views/errors.php может быть таким:
Теперь давайте по-порядку, чем мы сейчас займемся:
- -Расширим базовый контроллер, добавив в него жизненно необходимые методы и работу с юзерами (которая присутствует в 99% проектов, хотя бы на уровне административного логина)
- -Создадим свой фронт-контроллер для более удобной и красивой работы с вью-файлами
- -Реализуем вывод ошибок валидации через фронт-контроллер
- -Улучшим базовый класс View
- -Ну и еще кое-какие полезные мелочи
Итак, начнем…
Предполагается, что мы уже сделали пару простейших действий — создали файл .htaccess по образу и подобию example.htaccess, поставив в нем необходимый путь до нашей рабочей директории; изменили параметры инициализации в методе Kohana::init (также прописали путь к рабочей директории и выставили (по вкусу) 'index_file' => FALSE; подключили необходимые для последующей работы модули…
Теперь смотрим в конце bootstrap.php туда, где устанавливается роут default — сколько обычно в приложении роутов? Десять? Двадцать? В моем последнем приложении на Ko3.0 их было порядка 30. Дофига, в общем, для хранения прямо здесь, вперемешку с теми данными, которые, собственно, хранятся в bootstrap.php. Выход? Выносим их все в отдельный файл и инклудим. Допустим, так:
Создаем новый файл в папке application с названием routes.php и переносим туда весь Route::set('default')… а на его бывшем месте в bootstrap.php просто пишем require_once APPPATH.'routes.php';
Теперь давайте вспомним, что у класса контроллера (того, что Kohana_Controller) есть замечательные методы before() и after(), которые выполняются, соответственно, до и после «тела» контроллера. Теперь вспомним о том, что в большинстве приложений мы будем подключать модуль авторизации (auth) — как минимум, для административного входа в систему. К чему это я?
А давайте ка переопределим базовый Kohana_Controller и прямо там начнем работу с (возможным) пользователем.
<?php class Controller extends Kohana_Controller { /** * @var auth property with instance of "Auth" module */ public $auth = NULL; /** * @var user property with object of user */ public $user = FALSE; public function before() { parent::before(); $this->auth = Auth::instance(); $this->user = $this->auth->get_user(); } public function after() { parent::after(); } }
И сохраняем это добро в папке application/classes под именем controller.php
Теперь подумаем, что еще нам может потребоваться в абсолютно любом месте любого контроллера в будущем? Редирект! Даже два! Собственно — редирект на главную и редирект назад. Возможные ситуации необходимости описывать не буду, просто если Вы согласны с необходимостью редиректов — читаем про них дальше:
Редирект на главную подразумевает редирект пользователя по адресу, который соответствует дефолтному роуту без параметров. Редирект назад подразумевает редирект по адресу из строки реферера, если это вообще ссылка. Если же нет — делаем ре��ирект на главную при помощи описанного выше редиректа. Также у нас может быть 2 типа редиректа (пока мне это ни разу не пригодилось, но возможность, мне кажется, полезная) — редирект текущего запроса (HMVC и все такое...) и редирект основного запроса. По-умолчанию будем выполнять редирект основного запроса. Собственно, реализацию описанного выше я предлагаю следующую (пишем все в том же application/classes/controller.php после метода before()):
public function go_home($current_request_only = FALSE) { $url = Route::url('default', NULL, TRUE); $this->go($url, $current_request_only); } public function go_back($current_request_only = FALSE) { Validate::url(Request::$referrer) OR $this->go_home($current_request_only); $this->go(Request::$referrer, $current_request_only); } private function go($url, $current_request_only) { $request = ($current_request_only) ? $this->request : Request::instance(); $request->redirect($url); }
Немного поясню методы:
в методе go_home() мы получаем URL дефолтного роута и вызываем метод go()
в методе go_back() мы проверяем на валидность URL из Request::$referrer и если он не проходит проверку — выполняем метод go_home() на котором выполнение и прерывается
в методе go() мы определяем, какой запрос редиректить (по-умолчанию — Request::instance() — основной запрос, но можно и $this->request)
Теперь, раз уж зашла речь о различных возможных запросах (HMVC/простой) — давайте устраним небольшое упущение в классе Request. Упущение состоит в том, что в Kohana 3.0, в отличии от Kohana 3.1, нет метода для определения принадлежности реквеста — только Request::$is_ajax.
Создаем файл application/classes/request.php и пишем:
class Request extends Kohana_Request { public function is_initial() { return $this === Request::instance(); } }
Так как Request::current() возвращает экземпляр основного запроса (Singletone), то достаточно проверить, является ли таковым текущий объект Request — $this. Этот метод пригодится нам в дальнейшем.
Дальше на очереди у нас расширение простого контроллера — контроллер, который будет не просто выполнять какие-то действия, но и работать с View. Назовем его Controller_Front (хотя Kohana предлагает нечто отдаленно похожее под названием Controller_Template — это дело вкуса, от названия ничего не зависит — лишь то, от какого класса Вы будете наследовать остальные контроллеры).
Controller_Front у нас будет обеспечивать разбиение всего View на «обертку» и на «контент». Обертка — это стандартная разметка, характерная для всех страниц нашего проекта — там будет доктайп, подключение стилей и все-все-все, что должно быть на всех страницах. Контент — это результат работы конкретного контроллера. Давайте немного отвлечемся от нашего Controller_Front и сразу их создадим, чтобы понимать, о чем, собственно, речь:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <meta http-equiv="content-type" content="text/html; charset=utf-8" /> <meta name="author" content="Roman Chvanikoff" /> <title><?php echo $title; ?></title> </head> <body> <?php echo $content; ?> </body> </html>
Вот такая у нас будет обертка, в которую будет вкладываться контент ($content), полученный в результате работы контроллера.
Сохраним это как application/views/index.php
Теперь давайте создадим контент-вью для роута, предлагаемого Kohana по-умолчанию, welcome/index
<h1>Welcome!</h1>
и сохраним это как application/views/welcome/index.php
Собственно, к этой строке статьи Вы уже должны понять, если по каким-то причинам не получалось раньше, что есть обертка, а что — контент.
Но вернемся к нашему Controller_Front. Давайте по-умолчанию будем рендерить все в обертку index, если в контроллере имя обертки не было переопределено (мало-ли какая логика работы Вашего приложения).
class Controller_Front extends Controller { /** * @var layout wrapper of content for final output */ public $layout = 'index'; /** * @var content controller-generated output */ public $content; /** * @var errors all logic errors (including Validate errors) should be stored here */ public $errors; /** * @var post Validate object of _POST */ public $post; /** * @var view_path define what folder should be used to generate Views * values: * NULL - View::path will be generated as name of called controller * FALSE - View::path will not affect views generation * string - View::path will have value of the view_path property */ public $view_path = NULL; public function before() { parent::before(); $this->layout = View::factory($this->layout); $this->layout->set_global('user', $this->user); $this->post = Validate::factory($_POST); // Doubts? Look at view_path property definition if (is_null($this->view_path)) { View::$view_path = $this->request->controller; } elseif ($this->view_path) { View::$view_path = $this->view_path; } } public function after() { /** * Clear View "environment" */ View::$view_path = NULL; if ( ! Validate::not_empty($this->errors)) { $this->errors = NULL; } else { // $this->errors should be an array to pass it as argument to View. is_array($this->errors) OR $this->errors = array($this->errors); // $this->errors is a View now $this->errors = View::factory('errors', array('errors' => $this->errors)); } // $this->content can be a simple string or something like that so we check if it is a View file if ($this->content instanceof View) { // Append post-data $this->content->post = $this->post; // Append errors $this->content->errors = $this->errors; } // If request is initial - return layout with attached content if ($this->request->is_initial() AND ! Request::$is_ajax) { // Append content to layout $this->layout->content = $this->content; // Set response $this->request->response = $this->layout; } else { // Set response as controller-generated output $this->request->response = $this->content; } parent::after(); } }
В этом контроллере я, надеюсь, все понятно из комментариев, кроме работы с каким-то View::$view_path — что это такое? Это то, что позволит нам не писать в пределах одного контроллера в разных экшнах строки типа $this->content = View::factory('user/edit'); а заменить их на более компактные $this->content = View::factory('edit');
Давайте, кстати, сам View расширим — иначе как мы это реализуем?
class View extends Kohana_View { /** * @staticvar view_path a directory that will be used to generate views */ public static $view_path = NULL; /** * Sets the view filename. * * $view->set_filename($file); * * @param string view filename * @return View * @throws Kohana_View_Exception */ public function set_filename($file) { $directory = 'views'; if ( ! is_null(View::$view_path)) { $directory .= DIRECTORY_SEPARATOR.View::$view_path; } if (($path = Kohana::find_file($directory, $file)) === FALSE) { throw new Kohana_View_Exception('The requested view :file could not be found in :directory', array( ':file' => $file, ':directory' => $directory, )); } // Store the file path locally $this->_file = $path; return $this; } }
Думаю, что после того, как была расписана работа с этим измененным классом из Controller_Front, останавливаться тут будет излишне, так что сохраняем этот файл как application/classes/view.php, сохраняем Controller_Front как application/classes/controller/front.php и «едем дальше».
Открываем контроллер welcome (application/classes/controller/welcome.php) и меняем extends Controller на extends Controller_Front, а в методе index заменяем строку $this->request->response = 'hello, world!'; на $this->content = View::factory('index');
Теперь, если все сделано верно, открываем браузер, заходим по адресу проекта и видим наш Views/welcome/index.php View-файл, обрамленный Views/index.php лэйоутом.
Послесловие
Статья пролежала в папке «дописать и опубликовать» без малого месяц, но наконец у меня дошли руки хоть как-то упорядочить информацию в ней и, собственно, опубликовать. Если Вы прочитали ее и какие-то моменты остались для вас непонятными, или я допустил ошибки в коде, или вы все сделали по статье, но ничего не заработало — незамедлительно пишите в комментариях, ��твечу при первой возможности. ;)
P.S.: думаю, когда-нибудь у меня дойдут руки и до остальных статей из папки «дописать и опубликовать», поэтому… Эта статья — только начало, дальше сделаем простенькое приложение (пишите пожелания в комментариях) на базе того, что мы успели сделать в этой статье — это позволит оценить удобства, которые мы только что создали.
UPD:
View-файл views/errors.php может быть таким:
<ul class="errors"> <?php foreach ($errors as $error) : ?> <li><?php echo $error; ?></li> <?php endforeach; ?> </ul>
