Как стать автором
Обновить

Kohana 3.0 — упрощаем себе жизнь

Время на прочтение8 мин
Количество просмотров10K
Фреймворк — это хорошо, это здорово, это возможность сэкономить кучу времени на раздумьях над архитектурой будущего приложения, но… Фреймворк как таковой — это каркас. И, на примере Kohana 3.0, о которой в данной статье пойдет речь, каркас этот надо, в той или иной степени, допиливать.
Теперь давайте по-порядку, чем мы сейчас займемся:
  • -Расширим базовый контроллер, добавив в него жизненно необходимые методы и работу с юзерами (которая присутствует в 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>
Теги:
Хабы:
Всего голосов 50: ↑39 и ↓11+28
Комментарии25

Публикации