Пример доски объявлений на 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;
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 и пропишите в нем следующий класс:
Теперь на русском — для модели категорий мы создали расширенный ORM (об этом немного ниже) класс таблицы `categories` с древовидной структкрой (ORM_Tree), у записи которой могут быть потомки в виде записей из этой же таблицы (protected $children = `categories`, по умолчанию родитель — это поле `parent_id`) и каждая запись которой может иметь много вложенных записей таблицы `items`. А для модели объявлений у нас получился простой ORM класс таблицы `items`, которая содержит одну запись из таблицы `users` и пренадлежит таблице `categories`
КонтроллерыС моделями вроде разобрались, теперь о контроллерах — они служат для отображения информации, являясь прослойкой между моделями и видами.
- для главной страницы, заодно и категорий
- для под-категорий
- для просмотра объявлений
- для входа пользователей
- для их регистрации
Дальше много кода, большинство из которого интуитивно понятна, и занакома людям, работавшим с ORM и MVC.
Что такое ORM?В методе index мы собираем массив главных категорий с объектами подкатегорий в нем и кол-вом объявлений(count) в подкатегориях и выводим это всё дело через вид category, который будет описан в главе «Виды».
Метод view показывает нам содержимое всех подкатегорий категории с id, переданным в параметре (как он туда попадёт — в главе «Окончательные настройки»). Также работает метод viewsub с разницей в том, что он применим для подкатегорий.
Теперь контроллер объявлений, items.php:
Думаю, единственный, до этого времени не встречавшийся нам класс — это Forge. Он используется для облегчения работы с формами, но, к сожалению, он исключен из стандартной поставки Kohana. Где его взять, я расскажу ниже.
Ну и контроллер пользователей, user.php:
Интересный момент при создании формы регистрации, а именно метод matches() объекта, который возвращает метод Forge password. При обращении к нему, параметром нужно указать другой объект password, при несоответствии значений которых скрипт выдаст ошибку.
Виды (представления), отвечают за отображение информации и являются конечной прослойкой между пользователем и приложением. Теперь немного о видах. Всего в нашем движке мы использовали три вида, поочередно о каждом:
Как Вы помните, мы ему передавали массив с категориями и подкатегориями, который он и обрабатывает, в цикле выводя их все.
Это «обвертка» для всех страниц, которые мы отображаем. В блоке enter у нас стоит такой-себе триггер, который, в зависимости от статуса пользователя отображает или его имя со ссылкой на выход, или ссылки на вход и регистрацию.
Этот вид показывает нам, само объявление, выводя ссылки на удаление и редактирование в случае, если пользователь админ или автор объявления. Также можно заметить использование двух хелперов — html и text. html::specialchars делает строку «безопасной», html::anchor делает ссылку — первый параметр адрес, второй — текст, ну а text::auto_p автоматически добавляет абзацы к plain text, как сказано в офф. документации «nl2br() on steroids».