Русскоязычный форум, посвященный фреймворку Kohana

Все о фреймворке Kohana. Обсуждение уроков, документации.
Текущее время: 19 мар 2024, 09:00

Часовой пояс: UTC + 4 часа [ Летнее время ]




Начать новую тему Ответить на тему  [ Сообщений: 5 ] 
Автор Сообщение
 Заголовок сообщения: Умные контроллеры
СообщениеДобавлено: 23 авг 2013, 01:17 
Не в сети
Бывалый
Аватара пользователя

Зарегистрирован: 02 апр 2013, 16:26
Сообщения: 474
Откуда: Сергиев Посад
При создании контроллеров постоянно приходится сталкиваться с однотипными повторяющимися действиями по созданию и загрузке моделей. Обычно каждый контроллер использует одну основную модель, например, контроллер статей создает модель статей, контроллер товаров для магазина создает модель товаров и т.д. Я решил упростить себе жизнь и создал контроллер, выполняющий всю черновую работу за меня.
Код:
abstract class Controller_ORM extends Controller_Template
{
   /**
    * Name of ORM model.
    * If not specified, based on controller name.
    *
    * @var mixed(string|ORM)
    */
   protected $model;

   /**
    * Request param with model id (parameter for find)
    * For switch off auto model loading set this property empty
    *
    * @var string
    */
   protected $param_model_id = 'id';

   /**
    * A list of actions that don't need create a model
    *
    * @var array
    */
   protected $deny_create_model_actions = array();

   /**
    * A list of actions that don't need auto loading(find by id) a model
    *
    * @var array
    */
   protected $deny_load_model_actions = array();

   /**
    * Automatically executed before the controller action. Can be used to set
    * class properties, do authorization checks, and execute other custom code.
    *
    * @return  void
    */
   public function before()
   {
      parent::before();
      
      if ( ! in_array($this->request->action(), $this->deny_create_model_actions))
      {
         if ( ! $this->model)
         {
            /*
             * Auto detect model name
             * Example: Controller_Blog_Category = Blog_Category(Model_Blog_Category)
             */
            $this->model = strtolower(substr(get_class($this), 11));
         }
         
         // Set id parameter for find
         $id = $this->request->param($this->param_model_id);
         
         // Create model
         $this->model = ORM::factory($this->model, $id);
         
         // Checking loading model
         if ( ! in_array($this->request->action(), $this->deny_load_model_actions) AND ! $this->model->loaded())
         {
            throw HTTP_Exception::factory(404, 'Model :model not loaded',
               array(':model' => $this->model->object_name()));
         }
      }
   }

   /**
    * Automatically executed after the controller action. Can be used to apply
    * transformation to the response, add extra output, and execute
    * other custom code.
    *
    * @return  void
    */
   public function after()
   {
      // Delete model object
      unset($this->model);
      
      parent::after();
   }

} // End Controller_ORM

Упрощенный пример использования:
Код:
class Controller_Category extends Controller_ORM
{
   // Не используем автозагрузку данных для action_list
   protected $deny_load_model_actions = array('list');

   // Выводит список категорий
   public function action_list()
   {
      $this->template->categories = $this->model->find_all();
   }

   // Выводит информацию конкретной категории
   public function action_list()
   {
      $this->template->category = $this->model;
   }
}


На самом деле большинство контроллеров имеет один и тот же функционал, например frontend контроллеры обычно похожи на показанный мной в примере - отображают список элементов или конкретный элемент, backend - производит CRUD(отображение\создание\изменение\удаление) действия на элементами, поэтому можно пойти дальше и создать универсальные контроллеры, для сложных задач расширять их, а совсем нестандартные уже создавать отдельно.

Как будет время напишу такие вот универсальные контроллеры)

_________________
Майкл Джордан играет в баскетбол. Чарльз Мэнсон убивает людей. Я пишу код. У каждого свой талант.


Вернуться к началу
 Профиль  
 
 Заголовок сообщения: Re: Умные контроллеры
СообщениеДобавлено: 18 сен 2013, 00:51 
Не в сети
Бывалый
Аватара пользователя

Зарегистрирован: 02 апр 2013, 16:26
Сообщения: 474
Откуда: Сергиев Посад
Слегка улучшил код. Теперь можно производить поиск сразу по нескольким параметрам и не требуется параметр $deny_load_model_actions, для удобства код автозагрузки модели вынесен в отдельный метод.
Код:
abstract class Controller_ORM extends Controller_Template {

    /**
     * Model name. If not specified, based on controller name.
     *
     * @var string|ORM
     */
    public $model;

