Пример доски объявлений на Kohana


    Kohana — довольно молодой PHP фреймворк, форк CI, всецело завязанный на ООП. К достоинствам Kohana можно отнести использование всех возможностей PHP5 на 100%, высокую скорость работы, «легковесность» и простоту как использования, так и изучения. Из минусов отчетливо выделяется небольшое комьюнити, как следствие, не шибко качественная документация и небольшое количество модулей и библиотек.

    Не так давно своё знакомство с фреймворками я начинал именно с Kohana и, надо сказать, я был удивлен, насколько легко он мне поддался. Думал будет намного сложнее. Но очень сказывалась нехватка документации и примеров кода. Зарывшись в маны и разбор исходников, через некоторое время, я восполнил интересующие меня пробелы относительно Kohana, и поэтому решил написать эту статью, дабы другим хабралюдям, оказавшимся в схожей ситуации не пришлось по ночам не досыпать и лечить головные боли.

    Под катом пример доски объявлений, написанный с помощью Kohana, возможно местами он не претендует на рациональность и здравый смысл, но всё-же я надеюсь услышать конструктивную критику.

    Статья рассчитана на людей, имеющих понятие об MVC и ООП, но не имевших, либо мало имевших, дело с фреймворками.


    Начнем


    Недавно передо мной встала задача написать небольшую доску объявлений на базе новостного сайта, где пользователи могли бы оставлять свои объявления о купле, продаже и прочем. Написать очень быстро. Честно говоря, до этого случая весь мой опыт с фреймворками заключался в установке сэндбокса Symfony и последующем его удалением. А всё, что я знал о фреймворках, так это то, что большинство из них «используют MVC» и то, что они очень облегчают жизнь. Т.к. в то время я уже почитывал Хабр, мне почему-то запала в голову одна из публикаций, которая утверждала, что фреймворк Kohana «cоздан быть легким, быстрым и простым в использовании». Думаю именно поэтому я выбрал его. Итак,

    Что мы хотим получить в результате?


    Мы хотим, чтобы у нас были такие возможности в отношении пользователей:
    • авторизация/регистрация пользователей
    • просмотр объявлений всеми пользователями;
    • добавлять объявления могут только зарегистрированные;
    • править/удалять могут только авторы и админы;

    Условия для категорий:
    • существуют главные категории;
    • существуют под-категории;
    • под-категории содержат объявления;


    С тем, что от нас нужно, мы определились, теперь придумаем, как мы это будем реализовывать.

    Для регистрации пользователей будем использовать модуль Auth, который входит в стандартную поставку версии 2.3. Для его использования создадим в базе несколько таблиц:
    # в этой таблице мы будем хранить пользователей<br/>
    CREATE TABLE IF NOT EXISTS `users` (
     `id` int(11) unsigned NOT NULL auto_increment,
     `username` varchar(32) NOT NULL default '',
     `password` char(50) NOT NULL default '',
     `email` varchar(127) NOT NULL default '',
     `join` int(10) unsigned NOT NULL default '0',
     `last_login` int(10) unsigned NOT NULL default '0',
     `logins` int(10) unsigned NOT NULL default '0',
     PRIMARY KEY (`id`),
     UNIQUE KEY `uniq_username` (`username`),
     UNIQUE KEY `uniq_email` (`email`)
    ) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=3;
    INSERT INTO `users` (`id`, `username`, `password`, `email`, `join`, `last_login`, `logins`) VALUES (1, 'admin', '098f6bcd4621d373cade4e832627b4f6', 'example@example.com', 1215075372, 0, 0);

    # тут у нас будут описания прав пользователей<br/>
    CREATE TABLE IF NOT EXISTS `roles` (
     `id` int(4) unsigned NOT NULL auto_increment,
     `name` varchar(32) NOT NULL,
     `description` varchar(255) NOT NULL,
     PRIMARY KEY (`id`),
     UNIQUE KEY `uniq_name` (`name`)
    ) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=3;
    INSERT INTO `roles` (`id`, `name`, `description`) VALUES (1, 'login', 'Зарегистрированный');
    INSERT INTO `roles` (`id`, `name`, `description`) VALUES (2, 'admin', 'Админ');

    # эта таблица будет содержать привязку прав к пользователю<br/>
    CREATE TABLE IF NOT EXISTS `roles_users` (
     `user_id` int(10) unsigned NOT NULL,
     `role_id` int(10) unsigned NOT NULL,
     PRIMARY KEY (`user_id`,`role_id`),
     KEY `fk_role_id` (`role_id`)
    ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
    INSERT INTO `roles_users` (`user_id`, `role_id`) VALUES (1, 2);

    # a эта таблица будет использоваться модулем Auth для хранения залогиненых пользователей<br/>
    DROP TABLE IF EXISTS `d_user_tokens`;
    CREATE TABLE IF NOT EXISTS `user_tokens` (
     `id` int(11) unsigned NOT NULL auto_increment,
     `user_id` int(11) unsigned NOT NULL,
     `user_agent` varchar(40) NOT NULL,
     `token` varchar(32) NOT NULL,
     `created` int(10) unsigned NOT NULL,
     `expires` int(10) unsigned NOT NULL,
     PRIMARY KEY (`id`),
     UNIQUE KEY `uniq_token` (`token`),
     KEY `user_id` (`user_id`)
    ) ENGINE=MyISAM DEFAULT CHARSET=utf8;


    Дальше создадим таблицы для категорий и объявлений. При этом все категории будут иметь поле parent_id, которое будет указывать на id родительской категории у которых parent_id будет равен 0.
    CREATE TABLE IF NOT EXISTS `categories` (
      `id` int(10) unsigned NOT NULL auto_increment,
      `parent_id` int(10) unsigned NOT NULL default '0',
      `name` varchar(150) character set utf8 NOT NULL,
     PRIMARY KEY (`id`),
     KEY `parent_id` (`parent_id`)
    ) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=14;
    INSERT INTO `categories` (`id`, `parent_id`, `name`) VALUES (1, 0, 'Недвижимость');
    INSERT INTO `categories` (`id`, `parent_id`, `name`) VALUES (2, 1, 'Квартиры');
    INSERT INTO `categories` (`id`, `parent_id`, `name`) VALUES (3, 1, 'Комнаты');
    INSERT INTO `categories` (`id`, `parent_id`, `name`) VALUES (4, 1, 'Дома, дачи');
    INSERT INTO `categories` (`id`, `parent_id`, `name`) VALUES (5, 0, 'Авто');
    INSERT INTO `categories` (`id`, `parent_id`, `name`) VALUES (6, 5, 'Легковые автомобили');
    INSERT INTO `categories` (`id`, `parent_id`, `name`) VALUES (7, 5, 'Запчасти, аксессуары');
    INSERT INTO `categories` (`id`, `parent_id`, `name`) VALUES (8, 5, 'Мотоциклы, мопеды');
    INSERT INTO `categories` (`id`, `parent_id`, `name`) VALUES (9, 0, 'Работа');
    INSERT INTO `categories` (`id`, `parent_id`, `name`) VALUES (10, 9, 'Админ персонал');
    INSERT INTO `categories` (`id`, `parent_id`, `name`) VALUES (11, 9, 'ИТ, интернет');
    INSERT INTO `categories` (`id`, `parent_id`, `name`) VALUES (12, 9, 'Работа на дому');
    INSERT INTO `categories` (`id`, `parent_id`, `name`) VALUES (13, 9, 'Другие сферы');

    CREATE TABLE IF NOT EXISTS `items` (
     `id` int(10) unsigned NOT NULL auto_increment,
     `title` varchar(250) character set utf8 NOT NULL,
     `category_id` int(10) unsigned NOT NULL default '0',
     `content` text NOT NULL,
     `user_id` int(10) unsigned NOT NULL,
     `datepub` int(10) unsigned NOT NULL default '0',
     PRIMARY KEY (`id`),
     KEY `title` (`title`)
    ) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=2;
    INSERT INTO `items` (`id`, `title`, `category_id`, `content`, `user_id`, `datepub`) VALUES (1, 'Тестовое объявление', 2, 'Это просто объявление. Таких тут вскоре будет много', 1, 1229251545);


    Модели


    Модели — это часть MVC, которая представляет данные и реагирует на запросы, являясь конечной инстанцией между скриптом и БД.

    Теперь о моделях, которые мы будем использовать. Для работы с пользователями у нас есть стандартные модели модуля Auth, для обращения к статьям и категориям, создадим свои — создайте файл /application/models/category.php и пропишите в нем следующий класс:
    class Category_Model extends ORM_Tree{
      protected $children = 'categories';
      protected $has_many = array('items');
    }

    Теперь создайте ещё один файл /application/models/item.php с таким классом:
    class Item_Model extends ORM{
      protected $has_one = array('user');
      protected $belongs_to = array('category');
    }

    Теперь на русском — для модели категорий мы создали расширенный ORM (об этом немного ниже) класс таблицы `categories` с древовидной структкрой (ORM_Tree), у записи которой могут быть потомки в виде записей из этой же таблицы (protected $children = `categories`, по умолчанию родитель — это поле `parent_id`) и каждая запись которой может иметь много вложенных записей таблицы `items`. А для модели объявлений у нас получился простой ORM класс таблицы `items`, которая содержит одну запись из таблицы `users` и пренадлежит таблице `categories`

    Думаю, стоит прояснить, что названия моделей должны быть в единичном числе, т.е. если используется таблица items, то название модели должно быть Item_Model. Также дела обстоят и с объявлениями переменных модели, если предполагается множество объектов ($children, $has_many), то аргумент должен быть во множественном числе, если же объект один ($has_one, $belong_to), то и число — единичное.


    Контроллеры


    С моделями вроде разобрались, теперь о контроллерах — они служат для отображения информации, являясь прослойкой между моделями и видами.

    Какие контроллеры нам нужны? Думаю такие:
    1. для главной страницы, заодно и категорий
    2. для под-категорий
    3. для просмотра объявлений
    4. для входа пользователей
    5. для их регистрации

    Поскольку первый со вторым, также третий с четвертым, являются логически взаимосвязанными, объединим их в один, для каждого типа и в итоге получаем три контроллера — category.php, items.php и user.php.

    Дальше много кода, большинство из которого интуитивно понятна, и занакома людям, работавшим с ORM и MVC.

    Что такое ORM?
    Думаю, что если Вы дочитали до сюда, то вопросов по MVC у Вас возникнуть не должно, а вот в отношении ORM они вполне могут появиться. Википедия нам говорит, что ORM — это технология программирования, которая связывает базы данных с концепциями объектно-ориентированных языков программирования, создавая «виртуальную объектную базу данных». Что это значит? Это значит, что вызвав метод factory класса ORM с параметром, к примеру, 'table', мы получим объект, проекцию таблицы `tables` (о множественных и единичных числах я говорил немного выше), и место того, чтобы делать SQL запросы к базе через расширение, или какой-либо абстрактный класс, мы можем обращаться к методам и свойствам вызваного класса. Т.е. например, выборка записей с id равным 5 из базы произойдет не привычным $db->query('SELECT * FROM `table` WHERE (`id` = 5) '), а обращением к ORM проекции — ORM::factory('table')->where('id', 5)->find_all(). Но в отличии от простого абстрактного доступа, ORM предоставляет массу вкусностей, которые сложно понять, не попробовав. Так, если речь идет именно об id, то запись можно получить ещё проще — ORM::factory('table', 5). На примере наших вышеобозначенных моделей, мы можем получить имя категории в которой лежит объявление с id = 5 вызовом ORM::factory('item', 5)->category->name, а не писать кучу SQL запросов.


    category.php:
    class Category_Controller extends Template_Controller {<br>  public $template = 'index';<br><br>  public function index() { // главная страница<br>    $_result = '';<br>    $_cats = array();<br>    $categories = ORM::factory('category')->where('parent_id', 0)->find_all();<br>    foreach($categories as $l) {<br>      $_tmp = new View('category');<br>      $_tmp->id = $l->id;<br>      $_tmp->name = $l->name;<br>      $_tmp->children = $l->children;<br>      $_cats[] = $_tmp;<br>    }<br>    $_result = new View('category');<br>    $_result->cats = $_cats;<br>    $this->template->content = $_result;<br>  }<br><br>  public function view($params) { // прсмотр категории<br>    $_result = '';<br>    $categories = ORM::factory('category', $params)->children;<br>    foreach($categories as $l) {<br>      foreach ($l->items as $n) {<br>        $_tmp = new View('item');<br>        $_tmp->content = $n;<br>        $_result.= $_tmp;<br>      }<br>    }<br>    $this->template->content = $_result;<br>  }<br><br>  public function viewsub($params) { // просмотр под-категории<br>    $_result = '';<br>    $categories = ORM::factory('category', $params);<br>    foreach($categories->items as $l) {<br>      $_tmp = new View('item');<br>      $_tmp->content = $l;<br>      $_result.= $_tmp;<br>    }<br>    $this->template->content = $_result;<br>  }<br>}

    В методе index мы собираем массив главных категорий с объектами подкатегорий в нем и кол-вом объявлений(count) в подкатегориях и выводим это всё дело через вид category, который будет описан в главе «Виды».

    Метод view показывает нам содержимое всех подкатегорий категории с id, переданным в параметре (как он туда попадёт — в главе «Окончательные настройки»). Также работает метод viewsub с разницей в том, что он применим для подкатегорий.

    Теперь контроллер объявлений, items.php:
    class Items_Controller extends Template_Controller {<br><br>  public $template = 'index';<br><br>  public function index() { // редирект на главную, если просто вызван контроллер<br>    url::redirect('/index');<br>  }<br><br>  public function view($arg) { // отображение статьи с id = $arg<br>    $_item = ORM::factory('item', $arg);<br>    $this->template->content = new View('item');<br>    $this->template->content->content = $_item;<br>  }<br><br>  public function edit($arg) { // правка статьи c id = $arg<br>    $_tmp = ORM::factory('item', $arg);<br><br>    if(Auth::instance()->logged_in('admin') || (Auth::instance()->get_user() &&<br>      (Auth::instance()->get_user()->id == $_tmp->user_id))) { // продолжать только если пользователь автор или админ<br>      $category = array();<br>      // наполняем список категорий<br>      foreach(ORM::factory('category')->where('parent_id', 0)->find_all() as $l) {<br>        foreach($l->children as $n) {<br>          $category[$l->name][$n->id] = $n->name;<br>        }<br>      }<br>      // создадим класс для работы с формами<br>      $form = new Forge(url::current());<br>      $form->set_attr('method', 'post');<br>      $form->input('title') // создаем тег <input id = 'title' /><br>           ->label('Заголовок') // делаем для него <label for='title'>Заголовок</label><br>           ->rules('required|length[3,40]') // правила валидации - обязательное, от 3 до 40 символов<br>           ->value($_tmp->title); // и присваиваем ему значение из $_tmp->title<br>      $form->textarea('addtext')<br>           ->label('Текст')<br>           ->rules('required')<br>           ->value($_tmp->content);<br>      $form->dropdown('category')<br>           ->label('Категория')<br>           ->options($category)<br>           ->selected($_tmp->category_id);<br>      $form->submit('Править');<br><br>      // вот такая вот простая валидация формы<br>      if ($form->validate()) {<br>        // создаем и заполняем объект ORM<br>        $new = ORM::factory('item', $arg);<br>        $new->title = $form->inputs['title']->value;<br>        $new->content = $form->inputs['addtext']->value;<br>        $new->category_id = $form->inputs['category']->value;<br>        // а после - сохраняем<br>        $new->save();<br>        url::redirect('/');<br>      }<br>      // отправка формы виду<br>      $this->template->content = $form->render();<br>    } else {<br>      $this->template->content = "Вы не зарегистрированы";<br>    }<br>  }<br><br>  public function add() { // добавление статьи<br>    if(Auth::instance()->logged_in()) { // только для зарегистрированых пользователей<br>      $category = array();<br>      foreach(ORM::factory('category')->where('parent_id', 0)->find_all() as $l) {<br>        foreach($l->children as $n) {<br>          $category[$l->name][$n->id] = $n->name;<br>        }<br>      }<br>      $form = new Forge(url::current());<br>      $form->set_attr('method', 'post');<br>      $form->input('title')<br>           ->label('Заголовок')<br>           ->rules('required|length[3,40]');<br>      $form->textarea('addtext')<br>           ->label('Текст')<br>           ->rules('required');<br>      $form->dropdown('category')<br>           ->label('Категория')<br>           ->options($category)<br>           ->selected(0);<br>      $form->submit('Добавить');<br>      if ($form->validate()) {<br>        $new = ORM::factory('item');<br>        $new->title = $form->inputs['title']->value;<br>        $new->content = $form->inputs['addtext']->value;<br>        $new->category_id = $form->inputs['category']->value;<br>        // вот так инстанция класса Auth дает доступ к пользовательским данным<br>        $new->user_id = Auth::instance()->get_user()->id;<br>        $new->datepub = time();<br>        $new->save();<br>        url::redirect('/');<br>      }<br>      $this->template->content = $form->render();<br>    } else {<br>      $this->template->content = "Вы не зарегистрированы";<br>    }<br>  }<br><br>  public function delete($arg) { // удаление статьи<br>    $new = ORM::factory('item', $arg);<br>    if(Auth::instance()->logged_in('admin') || (Auth::instance()->get_user() &&<br>      (Auth::instance()->get_user()->id == $new->user_id))) { // только автор или админ<br>      $new->delete();<br>      url::redirect('/');<br>    } else {<br>      $this->template->content = "Вы не зарегистрированы";<br>    }<br>  }<br>}

    Думаю, единственный, до этого времени не встречавшийся нам класс — это Forge. Он используется для облегчения работы с формами, но, к сожалению, он исключен из стандартной поставки Kohana. Где его взять, я расскажу ниже.

    Ну и контроллер пользователей, user.php:
    class User_Controller extends Template_Controller {<br><br>  public $template = 'index';<br><br>  public function login() { // вход<br>    if (Auth::instance()->logged_in()) { // если не зарегистрированы, то на главную<br>      url::redirect('/');<br>    } else {    // создание формы<br>      $form = new Forge;<br>      $form->set_attr('method', 'post');<br>      $form->input('username')<br>           ->label('Логин')<br>           ->rules('required|length[4,32]');<br>      $form->password('password')<br>           ->label('Пароль')<br>           ->rules('required|length[4,40]');<br>      $form->submit('Войти');<br>      if ($form->validate()) {<br>        $user = ORM::factory('user', $form->username->value);<br>        // если вход успешен, то на главную<br>        if (Auth::instance()->login($user, $form->password->value)) {<br>          url::redirect('/');<br>        } else {<br>          // если нет, то вывести ошибку<br>          $form->password->add_error('login_failed', 'Неверное имя пользователя, или пароль.');<br>        }<br>      }<br>    }<br>    $this->template->content = $form->render();<br>  }<br><br>  public function logout() { // выход<br>    if(Auth::instance()->logged_in()) {<br>      Auth::instance()->logout(TRUE);<br>    }<br>    url::redirect('/');<br>  }<br><br>  public function register() { // регистрация<br>    if(Auth::instance()->logged_in()) {<br>      url::redirect('/');<br>    } else {<br>      $form = new Forge(url::current(), 'Регистрация');<br>      $form->set_attr('method', 'post');<br>      $form->input('username')<br>           ->label('Логин')<br>           // допускается только пароль из латиницы, с цифрами знаками '_' и '-' от 4 до 32 символов<br>           ->rules('required|length[4,32]|valid_alpha_dash');<br>      $form->password('password')->label('Пароль');<br>      $form->password('password2')<br>           ->label('Опять пароль')<br>           ->rules('required|length[6,40]|valid_alpha_dash')<br>           // это поле должно совпадать с полем 'password'<br>           ->matches($form->password);<br>      $form->input('email')<br>           ->label('E-Mail')<br>           ->rules('required|valid_email');<br>      $form->submit('Регистрация');<br>      if ($form->validate()) {<br>        $user = ORM::factory('user', $form->username->value);<br>        // проверяем, нет ли уже такого пользователя<br>        if (!$user->username_exists($form->username->value)) {<br>          $user->username = $form->username->value;<br>          $user->password = $form->password->value;<br>          $user->email = $form->email->value;<br>          // добавляем пользователя и ставим для него права<br>          if($user->save() && $user->add(ORM::factory('role', 'login'))) {<br>            Auth::instance()->login($user, $form->password->value);<br>            url::redirect('/');<br>          }<br>        }<br>      }<br>    }<br>    $this->template->content = $form->render();<br>  }<br>}

    Интересный момент при создании формы регистрации, а именно метод matches() объекта, который возвращает метод Forge password. При обращении к нему, параметром нужно указать другой объект password, при несоответствии значений которых скрипт выдаст ошибку.

    Виды


    Виды (представления), отвечают за отображение информации и являются конечной прослойкой между пользователем и приложением.
    Теперь немного о видах. Всего в нашем движке мы использовали три вида, поочередно о каждом:

    category.php:
    <?php foreach($cats as $content): ?><br> <div class="category"><br>  <h3><a href="/category/<?php echo $content->id ?>"><?php echo $content->name ?></a>/h3><br>  <ul><br>  <?php foreach($content->children as $l): ?><br>   <li><a href="/subcategory/<?php echo $l->id ?>"><?php echo $l->name ?></a></li><br>  <?php endforeach; ?><br>  </ul><br> </div><br><?php endforeach; ?>

    Как Вы помните, мы ему передавали массив с категориями и подкатегориями, который он и обрабатывает, в цикле выводя их все.

    index.php:
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><br><html xmlns="http://www.w3.org/1999/xhtml"><br><head><br> <title>Kohana</title><br></head><br><body><br>  <div id="enter" style="float:right"><br>   <?php if(Auth::instance()->logged_in()) { ?><br>    Привет, <b><?php echo Auth::instance()->get_user()->username ?></b><a href="/logout">Выход</a><br>   <?php } else { ?><br>    <a href="/login">Войти</a><a href="/register">Регистрация</a><br>   <?php } ?><br>  </div><br>  <div id='links'><br>   <a href="/">Главная</a><br>   <a href="/add">Добавить</a><br>  </div><br>  <div id='content'><br>   <?php echo $content ?><br>  </div><br></body><br></html>

    Это «обвертка» для всех страниц, которые мы отображаем. В блоке enter у нас стоит такой-себе триггер, который, в зависимости от статуса пользователя отображает или его имя со ссылкой на выход, или ссылки на вход и регистрацию.

    item.php:
    <div class="item"><br> <?php  if(Auth::instance()->logged_in('admin') || (Auth::instance()->get_user() &&<br>           Auth::instance()->get_user()->id == $content->user_id))  { ?><br>  <span style="float:right"><br>   <a href="/items/edit/<?php echo $content->id ?>">Править</a><br>   <a href="/items/delete/<?php echo $content->id ?>">Удалить</a><br>  </span><br> <?php } ?><br> <h1><?php echo html::anchor('/' . $content->id, html::specialchars($content->title)) ?></h1><br> <div class="other">Опубликовано <?php echo date("j-M-Y ", $content->datepub) ?><br>           пользователем <b><?php echo html::specialchars($content->>user->username) ?></b><br> </div><br> <div class="news"><br> <?php<br>  echo text::auto_p($content->content);<br> ?><br> </div><br></div>

    Этот вид показывает нам, само объявление, выводя ссылки на удаление и редактирование в случае, если пользователь админ или автор объявления. Также можно заметить использование двух хелперов — html и text. html::specialchars делает строку «безопасной», html::anchor делает ссылку — первый параметр адрес, второй — текст, ну а text::auto_p автоматически добавляет абзацы к plain text, как сказано в офф. документации «nl2br() on steroids».

    Окончательные настройки


    Теперь немного по-настраиваем нашу «систему» перед запуском.

    Создайте в папке /application/config/ файл database.php и пропишите в нем следуещее:
    $config['default']['connection'] = array(
        'type'   => 'mysql',
        'user'   => 'пользователь',
        'pass'   => 'пароль',
        'host'   => 'сервер',
        'database' => 'имя базы',
      );

    Это, как Вы поняли, настройки для MySQL.

    Далее, откройте файл config.php в той же папке и приведите массив $config['modules'] к виду
    $config['modules'] = array
      (
        MODPATH.'auth',
        MODPATH.'forge',
      );

    Также следует изменить $config['site_domain'] на '/', если директории Kohana лежат у Вас в корне сайта. И настоятельно рекомендую установить значение $config['index_page'] в пустую строку, в ином случае, Kohana будет генерировать относительные ссылки с вставкой /index.php/ в URL (прим.: site.com/index.php/mail).

    А теперь создайте файл routes.php и пропишите в нем
    $config['_default'] = 'category';
    $config['category/([0-9]+)'] = 'category/view/$1';
    $config['subcategory/([0-9]+)'] = 'category/viewsub/$1';
    $config['add'] = 'items/add';
    $config['items/([0-9]+)'] = 'items/view/$1';
    $config['edit/([0-9]+)'] = 'items/edit/$1';
    $config['delete/([0-9]+)'] = 'items/delete/$1';
    $config['login'] = 'user/login';
    $config['logout'] = 'user/logout';
    $config['register'] = 'user/register';
    $co
    Поделиться публикацией

    Похожие публикации

    Комментарии 82

    • НЛО прилетело и опубликовало эту надпись здесь
        0
        так в блог Кохана переноси быстро!))
        Нужно продвигать сей чудесный фреймфорк. ато у нас он почему-то еще относительно не популярен.
        А и спасибо за статью. Я бы сам тоже написал что-нить. Но все времени и сил не хватает.
          0
          Перенес =)
          0
          Мое первое знакомство прошло на ура, только после того как они обновили фреймворк до новой версии модуль генерации форм убрали из пакета. На етом мой интерес и закончился )
            +7
            Забавное название у CMS. Можно еще одну систему управления создать, которой пользоваться иногда для проектов на стороне и назвать ее «Kohanka» ;)
              +6
              :) Дело в том, что не все знают украинский язык ;)
              Для справки: «Кохана» на украинском означает «Любимая», а вот «Коханка» — «Любовница»

              Так, на всякий случай ;)
                +3
                Главное — чтобы с Kohank'ой не пришлось заниматься тем, для чего обычно держат любовниц ^_^
                • НЛО прилетело и опубликовало эту надпись здесь
                    +3
                    Только не в моск… (с)
              0
              Поясни пожалуйста
              $categories = ORM::factory('category')->where('parent_id', 0)->find_all();
              foreach($categories as $l) {
              ...
              foreach($l->children as $n) {
              $_tmp->count+= ORM::factory('item')->where('category_id', $n->id)->count_all();
              }
              ...
              }

              Каждое обращение к ORM это один запрос к базе?
              Если да, то наверное было бы логичным сделать это всё одним запросом чем многими в цикле.
                0
                Дело в том, что для подсчета кол-ва всех записей в подкатегория, в любом случае необходим их перебор, так, что кол-во запросов не измениться. Тем более, в первом вызове ORM мы получаем список главных категорий, то мы никак не сможем его перебрать из этой же инстанции.

                Хотя, при действительно большем кол-ве подкатегорий (у меня в исходном проекте их 105) запросов к базе будет не меньше, так-что думаю вставить в items колонку с главной категорией, которой принадлежит подкатегория. Если у кого-то есть лучшие решения — с удовольствием выслушаю =)
                  0
                  *и мы никак не сможем перебрать из этой же инстанции все объявления, только подкатегории.
                  • НЛО прилетело и опубликовало эту надпись здесь
                      +2
                      Если просто на sql, то в принципе посчитать можно таким запросом.
                      SELECT count(*) as count, category_id from items group by category_id
                        0
                        Дело в том, что если дать такой запрос базе через ORM, то в итоге мы получаем не массив, как должно было бы быть, а целое число, кол-во объявлений в подкатегории самого первого объявления. Вариант ниже, с IN, к сожалению, тоже не подходит.

                        Происдел всю ночь, так и не придя к какому-либо выводу, видимо придется всё-таки добавить поле в исходный проект, если «с утра» ничего в голову не придет, а с примером — буду думать…
                        • НЛО прилетело и опубликовало эту надпись здесь
                            0
                            Согласен, из примера пока этот кусок уберу вообще.

                            В принципе, сделал реализацию, сократив запросы до 32х, хотя тоже считаю что это слишком много, да и выполнено в таком грязном виде, что лучше думать, что её вообще нет.

                            Темболее есть все причины думать, что можно ограничится 12 запросами, при довольно изящной реализации, но пока не знаю точно как, будем работать =)
                            0
                            А почему бы вам не сделать запрос в методе orm модели, через $this->db->query()?
                              0
                              В том то и дело, что хотелось бы, для наглядности, реализовать это всё, не обращаясь на прямую к базе. Всё только через ORM.

                              Тут ведь не в функционале суть, а в его реализации.
                          0
                          А если вам не подходит GROUP, а нужно из определенных категорий, то используйте
                          SELETC BLA...BLA FROM BLA WHERE CategoryId IN ('1','2','3')
                            +1
                            Да, я это и имел ввиду.
                            С каждым днем всё больше убеждаюсь в неоправданности ORM и ActiveRecords.
                        +1
                        Вау, хорошее вступление, в фреймворк но ещё для самых торопливых, пожалуйста, добавьте к статье архив с вашей субдирректорией application.

                        Плюсик вам в карму, надеюсь что будете ещё писать о кохане :)
                          +1
                          Пожалуста, прикрепил :)
                          –6
                          Если бы вы по больше познакомились с симфони, то возможно на это изучать не стали. Имхо не лучший фремворк для изучения. Новичкам вообще не следует с него начать изучение фреймворков.
                          Пример показывает, что вы смогли разобраться в ней, но не все как вы.
                            –1
                            В ближайших планах ознакомиться именно с симфони, надеюсь будет ценный опыт.

                            По поводу сложности — к сожалению я с другими фреймворкими не сталкивался, а Kohana выбрал поддавшись лишь минутной слабости и, хочу заметить, ниразу не пожалел. А статьей как-раз и хочу помочь, как Вы сказали, не таким, как я =)
                            0
                            Я не вникал особо в код, но сразу в глаза бросились 3 момента:

                            1. Названия таблиц без префиксов. Из в принципе нельзя использовать или тут только для примера?
                            2. А чего при создании таблиц где-то указано DEFAULT CHARSET=utf8; а где-то нет? Ну понятно, для примера, но всеже если этот пример выполнить в баде где по дефолту Win-1251 установлено, что тогда будет? Не съедут ли данные?
                            3. ORM_Tree работает с деревьями на основе parent_id? Только с ними? Просто не самый лучший способ хранить деревья.

                            Да, и во вьювах на мой взгляд конструкции
                            <?php if(Auth::instance()->logged_in()): ?>

                            <?php else: ?>

                            <?php endif;>

                            более понятны, нежели

                            <?php if(Auth::instance()->logged_in()) { ?>

                            <?php } else { ?>

                            <?php } ?>


                            но это мое личное мнение.
                              0
                              Префиксы, конечно же, можно использовать, задаются они в config/database.php значением $config['default']['table_prefix'] и доставляются, естественно, автоматом. А в коде — просто для примера, чтобы не усложнять.

                              С DEFAULT CHARSET — виноват, сейчас исправлюсь.

                              По поводу ORM_Tree — нет, в описании модели класса можно объявить свойство protected $parent_key, которое будет содержать имя колонки с id родителя.

                              А шаблоны — не раз уже затрагивалась эта тема, но мне так удобнее, это просто пример =)
                                0
                                По поводу ORM_Tree — нет, в описании модели класса можно объявить свойство protected $parent_key, которое будет содержать имя колонки с id родителя.


                                Вы, кажется, не поняли вопроса. Я спрашивал не про имя колонки, а про алгоритм хранения дерева. Тут используется алгоритм на основе хранения id родителя (parent_id), что вообще-то часто достаточно ресурсоемко. Кроме этого существуют другие, менее ресурсоемкие алгоритмы (Depth-Level, Nested Set). Вопрос — поддерживае ли их ORM_Tree?
                                  0
                                  Прошу прощения. Насколько я понял из кода, ORM_Tree — это расширенный класс ORM, а ORM_Tree::parent просто возвращает запись, в которой id равен значению $parent_key текущего объекта. Сообветственно, ORM_Tree::children вернет объект с записями, у который id текщего объекта заявлен как $parent_key.

                                  Хотя, существует переписаный вариант библиотеки от CI MPTT для Kohana, но я с ним дела, к сожалению, не имел.
                                +1
                                А почему везде советуют использовать префиксы?
                                Единственный плюс вижу в том, что бы в одной базе хранить данные нескольких проектов, но по моему уже 90% хостеров даже по самым дешевым тарифам дают 3-5 бд.
                                А минусом является то, что надо не забыть его подставлять в запросы.
                                  +1
                                  У 90% из этих 90% :) количество БД соответствует (а то и меньше) количеству сайтов на аккаунте, а значит без префиксов (точнее без любого механизма избежания коллизий в именовании таблиц) нельзя (вернее могут быть проблемы, скажем таблица users очень популярна) запустить два движка на одном сайте, например, собственно CMS и популярный форумный двиг
                                    +1
                                    просто иногда дампить удобно. Часть таблиц (ядро) идет с префиксом SYS_, их не трогаю при дампе контента. Или например, если логирование идет в базу, то зачастую нет смысла переносить таблицы с префиксом LOG_
                                    0
                                    Есть Kohana-mptt для дерева.
                                      0
                                      По моему опыту скажу, что в условии большого количества кода, вариант с фигурными скобками гораздо легче выявить в тексте, в отличие от варианта предложенного вами, так как в большенстве ide имеется подсветка начала и конца скобок.
                                      0
                                      Эм… Вы уж простите за оффтопик, но первое, что бросилось в глаза — это отсутствие FOREIGN KEY's во всех таблицах БД и, соответственно, использование MyISAM.
                                      Вы действительно настолько уверенны в PHP и konaha, что готовы переложить на него полностью всю заботу о синхронизации данных в БД? Не страшно?
                                        0
                                        Ну, не думаю, что в контексте примера это так критично.
                                          0
                                          В контексте примера — я бы добавил для общего понимания. И обоснование выбора — так же. В качестве best practice.
                                        0
                                        «Кохана» — в переводе с украинского на русский — означает «любимая», занятно )

                                        Старый друг — лучше новых двух… Меня, как программиста в универе учили лени, т.е. изначально изучать старое, и годами проверенное, чем искать (и не дай Бог) изобретать что-либо новое…
                                        • НЛО прилетело и опубликовало эту надпись здесь
                                            0
                                            Ребят, я не понимаю, как в современном фреймворке допустимо такое
                                             $_tmp->id = $l->id;
                                             $_tmp->name = $l->name;
                                             $_tmp->children = $l->children;
                                            
                                              +1
                                              Я несколько старомоден =D

                                              А если серьезно, то я не знаю, как ещё передать всего три свойства одного класса другому. Если подскажете, буду весьма признателен.
                                                0
                                                А через list() нельзя?
                                                  0
                                                  Можно, получится одна длинная стока (длинее, чем эти 3), которая еще и схватит немного лишних ресурсов. Проще просто исходные 3 строки в одну записать.
                                              +2
                                              Не впечатлило.
                                              • НЛО прилетело и опубликовало эту надпись здесь
                                                  0
                                                  Вы правы, изящности не хватает, но всё-же не следует забывать, что это не обзорная статья, а такой-себе user-guide, для начинающих, с небольшим кол-вом опыта, для которых приимущество есть не изящность, а порстота.
                                                  +1
                                                  выглядит очень неряшливо, уж не знаю чья это заслуга, ваша или коханы. но что очевидно, используются т.н. fat controllers, то есть в контроллерах помимо логики управления находятся еще логика отображения и обработки данных. по уму должен нужно стараться этого избегать и писать в соответствующих местах — view и model.
                                                    +2
                                                    Не сомневайтесь, моя.

                                                    Дело в том, что я ещё не шибко хорошо пишу, а т.к. про этот фреймворк мало кто знает, и ещё меньше про него пишут, пришлось руководствоваться принципом «кто, если не я», Вы уж простите.

                                                    Надеюсь в далнейшем инициативу перехавтят авторы с гораздо большим опытом и способностями к его изложению.
                                                      0
                                                      а не подскажете, где можно посмотреть на код на Kohana или CI который можно было бы считать образцом для подражания? только не простенькие примеры из туториалов.
                                                      дело в том что сейчас я пишу первый, но относительно сложный проект на CI и возникают всякие вопросы, например — где проводить валидацию данных, в модели или контроллере, как можно бы сделать view-обертку для других view и т.п. хотелось бы глянуть как это все принято правильно делать.
                                                        +1
                                                        Валидацию лучше проводить на уровне контроллеров, имхо.

                                                        Насчет view-обертки — в CI нет встроенных средств а-ля Layout в Zend Framework. Лично я использую библиотеку «Template»: официальная страничка и ветка на форуме CI.
                                                          –1
                                                          я не считаю игнитер образцом для подражания вообще. советовать другие фреймворки в тематическом блоге считается моветоном, поэтому загляните в мой профиль, чтобы узнать на что смотреть.
                                                          валидация данных должна проводится в модели тчк
                                                            0
                                                            Если отдавать валидацию модели, что чисто теоретически выглядит более правильным, однажды можно уткнуться в то, что набор правил должен меняться в зависимости от тех или иных условий и вот тогда вся красота рушится…
                                                          0
                                                          интересно
                                                            +2
                                                            Это именно то, что нужно для старта — реальный пример разработки приложения с помощью Kohana. Спасибо!
                                                              0
                                                              Чтото многовато кода для доски объявлений.
                                                                +1
                                                                Это ведь пример, а не готовое решение, о чем я уже писал ваше. Если у вас есть предложения по оптимизации, с радостью их рассмотрю.
                                                                  0
                                                                  Дело в том, что для меня этот пост – первое впечатление в Kohana вообще.
                                                                  И первое впечатление такое – для доски объявлений слишком много кода. Имхо надо больше гибкости. И меньше кода. Но если большая часть этого кода будет реиспользоваться в других частях приложения (не копипаст а именно использоваться), тогда я возьму слово назад =).
                                                                    +1
                                                                    Тут примерно половина — авторизация пользователей на сате.
                                                                      +1
                                                                      Вижу =). Беру слово назад.
                                                                0
                                                                И ещё, у Вас ссылка на ORM ведёт на wikipedia.ru. Надо на wikipedia.org (http://ru.wikipedia.org/wiki/ORM)
                                                                  +1
                                                                  Прошу прощения… Уже исправл.
                                                                  +1
                                                                  Спасибо, отличная статья!
                                                                    0
                                                                    Статья хороша, но у меня вопрос — сколько запросов к базе идет при отображении того или иного view?
                                                                      0
                                                                      Много. Для 8 объвлений — 17 запросов (в том числе и запросы к таблице ползователей и их прав).
                                                                        0
                                                                        А для 16, 32?
                                                                        Я хочу понять зависимость кол-ва запросов от объема данных…
                                                                      0
                                                                      > Для работы с пользователями у нас есть стандартные модели модуля Auth, для обращения к статьям и категориям, создадим свои — создайте файл /application/models/category.php и пропишите в нем следующий класс:

                                                                      Сие предложение ввело меня в ступор, дальнейшие объяснения понять не смог.
                                                                        0
                                                                        Извините, но что именно не понятно?

                                                                        Модуль Auth уже имеет в своем арсенале необхдимые для него модели и требует несколько таблиц от базы для хранения своих данных. Далее я предлагаю создать модели, необходимые для хранения структуры категорий и объявлений.
                                                                          0
                                                                          > Извините, но что именно не понятно?

                                                                          Русская языка… Может так будет понятнее:

                                                                          «У Маши в корзине лежит пять яблок, для того чтобы кушать, купила еще — зашла в магазин и заплатила.»

                                                                          Вопросы:

                                                                          Пять яблок для того чтобы кушать?

                                                                          или

                                                                          Для того чтобы кушать, купила еще?

                                                                          Ы?
                                                                            0
                                                                            Лично я считаю, что цитируемая Вами фраза вполне понятна, и если у Вас возникли трудности с её пониманием, то мне жаль.

                                                                            В случае наличия у Вас более подходящих вариантов для её изложения, я с радостью заменю исходный на Ваш, при целесообразности сиих манипуляций.
                                                                              0
                                                                              Я с радостью подсказал бы вам. Но мне непонятно что вы имеете в виду.
                                                                        0
                                                                        Пытаюсь разобраться и не пойму где происходит проверка на то является ли категория главной или нет? Или вся надежда на то, что список в форме заполнен так, что id может быть только у подкатегорий?
                                                                          0
                                                                          У главных категорий id = 0, по этому признаку и отбераем главные.
                                                                            0
                                                                            Видимо непонятно выразился, попробую еще раз.
                                                                            Не могу найти место в коде, где отсекаются запросы на добавление объявления с указанием category_id скажем 1.
                                                                          +2
                                                                          ORM удобная весч. Но я бы вам не советовал привыкать его использовать в контроллерах или моделях Публичной части сайта.
                                                                          Может быть только в реализации Админки сайта.

                                                                          ORM в своей работе, обычно, ОЧЕНЬ активно посылает SQL-запросы, и даже для простых задач число запросов может исчисляться десятками… Для меня это много…
                                                                          Может я старомоден ) но если при генерации страницы мой Движек делает более 7-10 запросов, то я начинаю нервничать и искать где что можно оптимизировать или закешировать…
                                                                          в случае использования ORM такая гибкость исчезает, к сожалению…
                                                                            0
                                                                            Согласен со всем, что Вы сказали.

                                                                            Я ещё не сильно разобрался, где и что лучше вызывать, но на основе комментариев для себя уже выделил несколько важных моментов, которые в дальнейшем планирую использовать и учитывать.
                                                                            0
                                                                            Могу конечно ошибаться, но надо ли выделять отдельно работу с категориями и подкатегориями? Нельзя использовать один и тот же метод?
                                                                              0
                                                                              Можно, если немного поменять модель роутинга и проверять, является ли категория главной, и вызывать ORM::factory('category', $params), либо же ORM::factory('category', $params)->children в ином случае. Но, по-моему, такой подход не сильно подходит для примера, где важнее наглядность.
                                                                              0
                                                                              недавно разместил статью о Kohana и Ci (прадва там о Kohana 2.2), может будет кому то полезно andrey.opeykin.ru/codeigniter-vs-kohana-form-validation/
                                                                                0
                                                                                Только начал разбираться — вообще интересный фреймворк конечно. Но только есть один момент из примера спорный. По крайней мере как для программиста с++.
                                                                                Зачем в каждом классе-наследнике Template_Controller переопределяется поле template? Если это делается для определение дефолтового значения переменной в классе-наследнике — то по канонам ооп это должно делаться в конструкторе класса. Собственно, я поэтому вместо Template_Controller написал свой базовый абстрактный класс, который при рендеринге переменной проверяет инициализацию этой по сути абстрактной переменной.
                                                                                  0
                                                                                  Для знакомства с Kohana решил выполнить пример. Все отлично. Все получилось. Все работает с одним «но». Медленно. Страничка формируется около 3-х секунд. Возможно, проблема в площадке (FirstVDS, Тариф «Старт»)?
                                                                                    0
                                                                                    Да, скорее всего дело в площадке. Для сравнения, у меня на домашней машине (384Mb, 1.5GHz) полное формирование страницы занимает порядка 0.3 секунды.
                                                                                    0
                                                                                    очень напрягает метод создания форм. работаю с фреймворком symfony — там это делается намного удобнее

                                                                                    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                                                                    Самое читаемое