Контент


ORM2 на практике

Добрый день, любители 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); для шаблонов. Кроме того, чтобы отслеживать запросы к БД, в конструкторе подключается объект Profilerconfig/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));

Google Bookmarks Digg Reddit del.icio.us Ma.gnolia Technorati Slashdot Yahoo My Web News2.ru БобрДобр.ru RUmarkz Ваау! Memori.ru rucity.com МоёМесто.ru Mister Wong

Опубликовано в учебник.

Теги: , , , .


Комментарии (8)

Будьте в курсе обсуждения, подпишитесь на RSS ленту комментариев к этой записи.

  1. cwer пишет:

    is_null($id) OR !ctype_digit($id)
    у меня надо в скобки взять, чтоб работало, когда я захожу на test/author
    и вообще может эту строку преобразовать, а то вообще многим может быть непонятно, что происходит :(

  2. BIakaVeron пишет:

    Странно, все работало, да и вполне корректно…
    По сути это краткая запись выражения
    If (is_null($id) OR (!ctype_digit($id)))
    Т.е. если параметр $id не передан или он не состоит только из цифр. Очень удобно, как мне кажется.

  3. WWTLF пишет:

    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);
    }

  4. WWTLF пишет:

    Почему в строке $article = ORM::factory(’article’)->where(’articles.id’, intval($id))->with(’author’)->find();
    нет with(‘comments’), но далее коментарии присутствуют как подобъект. А with(’author’) есть…???

  5. BIakaVeron пишет:

    Метод with() используется только для тех таблиц, которые связаны с текущей моделью через $has_one или $belongs_to. Т.е. если объекту соответствует не более одного подобъекта, то его можно сразу подгрузить с помощью метода with(), что сэкономит один запрос. Комментарии загружаются при первом обращении к свойству comments (смотрите код метода __get() в объекте ORM) — т.н. «ленивая загрузка».

  6. V3r пишет:

    Прошу прощения за офтоп и вопросы не по теме.

    Помогите пожалуйста.
    Я новичок в 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

    уже содержит название статьи.

  7. BIakaVeron пишет:

    V3r
    А текст модели бы еще… Есть подозрение, что вы конструктор изменили, потеряв там параметр $id.

  8. V3r пишет:

    Спасибо! Разобрался.))



Можно включить подсветку кода: <code><pre lang="">...</pre></code>
Разрешены некоторые HTML теги

или используйте trackback.