    /**
    * Request params, uses for model find rows.
    * Sets as array of param => value
    * [!!] Model will not be created if the list is empty or the first param is NULL.
    *
    * @var array
    */
    public $model_params = array('id' => NULL);

    /**
    * A list of actions that don't need create a model
    *
    * @var array
    */
    public $actions_without_model = array();

    /**
    * Model autoloader
    *
    * @return void
    */
    protected function _autoload_model()
    {
        // Checking list actions without model
        if (in_array($this->request->action(), $this->actions_without_model))
        {
            return NULL;
        }
       
        if (empty($this->model))
        {
            /*
             * Auto detect model name
             * Example: Controller_Blog_Category => Blog_Category => Model_Blog_Category
             */
            $this->model = strtolower(substr(get_class($this), 11));
        }
       
        // Sets model parameters for search
        if ($this->request->param(current($this->model_params)))
        {
            foreach ($this->model_params as $key => $value)
            {
                $this->model_params[$key] = $this->request->param($key, $value);
            }
        }
        else
        {
            $this->model_params = NULL;
        }
       
        // Create model
        $this->model = ORM::factory($this->model, $this->model_params);
       
        // Checking loading model
        if ($this->model_params !== NULL AND ! $this->model->loaded())
        {
            throw HTTP_Exception::factory(404, 'Model :name not loaded',
                array(':name' => $this->model->object_name()));
        }
    }

    /**
    * Called before executing action
    *
    * @return void
    */
    public function before()
    {
        parent::before();
       
        // Create model
        $this->_autoload_model();
    }

} // End Controller_ORM

Упрощенный пример использования:
Код:
Route::set('default', '(<controller>(/<action>(/<slug>)))', array(
        'slug' => '[\w\-]+',
    ))
    ->defaults(array(
        'directory'  => 'Frontend',
        'controller' => 'Element',
        'action'     => 'index',
        'slug'        => NULL,
    ));

class Controller_Frontend_Element extends Controller_ORM
{
    // Параметры запроса, передаваемые в модель
    // slug - ЧПУ id, active - выбираем только активные элементы
    public $model_params = array('slug' => NULL, 'active' => 1);
   
    // Не используем автозагрузку модели для action_config
    public $actions_without_model = array('config');
   
    // Выводит конкретный элемент
    public function action_item()
    {
        $this->template->item = $this->model;
    }
   
    // Выводит список элементов
    public function action_index()
    {
        $this->template->items = $this->model->find_all();
    }
   
    // Конфигурирование страницы
    public function action_config()
    {
        $this->template->config = Kohana::$config->load('item');
    }
}

http://site.ru/element/ Выводит список элементов
http://site.ru/element/item/zelenie_tapki Выводит конкретный элемент
http://site.ru/element/config Конфигурирование страницы

_________________
Майкл Джордан играет в баскетбол. Чарльз Мэнсон убивает людей. Я пишу код. У каждого свой талант.


Вернуться к началу
 Профиль  
 
 Заголовок сообщения: Re: Умные контроллеры
СообщениеДобавлено: 18 сен 2013, 00:56 
Не в сети
Бывалый
Аватара пользователя

Зарегистрирован: 02 апр 2013, 16:26
Сообщения: 474
Откуда: Сергиев Посад
Отложим пока CRUD контроллеры, с ними более-менее все ясно и обсудим вот что:

HMVC Матрёшка

Наконец полностью сформировалась в голове идея организации базового контроллера моей CMS, чем и спешу поделиться :)
Каждая страница представляет собой слой(шаблон) на котором размещены виджеты и сниппеты:
Код:
<div class="g">
    <div class="g-row">
        <header>
            <?=CMS::snippet('header/logo')?>
            <?=CMS::widget('menu/top')?>
            <?=CMS::widget('account')?>
        </header>
    </div>
    <div class="g-row">
        <aside class="g-4"><?=CMS::widget('menu/left')?></aside>
        <article class="g-8"><?=CMS::snippet('main_content')?></article>
    </div>
    <div class="g-row">
        <footer>
            <?=CMS::widget('menu/footer')?>
            <?=CMS::snippet('footer/copyright')?>
        </footer>
    </div>
</div>

    CMS::snippet - сниппет. Представляет собой простой View, напрямую (через View::factory) они не вызываются потому, что данный метод будет реализовывать ряд дополнительных действий, например подгрузку css\js контента сниппета.
    CMS::snippet('main_content') - вывод основного контента также реализован сниппетом.
    CMS::widget - виджет. Реализует запрос(request) к контроллеру виджета и выводит полученный контент, опять же используем метод-обертку т.к. он кроме этого будет иметь дополнительный функционал.

