Добрый день, любители Kohana!
Сегодня я предлагаю немного потренироваться в использовании базовых методов ORM-объектов, о которых я писал недавно. Мы закрепим знания и получим еще немного сверху.
Итак, для начала я опишу конфигурацию таблиц БД, которые мы будем использовать. Чтобы не ходить далеко, возьмем использованные нами ранее принципы «статья-автор-комментарии», которые встретились нам в статье «Наша первая страница на Kohana». Правда, таблицы будут немного модернизированы, и вместо authors мы будем работать с users (которую мы создавали при регистрации пользователей для работы с модулем Auth):
CREATE TABLE `articles` ( `id` int(10) unsigned NOT NULL auto_increment, `title` varchar(255) collate utf8_unicode_ci NOT NULL default '', `text` text collate utf8_unicode_ci NOT NULL, `author_id` int(10) unsigned NOT NULL, `ts` timestamp NOT NULL default CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `FK_articles_users` (`author_id`), KEY `FK_articles_redactors` (`redactor_id`), CONSTRAINT `FK_articles_redactors` FOREIGN KEY (`redactor_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT `FK_articles_users` FOREIGN KEY (`author_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='InnoDB free: 7168 kB; (`author_id`) REFER `users`(`id`)'; CREATE TABLE `comments` ( `id` int(10) unsigned NOT NULL auto_increment, `text` varchar(255) NOT NULL default '', `author_id` int(10) unsigned NOT NULL, `article_id` int(10) unsigned NOT NULL, `ts` timestamp NOT NULL default CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `FK_comments_articles` (`article_id`), KEY `FK_comments_users` (`author_id`), CONSTRAINT `FK_comments_articles` FOREIGN KEY (`article_id`) REFERENCES `articles` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT `FK_comments_users` FOREIGN KEY (`author_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci ; CREATE TABLE `users` ( `id` int(11) unsigned NOT NULL auto_increment, `email` varchar(127) NOT NULL, `username` varchar(32) NOT NULL default '', `password` char(50) NOT NULL, `logins` int(10) unsigned NOT NULL default '0', `last_login` int(10) unsigned default NULL, PRIMARY KEY (`id`), UNIQUE KEY `uniq_username` (`username`), UNIQUE KEY `uniq_email` (`email`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; insert into articles(title, text, author_id) values ('Статья 1', 'Текст статьи 1', 2), ('Статья 2', 'Текст статьи 2', 3), ('Статья 3', 'Текст статьи 3', 1), ('Статья 4', 'Текст статьи 4', 2); insert into comments(text, author_id, article_id) values ('Комментарий 1', 1,3), ('Комментарий 2', 2,2), ('Комментарий 3', 3,1), ('Комментарий 4', 2,2), ('Комментарий 5', 1,1); |
Таблицу users я не заполняю, так как в ней хранится хэш паролей, и дабы не засорять ее мы будем использовать хранящиеся в ней реальные данные (проще зарегистрировать парочку пользователей через созданный в статье «Представьтесь!» интерфейс). Впрочем, если ранее вы этой таблицей не пользовались, можете ее и вручную заполнить. Далее необходимо создать модели:
// models/article.php class Article_Model extends ORM { protected $has_many = array('comments'); protected $belongs_to = array('author'); } // models/comment.php class Comment_Model extends ORM { protected $belongs_to = array('article', 'author'); } // models/author.php class Author_Model extends ORM { protected $has_many = array('articles', 'comments'); protected $table_name = 'users'; protected $foreign_key = array('comments'=>'author_id', 'articles'=>'author_id'); } |
Обратите внимание, что несмотря на использование таблицы users модель называется Author_Model. Дело в том, что модель User_Model у нас уже есть (в составе модуля Auth) и не хотелось бы менять ее или отключать модуль. А так как имя таблицы обычно определяется из названия модели, необходимо явно указать ее — через свойство $table_name. Еще один момент — необходимость также явно указать имена внешних ключей в связанных таблицах, если имена этих самых ключей отличаются от имени, указанного в $table_name. Тут нам пригодилось свойство $foreign_key.
А теперь напишем контроллер. Давайте сделаем возможность переходить между пользователями, статьями и комментариями (они же связаны между собой). Итак, текст контроллера:
class Test_Controller extends Controller { public function __construct() { parent::__construct(); $this->profiler = new Profiler; } public function authors($id = NULL) { $id AND ctype_digit($id) AND url::redirect('test/author/'.$id); $authors = ORM::factory('author')->find_all(); $this->template = new View('test/authors'); $this->template->authors = $authors->as_array(); $this->template->render(TRUE); } public function author($id = NULL) { is_null($id) OR !ctype_digit($id) AND url::redirect('test/authors'); $author = ORM::factory('author', intval($id)); $this->template = new View('test/author'); $this->template->author = $author->as_array(); $this->template->articles = $author->articles->as_array(); $this->template->comments = $author->comments->as_array(); $this->template->render(TRUE); } public function article($id = NULL) { is_null($id) OR !ctype_digit($id) AND url::redirect('test/articles'); $this->template = new View('test/article'); $article = ORM::factory('article')->where('articles.id', intval($id))->with('author')->find(); $this->template->article = $article->as_array(); $this->template->author = $article->author->as_array(); $this->template->comments = $article->comments->as_array(); $this->template->render(TRUE); } public function articles($id = NULL) { $id AND ctype_digit($id) AND url::redirect('test/article/'.$id); $articles = ORM::factory('article')->with('author')->find_all(); $this->template = new View('test/articles'); $this->template->articles = $articles->as_array(); $this->template->render(TRUE); } public function comments($id = NULL) { $id AND ctype_digit($id) AND url::redirect('test/comment/'.$id); $comments = ORM::factory('comment')->with('author')->with('article')->find_all(); $this->template = new View('test/comments'); $this->template->comments = $comments->as_array(); $this->template->render(TRUE); } |
Для удобства я сделал проверку на введение идентификатора объекта (статьи/пользователя/комментария) и редирект, если id передан или не передан. Внутри создается один или несколько ORM-объектов (в зависимости от того, насколько выгодно их разделять). Я стараюсь сохранять в шаблоне только сами значения полей (результат as_array()), чтобы не копировать лишние данные.
Обратите внимание, что в результате работы метода as_array() в переменную не сохраняются данные смежных таблиц, поэтому их сохраняем отдельно.
Текст шаблонов я приводить здесь не буду, выложу архив с контроллерами, моделями и шаблонами (архив rar). На всякий случай поясню, что т.к. наш контроллер не унаследован от Template_Controller, необходимо вручную указывать render(TRUE); для шаблонов. Кроме того, чтобы отслеживать запросы к БД, в конструкторе подключается объект Profiler (в config/profiler.php не забудьте прописать $config['database'] = TRUE;). Внизу страницы теперь выводится статистика, очень полезно ее анализировать. Доступ к контроллеру, как вы понимаете, будет по УРЛ вида http://localhost/test/articles.
Домашнее задание
А теперь вопрос — почему где-то я использую один ORM-объект с методом with(), где-то несколько. Где-то создаю объект с передачей первичного ключа, а где-то создаю пустой и применяю метод where(). В общем, попробуйте поэкспериментировать, это никогда не вредно.
Update
Кстати, эти записи эквивалентны:
$article = ORM::factory('article')->where('articles.id', intval($id))->with('author')->find(); $article = ORM::factory('article')->with('author')->find(intval($id)); |
is_null($id) OR !ctype_digit($id)
у меня надо в скобки взять, чтоб работало, когда я захожу на test/author
и вообще может эту строку преобразовать, а то вообще многим может быть непонятно, что происходит
Странно, все работало, да и вполне корректно…
По сути это краткая запись выражения
If (is_null($id) OR (!ctype_digit($id)))
Т.е. если параметр $id не передан или он не состоит только из цифр. Очень удобно, как мне кажется.
public function article($id = NULL) {
is_null($id) OR !ctype_digit($id) AND url::redirect(‘test/articles’);
$this->template = new View(‘test/article’);
$article = ORM::factory(‘article’)->where(‘articles.id’, intval($id))->with(‘author’)->find();
$this->template->article = $article->as_array();
$this->template->author = $article->author->as_array();
$this->template->comments = $article->comments->as_array();
$this->template->render(TRUE);
}
Почему в строке $article = ORM::factory(’article’)->where(’articles.id’, intval($id))->with(’author’)->find();
нет with(‘comments’), но далее коментарии присутствуют как подобъект. А with(’author’) есть…???
Метод with() используется только для тех таблиц, которые связаны с текущей моделью через $has_one или $belongs_to. Т.е. если объекту соответствует не более одного подобъекта, то его можно сразу подгрузить с помощью метода with(), что сэкономит один запрос. Комментарии загружаются при первом обращении к свойству comments (смотрите код метода __get() в объекте ORM) — т.н. «ленивая загрузка».
Прошу прощения за офтоп и вопросы не по теме.
Помогите пожалуйста.
Я новичок в Kohana и столкнулся с проблемой:
$posts = ORM::factory(‘blog_post’) -> find_all(10);
foreach ($posts as $subres)
{
echo $subres -> title;
}
не возвращает значений, хотя
$post = ORM::factory(‘blog_post’) -> find(1);
$post -> title
уже содержит название статьи.
V3r
А текст модели бы еще… Есть подозрение, что вы конструктор изменили, потеряв там параметр $id.
Спасибо! Разобрался.))