ORM: Работаем с базой данных

После того, как мы научились отображать новости, попытаемся сделать то же самое, только новости будем брать из базы данных. В качестве примера возьмем дамп MySQL таблицы. Поднимите его в базе данных, созданной при настройке сервера, любым удобным способом. Результатом будет таблица news со следующими полями:

  • id - идентификатор новости
  • idate - дата в формате UNIX TIMESTAMP
  • title - заголовок
  • announce - краткий текст (для отображения в списке новостей)
  • content - полный текст

Создаем модуль ORM

Первая наша задача - создать модуль Component.News.DB, обеспечивающий доступ к созданной таблице. В TAO связь с базой данных осуществляется по технологии ORM (Object-relational mapping), т.е. каждая сохраняемая в БД запись (в данном случае новость) отображается в виде объекта ООП (экземпляра класса) PHP.

Таким образом, для функционирования новостей нам необходимо в модуле Component.News.DB создать два класса:

  • ORM-маппер - класс, организующий связь объектов ООП c записями в реляционной БД. Такие классы обычно наследуются от базового класса CMS_ORM_Mapper.
  • Класс, которым оперирует ORM-маппер и экземпляры которого будут представлять собой отдельные новости. Наследуется от CMS_ORM_Entity.

В простейшем случае (таком, как наша задача) модуль будет коротким и лаконичным. Итак, создаем файл app/components/News/DB.php:

<?php

class Component_News_DB extends CMS_ORM_Mapper implements Core_ModuleInterface
{
	public function setup() {
		return $this
			->table('news')
			->classname('Component_News_DB_Item')
			->key('id')
			->order_by('idate DESC,id DESC')
			->columns('id','idate','title','announce','content');
	}
}

class Component_News_DB_Item extends CMS_ORM_Entity
{
}

Перед нами два класса. Класс Component_News_DB_Item, реализующий запись, вообще пустой, поскольку никакой особенной функциональности от него нам пока не надо. Класс Component_News_DB, реализующий ORM-маппер (он оформлен в виде главного класса модуля) содержит одну единственную функцию setup, в которой указывается основная информация о наших новостях:

  • table - имя таблицы в БД.
  • classname - имя класса, экземпляры которого представляют собой отдельные новости.
  • key - имя ключевого поля (числовой идентификатор записи).
  • order_by - порядок сортировки записей по умолчанию.
  • columns - список полей в БД.

Для того, чтобы воспользоваться этим модулем, необходимо сначала зарегистрировать его в системе, вызвав функцию CMS::orm()->add($name,$module), где:

  • $name - произвольное мнемоническое имя, под которым ORM-маппер будет доступен для использования. В простых случаях (как наш) его обычно делают совпадающим с именем таблицы в БД (в нашем случае news), но вообще-то это совершенно не обязательно.
  • $module - имя модуля, реализующего ORM-маппер.

При регистрации модуля его загрузки не производится - только запоминается его имя. Реально он будет загружен непосредственно при первом к обращении к ORM-мапперу по его мнемоническому имени. Регистрацию лучше всего произвести при инициализации компонента. Запишем функцию initialize модуля Component.News следующим образом:

public static function initialize()
{
	CMS::add_component('News', new Component_News_Router);
	CMS::orm()->add('news','Component.News.DB');
}

Теперь здесь регистрируется не только компонент со своим роутером, но и ORM-модуль.

Использование созданного модуля ORM

После вышеописанных действий экземпляр класса Component_News_DB стал доступен через вызов CMS::orm()->news, и мы получили простейшие механизмы для обмена данными с таблицей news в базе данных:

Загрузка одной записи

$item = CMS::orm()->news->find($id);

Данная конструкция вернет экземпляр класса Component_News_DB_Item. А в случае если записи с таким идентификатором не существует, то будет возвращен null.

Доступ к полям загруженной записи

Поля из БД доступны через синтаксис массива:

print date('d.m.Y',$item['idate']);
print $item['title'];

Кроме этого, разумеется, никто не запрещает добавлять в класс Component_News_DB_Item дополнительные свойства и методы для работы с содержащейся в нем информацией.

Получение количества записей в таблице

$count = CMS::orm()->news->count();

Получение списка записей

Прежде всего стоит знать, что ORM-маппер может работать как итератор, что обычно и используется для чтения списка записей:

foreach(CMS::orm()->news as $item) {
	//.....
}

Есть возможность также получить непосредственно массив записей (однако, не стоит этого делать при большом количестве записей):

$rows = CMS::orm()->news->select();

Для получения записей по лимиту можно воспользоваться этим:

// Получение первых десяти записей (от смещения 0)
$rows = CMS::orm()->news->range(10);

// Получение десяти записей от смещения 20
$rows = CMS::orm()->news->range(10,20);

Создание объекта записи make_entity()

Если возникает необходимость создания новых записей в таблице, то сначала требуется создать объект сущности. Для этого необходимо воспользоваться методом make_entity().

$news_stat = CMS::orm()->news->make_entity(); // создаем объект записи

При этом используется обращение к мапперу Component.News.DB, который занимается организацией записи данных. Метод make_entity() создает нужный объект сущности класса, имя которого указанно при вызове classname() маппера. При этом сработает метод setup() объекта сущности. В результате переменная $news_stat будет объектом класса Component_News_DB_Item.

Далее, если требуется, устанавливаются значения полей, и вызывается метод вставки данных в таблицу. Методы insert(), update(), delete() и другие наследуются. Именно в этот момент происходит запись в БД.

Модифицируем контроллер

Теперь изменим модуль Component.News.Controller так, чтобы он брал список записей из БД. Массив $news из контроллера удаляем, а функцию view_list запишем так:

public function view_list()
{
	return $this->render('list',array(
		'rows' => CMS::orm()->news,
	));
}

Как видим, она практически никак не изменилась. Только вместо массива записей мы передаем ORM-маппер.

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

<h1>Новости</h1>

<?php foreach($rows as $row) { ?>
	<div class="news_item">
		<div class="date"><?= date('d.m.Y',$row['idate']) ?></div>
		<a class="title" href="/news/<?= $row['id'] ?>/"><?= $row['title'] ?></a>
		<div class="announce"><?= $row['announce'] ?></div>
	</div>
<?php } ?>

Вот теперь все заработало. Теперь осталось переделать функцию показа одной новости:

public function view_item($id)
{
	// Загрузим запись
	$item = CMS::orm()->news->find($id);

	// Если такой новости не существует,
	// то вернем 404
	if (!$item) {
		return $this->page_not_found();
	}

	return $this->render('item',array(
		'item' => $item,
	));
}

Итоги

Окончательный код полученного компонента вы можете скачать по этой ссылке. Что в нем не хватает? Очевидно, хотелось бы видеть список новостей с постраничной навигацией. Однако, эту тему мы сейчас рассматриватиь не будем и предложим реализовать это самостоятельно. Все необходимое для этого у вас имеется. Нужно сделать следующее:

  • В экшен view_list передать параметр (номер страницы)
  • Получить количество новостей в БД и вычислить количество страниц
  • Получить не все новости, а только текущую страницу (модификатор range ORM-маппера)
  • В шаблон list.phtml передать также информацию, необходимую для отображения навигатора страниц
  • В шаблоне отобразить навигатор страниц на основании переданной ему информации

Теперь перейдем к более интересной теме: администрированию новостей.

26.12.2013
Все статьи