Такой поход позволит легко изменять структуру разделов-страниц, а также добавлять новые. В идеале планирую сделать визуальный редактор дизайна в админке (аля http://jqueryui.com/sortable/#portlets).

Создание сайта будет при таком подходе выглядеть следуюшим образом:

Создание слоёв. Например если дизайн главной страницы, отличается отличается от оформления внутренних, то для этого потребуется создать 2 шаблона.
Добавление страниц(разделов). Каждая страница имеет 4 основных параметра:
    slug - ЧПУ идентификатор (например, `blog` или `about_us`)
    layout - шаблон, используемый в качестве основы
    content - html текст страницы
    controller - контроллер страницы, формирующий основной контент. Имеет более широкий, по сравнению с виджетами, функционал. Например, методы для формирования meta данных страницы, поиска по разделу и создания sitemap(для frontend контроллеров). Данный параметр является необязательным
.
CMS::snippet('main_content') представляет собой текст content + результат запроса к controller'y.

Страницы будут иметь URL примерно следующего вида: `(/<slug>(/<page_action>(/<page_slug>)))`

Например, http://site.ru/myblog/post/html5-lesson_1/ или http://site.ru/about_us/

Принцип работы:
    Вызывается Controller_Page - action_index и выполняется поиск данных страницы по параметру `slug`, используя Model_Page.
    Если у страницы задан параметр controller, то будет выполнен запрос(request).
    URL запроса формируется исходя из оставшихся параметров первичного запроса(slug удаляется) и значения controller. Полученный результат добляется к данных страницы.
    Выполняется рендеринг слоя.
    Формируется и возвращается контент страницы

Например, при вызове `http://site.ru/myblog/post/html5-lesson_1/` происходит поиск страницы `myblog`, для которой задан controller blog(`content/page/blog`) - `content/<directory>/<controller>`, в результате получаем запрос `http://site.ru/content/page/blog/post/html5-lesson_1/`, вызывающий Controller_Page_Blog - action_post, в нем происходит поиск записи с идентификатором `html5-lesson_1`.

Следующим шагом надо будет заменить отдельные контент controller'ы на контент-модули, которые будут группировать имеющийся функционал (например, модуль блога или форума).

А также сделать редактирование content по аналогии с наполнением слоёв сниппетами и виджетами, но это вообще far, far away... :(

Схематический код добавлю попозже, но вроде и так все довольно прозрачно вышло =)

_________________
Майкл Джордан играет в баскетбол. Чарльз Мэнсон убивает людей. Я пишу код. У каждого свой талант.


Вернуться к началу
 Профиль  
 
 Заголовок сообщения: Re: Умные контроллеры
СообщениеДобавлено: 27 сен 2013, 00:40 
Не в сети
Бывалый
Аватара пользователя

Зарегистрирован: 02 апр 2013, 16:26
Сообщения: 474
Откуда: Сергиев Посад
HMVC Матрёшка - пример реализации

Маршруты:
Код:
Route::set('page', '(<slug>(/<subrequest>))', array(
        'slug'       => '[\w\-]+',
        'subrequest' => '\.+',
    ))
    ->defaults(array(
        'directory'  => NULL,
        'controller' => 'Page',
        'action'     => 'item',
        'slug'       => Kohana::$config->load('page.default.slug'),
        'subrequest' => NULL,
    ))
    ->filter(
        function(Route $route, $params, Request $request)
        {
            return $request->is_initial() ? $params : FALSE;
        }
    );

Route::set('default', '(<directory>/<controller>(/<action>(/<id>)))')
    ->defaults(array(
        'directory'  => 'Page',
        'controller' => 'Home',
        'action'     => 'index',
    ));

SQL дамп таблицы со страницами
Код:
CREATE TABLE IF NOT EXISTS `pages` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `title` varchar(255) NOT NULL,
  `slug` varchar(255) NOT NULL,
  `content` text NOT NULL,
  `layout` varchar(255) NOT NULL,
  `controller` varchar(255) NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `slug` (`slug`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 AUTO_INCREMENT=1;

Контроллер страниц (основной)
Код:
class Controller_Page extends Controller_Template
{
    public $template = 'wrapper';

    public function action_item()
    {
        $slug = $this->request->param('slug');
       
        $page = ORM::factory('Page')->where('slug', '=', $slug)->find();
        if ( ! $page->loaded())
        {
            throw HTTP_Exception::factory(404, 'Page :name not found', array(':name' => $slug));
        }
        View::set_global('PAGE', $page);
       
        View::set_global('PROTOCOL', $this->request->protocol());
       
        $this->template->layout = View::factory('layout/'.$page->layout)->render();
    }
} // End Controller_Page


Вид wrapper (обёртка для контента)
Код:
<!DOCTYPE html><?php defined('SYSPATH') OR die('No direct script access.') ?>
<html lang="<?php echo I18n::$lang ?>">
    <head>
        <base href="<?php echo URL::base($PROTOCOL) ?>">
        <title><?php echo $PAGE->title ?></title>
        <!--[if lt IE 9]><script src="https://html5shiv.googlecode.com/svn/trunk/html5.js"></script><![endif]-->
    </head>
    <body>
        <?php echo $layout ?>
    </body>
</html>

Вид layout/home (слой для главной)
Код:
<header><?php echo CMS::snippet('home/header') ?></header>
<main>
    <aside id="left_menu"><?php echo CMS::widget('menu/left') ?></aside>
    <article><?php echo CMS::snippet('content') ?></article>
</main>
<footer><?php echo CMS::snippet('footer') ?></footer>

Вид layout/default (слой для всех остальных страниц)
Код:
<header><?php echo CMS::snippet('default/header') ?></header>
<main>
    <aside id="menu-left"><?php echo CMS::widget('menu/left') ?></aside>
    <article><?php echo CMS::snippet('content') ?></article>
    <aside id="menu-right"><?php echo CMS::widget('menu/right') ?></aside>
</main>
<footer><?php echo CMS::snippet('footer') ?></footer>

Вид snippet/content (сниппет основного контента)
Код:
<?php if ($PAGE->content): ?>
<div class="header">
    <?php echo $PAGE->content ?>
</div>
<?php endif ?>
<?php if ($PAGE->controller): ?>
<div class="footer">
    <?php echo CMS::request('page/'.$PAGE->controller.'/'.Request::initial()->param('subrequest')) ?>
</div>
<?php endif ?>

Вспомогательный класс CMS
Код:
abstract class Kohana_CMS
{
    /**
     * Create identification tag(key) uses the methods attributes.
     * Basically used for get or set cache.
     *
     * @static
     * @return  string
     */
    public static function generate_tag()
    {
        return I18n::$lang.'_'.sha1(serialize(func_get_args()));
    }
   
    /**
     * Request bulder
     *
     */
    public static function request($name, array $data = array(),
        $method = NULL, $allow_external = FALSE, Cache $cache = NULL)
    {
        $options = array();
        if ( ! is_null($cache) AND Kohana::$caching)
        {
            $options['cache'] = HTTP_Cache::factory($cache);
        }
       
        $request = Request::factory($name, $options, $allow_external);
       
        if (is_null($method))
        {
            $method = Request::GET;
        }
        $request->method($method);
       
        switch ($method)
        {
            case Request::GET:
                $request->query($data);
                break;
            case Request::POST:
                $request->post($data);
                break;
        }
       
        return $request->execute();
    }
   
    public static function snippet($name, array $data = array(), $caching = TRUE)
    {
        $name = 'snippet'.DIRECTORY_SEPARATOR.str_replace(array(' ', '_', '\\'), DIRECTORY_SEPARATOR, $name);
       
        if ($caching AND Kohana::$caching)
        {
            $cache = Cache::instance('snippet');
            $tag = CMS::generate_tag($name, $data);
            if ($snippet = $cache->get($tag))
            {
                return $snippet;
            }
        }
       
        $snippet = View::factory($name, $data)->render();
       
        if (isset($cache))
        {
            $cache->set($tag, $snippet);
        }
       
        return $snippet;
    }

    public static function widget($name, array $data = array(), $caching = TRUE)
    {
        if ($caching AND Kohana::$caching)
        {
            return CMS::request('widget/'.$name, $data, NULL, FALSE, Cache::instance('widget'));
        }
        else
        {
            return CMS::request('widget/'.$name, $data, NULL, FALSE);
        }
    }
}

_________________
Майкл Джордан играет в баскетбол. Чарльз Мэнсон убивает людей. Я пишу код. У каждого свой талант.


Вернуться к началу
 Профиль  
 
 Заголовок сообщения: Re: Умные контроллеры
СообщениеДобавлено: 24 окт 2013, 15:17 
Не в сети
Администратор
Аватара пользователя

Зарегистрирован: 24 июл 2012, 18:00
Сообщения: 701
Откуда: Murom, Russia
Сколько ты всего хорошего понаписал за моё отсутствие! :) Молодец, так держать :)


Вернуться к началу
 Профиль  
 
Показать сообщения за:  Поле сортировки  
Начать новую тему Ответить на тему  [ Сообщений: 5 ] 

Часовой пояс: UTC + 4 часа [ Летнее время ]


Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 3


Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете добавлять вложения

Найти:
Перейти:  
cron
Все о фреймворке Kohana  | 
Powered by phpBB® Forum Software © phpBB Group