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 передать также информацию, необходимую для отображения навигатора страниц
- В шаблоне отобразить навигатор страниц на основании переданной ему информации
Теперь перейдем к более интересной теме: администрированию новостей.