Введение в Zend Framework. Часть 2
В первой части статьи была описана концепция программной архитектуры MVC, рассмотрена структура типового веб-приложения, базирующегося на Zend Framework и выполнена демонстрационная реализация контроллера и вида на его основе. Во второй части будет раскрыта тема модели и приведен пример взаимодействия приложения с базой данных. |
Перевод: Александр Мусаев,
База данных
Теперь, когда управляющая часть нашего приложения и код визуализации разделены, пришло время заняться моделью. Запомните, что модель — базовая часть приложения, которая реализует его основные функции. Следовательно, в нашем случае модель выполняет работу с базой данных. Мы используем класс Zend FrameworkZend_Db_Table
, предназначенный для поиска, вставки, обновления и удаления записей в таблице базы данных.Настройка
Для использованияZend_Db_Table
, понадобится сообщить ему, к какой базе данных, под каким именем пользователя и с каким паролем он будет обращаться. Принимая во внимание, что эту информацию предпочтительно не забивать в код, воспользуемся конфигурационным файлом для ее хранения.Zend Framework предоставляет для этой цели класс
Zend_Config
, обеспечивающий гибкий объектно-ориентированный доступ к конфигурационным файлам в форматах INI и XML. Остановим свой выбор на INI-файле:zf-tutorial/application/config.ini:
[general]
db.adapter = PDO_MYSQL
db.config.host = localhost
db.config.username = rob
db.config.password = 123456
db.config.dbname = zftest
(Безусловно, вам понадобится использовать свои собственные параметры доступа к БД, а не приведенные в примере.)
Использовать
Zend_Config
будет очень просто:$config = new Zend_Config_Ini('config.ini', 'section');
В данном случае,
Zend_Config_Ini
загружает одну секцию из INI-файла (таким же образом при необходимости можно загрузить любую другую секцию). Возможность именования секций реализована для того, чтобы лишние данные без нужды не загружались. Zend_Config_Ini
использует точку в именах параметров в качестве иерархического разделителя, благодаря чему можно группировать родственные параметры. В нашем файле config.ini
, параметры host
, username
, password
и dbname
будут сгруппированы в объекте $config->db->config
.Мы будем загружать конфигурационный файл из файла начальной загрузки (
index.php
):zf-tutorial/index.php:
...
Zend_Loader::loadClass('Zend_Controller_Front');
Zend_Loader::loadClass('Zend_Config_Ini');
Zend_Loader::loadClass('Zend_Registry');
// load configuration
$config = new Zend_Config_Ini('./application/config.ini', 'general');
$registry = Zend_Registry::getInstance();
$registry->set('config', $config);
// setup controller
...
После загрузки необходимых для работы классов (
Zend_Config_Ini
и Zend_Registry
), в объект $config
загружается секция конфигурационного файла application/config.ini
под именем “general
”. Далее объект $config
включается в реестр, что обеспечивая доступ к нему из всего приложения.Замечание: В данном примере нет реальной потребности хранить
$config
в реестре. Это сделано в качестве примера работы «настоящего» приложения, в конфигурационном файле которого вам, вероятно, придется хранить нечто большее, чем параметры доступа к БД. При использовании реестра, обратите так же внимание, что данные в нем доступны на глобальном уровне, и что при неосторожном использовании это может стать причиной потенциальных конфликтных ситуаций внутри приложения.Использование Zend_Db_Table
Для того, чтобы использовать классZend_Db_Table
, нам понадобится передать в него параметра доступа к базе данных, которые были только что загружены. Для этого необходимо создать объект Zend_Db
и зарегистрировать его функцией Zend_Db_Table::setDefaultAdapter()
. Ниже приведен соответствующий фрагмент кода в файле первичной загрузки:zf-tutorial/index.php:
...
Zend_Loader::loadClass('Zend_Controller_Front');
Zend_Loader::loadClass('Zend_Config_Ini');
Zend_Loader::loadClass('Zend_Registry');
Zend_Loader::loadClass('Zend_Db');
Zend_Loader::loadClass('Zend_Db_Table');
// load configuration
$config = new Zend_Config_Ini('./application/config.ini', 'general');
$registry = Zend_Registry::getInstance();
$registry->set('config', $config);
// setup database
$db = Zend_Db::factory($config->db->adapter,
$config->db->config->toArray());
Zend_Db_Table::setDefaultAdapter($db);
// setup controller
...
Создание таблицы
Мы будем использовать базу данных MySQL, поэтому SQL запрос на создание таблицы будет выглядеть так:CREATE TABLE album (
id int(11) NOT NULL auto_increment,
artist varchar(100) NOT NULL,
title varchar(100) NOT NULL,
PRIMARY KEY (id)
);
Этот запрос можно выполнить через любой MySQL-клиент, например, phpMyAdmin или стандартную консольную утилиту.
Добавление тестовой записи
Добавим несколько тестовых записей в таблицу для проверки функциональности главной страницы, на которой они в последствии должны отображаться.INSERT INTO album (artist, title)
VALUES
('James Morrison', 'Undiscovered'),
('Snow Patrol', 'Eyes Open');
Модель
Zend_Db_Table
— абстрактный класс, поэтому на его основе необходимо создать специализированный на нашей задаче класс-наследник. Имя нового класса не имеет принципиального значения, но такие классы стоит называть аналогично соответствующим им таблицам БД (это повысит уровень самодокументируемости кода). Таким образом, для нашей таблицы album
имя класса будет Album
.Для того, чтобы передать в
Zend_Db_Table
имя таблицы, которой он будет управлять, необходимо присвоить это значение защищенному свойству класса $_name
. Стоит отметить, что в классе Zend_Db_Table
по-умолчанию всегда используется ключевое автоинкрементное поле таблицы с именем id
. При необходимости значение имени этого поля так же можно менять.Класс
Album
будет храниться в директории моделей.zf-tutorial/application/models/Album.php:
<?php
class Album extends Zend_Db_Table
{
protected $_name = 'album';
}
Ничего сложного, не так ли? Учитывая, что наши потребности в данном случае совсем невелики, базовой функциональности
Zend_Db_Table
хватит, чтобы полностью их удовлетворить. Если же в ваших задачах понадобится реализовать более сложные операции взаимодействия с базой данных, то класс модели — это именно то место, куда стоит поместить соответствующий код.Список альбомов
Теперь, когда мы сконфигурировали базу данных, можно приступить к выполнению основной задачи приложения и отобразить несколько записей. Это должно быть реализовано в классеIndexController
. Каждая функция-действие внутри IndexController
взаимодействует с таблицей album
через класс Album
. Поэтому имеет смысл загрузить его при инициализации контролера внутри функции init()
.zf-tutorial/application/controllers/IndexController.php:
...
function init()
{
$this->view->baseUrl = $this->_request->getBaseUrl();
Zend_Loader::loadClass('Album');
}
...
Замечание: В данном фрагменте кода приведен пример использования метода
Zend_Loader::loadClass()
для загрузки нестандартных классов. Это срабатывает, т. к. директория моделей (в которой хранится подгружаемый класс Albums
) была добавлена нами в include_path
.Создадим список альбомов из базы с помощью
indexAction()
:zf-tutorial/application/controllers/IndexController.php:
...
function indexAction()
{
$this->view->title = "My Albums";
$album = new Album();
$this->view->albums = $album->fetchAll();
}
...
Функция
fetchAll()
возвращает объект Zend_Db_Table_Rowset
, который позволит нам сформировать список из всех записей в шаблонном файле вида:zf-tutorial/application/views/scripts/index/index.phtml:
<?php echo $this->render('header.phtml'); ?>
<h1><?php echo $this->escape($this->title); ?></h1>
<p><a href="<?php echo $this->baseUrl; ?>/index/add">Add new album</a></p>
<table>
<tr>
<th>Title</th>
<th>Artist</th>
<th> </th>
</tr>
<?php foreach($this->albums as $album) : ?>
<tr>
<td><?php echo $this->escape($album->title);?></td>
<td><?php echo $this->escape($album->artist);?></td>
<td>
<a href="<?php echo $this->baseUrl; ?>/index/edit/id/<?php
echo $album->id;?>">Edit</a>
<a href="<?php echo $this->baseUrl; ?>/index/delete/id/<?php
echo $album->id;?>">Delete</a>
</td>
</tr>
<?php endforeach; ?>
</table>
<?php echo $this->render('footer.phtml'); ?>
По адресу
http://localhost/zf-tutorial/
теперь должен отображаться список из двух наших альбомов.Добавление нового альбома
Теперь перейдем к функции добавления нового диска в базу. Эта задача состоит из двух частей:- отображение формы, через которую пользователь будет вводить данные;
- добавление принятых из формы данных в базу.
addAction()
:zf-tutorial/application/controllers/IndexController.php:
...
function addAction()
{
$this->view->title = "Add New Album";
if ($this->_request->isPost()) {
Zend_Loader::loadClass('Zend_Filter_StripTags');
$filter = new Zend_Filter_StripTags();
$artist = $filter->filter($this->_request->getPost('artist'));
$artist = trim($artist);
$title = trim($filter->filter($this->_request->getPost('title')));
if ($artist != '' && $title != '') {
$data = array(
'artist' => $artist,
'title' => $title,
);
$album = new Album();
$album->insert($data);
$this->_ redirect('/');
return;
}
}
// set up an "empty" album
$this->view->album = new stdClass();
$this->view->album->id = null;
$this->view->album->artist = '';
$this->view->album->title = '';
// additional view fields required by form
$this->view->action = 'add';
$this->view->buttonText = 'Add';
}
...
Обратите внимание, как в начале функции было определено, имела ли место пересылка данных их формы. Если данные из формы были переданы, мы извлекаем значения
artist
и title
, и обрабатываем их фильтром HTML тагов Zend_Filter_StripTags
. Далее, если строки имеют непустое значение, мы используем класс Album()
для добавления новой записи в таблицу БД. После добавления альбома, пользователь переадресуется обратно на главную страницу методом контроллера _redirect()
.Последнее, что понадобится сделать, — подготовить HTML-форму для шаблона вида. Забегая вперед, можно отметить, что форма редактирования записей будет выглядеть идентично форме для их добавления. Поэтому мы используем общий шаблонный файл (
_form.html
), который будет использован в add.phtml
и edit.phtml
:Шаблон страницы добавления нового альбома:
zf-tutorial/application/views/scripts/index/add.phtml:
<?php echo $this->render('header.phtml'); ?>
<h1><?php echo $this->escape($this->title); ?></h1>
<?php echo $this->render('index/_form.phtml'); ?>
<?php echo $this->render('footer.phtml'); ?>
zf-tutorial/application/views/scripts/index/_form.phtml:
<form action="<?php echo $this->baseUrl ?>/index/<?php
echo $this->action; ?>" method="post">
<div>
<label for="artist">Artist</label>
<input type="text" name="artist"
value="<?php echo $this->escape(trim($this->album->artist));?>"/>
</div>
<div>
<label for="title">Title</label>
<input type="text" name="title"
value="<?php echo $this->escape($this->album->title);?>"/>
</div>
<div id="formbutton">
<input type="hidden" name="id" value="<?php echo $this->album->id; ?>" />
<input type="submit" name="add"
value="<?php echo $this->escape($this->buttonText); ?>" />
</div>
</form>
Получился достаточно несложный код. Учитывая, что мы предполагаем применять
_form.phtml
еще и при редактировании записей, используем переменную $this->action
, вместо того, чтобы жестко задавать имя необходимого действия. Таким же образом, через переменную задается надпись на кнопке передачи данных из формы.Редактирование альбома
Редактирование альбома во многом идентично добавлению новой записи, поэтому и код получается похожим:zf-tutorial/application/controllers/IndexController.php:
...
function editAction()
{
$this->view->title = "Edit Album";
$album = new Album();
if ($this->_request->isPost()) {
Zend_Loader::loadClass('Zend_Filter_StripTags');
$filter = new Zend_Filter_StripTags();
$id = (int)$this->_request->getPost('id');
$artist = $filter->filter($this->_request->getPost('artist'));
$artist = trim($artist);
$title = trim($filter->filter($this->_request->getPost('title')));
if ($id !== false) {
if ($artist != '' && $title != '') {
$data = array(
'artist' => $artist,
'title' => $title,
);
$where = 'id = ' . $id;
$album->update($data, $where);
$this->_redirect('/');
return;
} else {
$this->view->album = $album->fetchRow('id='.$id);
}
}
} else {
// album id should be $params['id']
$id = (int)$this->_request->getParam('id', 0);
if ($id > 0) {
$this->view->album = $album->fetchRow('id='.$id);
}
}
// additional view fields required by form
$this->view->action = 'edit';
$this->view->buttonText = 'Update';
}
...
Стоит отметить, что в тех случаях, когда не была выполнена передача данных в скрипт из формы, мы можем получить параметр
id
из свойства params
объекта Request
, с помощью метода getParam()
.Код шаблона приведен ниже:
zf-tutorial/application/views/scripts/index/edit.phtml:
<?php echo $this->render('header.phtml'); ?>
<h1><?php echo $this->escape($this->title); ?></h1>
<?php echo $this->render('index/_form.phtml'); ?>
<?php echo $this->render('footer.phtml'); ?>
Рефакторинг
Безусловно, от вашего внимания не могло ускользнуть, чтоaddAction()
и editAction()
очень похожи, а шаблоны добавления и редактировании записей вообще идентичны. Логично предположить необходимость рефакторинга кода. Решите эту задачу самостоятельно, в качестве дополнительного практического упражнения.Удаление альбома
Для того, чтобы завершить разработку приложения, понадобится добавить функцию удаления записей. У нас предусмотрены ссылки для удаления альбомов напротив каждого элемента в списке. Первое приходящее на ум решение — удалять записи сразу при клике по одной из этих ссылок. Но это неправильный подход. Вспомните, что согласно спецификации HTTP, не следует выполнять необратимых действий с помощью метода GET. В таких случаях рекомендуется применять POST. Примером того может быть работа Google Accelerator.Мы должны запрашивать подтверждение перед удалением записей и удалять их только после того, как оно будет получено. Код, реализующий эти действия, в некоторой степени похож на уже существующие в нашем приложении функции-действия:
zf-tutorial/application/controllers/IndexController.php:
...
function deleteAction()
{
$this->view->title = "Delete Album";
$album = new Album();
if ($this->_request->isPost()) {
Zend_Loader::loadClass('Zend_Filter_Alpha');
$filter = new Zend_Filter_Alpha();
$id = (int)$this->_request->getPost('id');
$del = $filter->filter($this->_request->getPost('del'));
if ($del == 'Yes' && $id > 0) {
$where = 'id = ' . $id;
$rows_affected = $album->delete($where);
}
} else {
$id = (int)$this->_request->getParam('id');
if ($id > 0) {
// only render if we have an id and can find the album.
$this->view->album = $album->fetchRow('id='.$id);
if ($this->view->album->id > 0) {
// render template automatically
return;
}
}
}
// redirect back to the album list unless we have rendered the view
$this->_redirect('/');
}
...
Использован тот же способ определения метода обращения к скрипту и выбора требуемой операции (удаления записи или выдачи формы подтверждения). Точно так же, как в случае с добавлением и редактированием, удаление выполняется через
Zend_Db_Table
методом delete()
. В конце функции выполняется перенаправление пользователя на страницу со списком альбомов. Таким образом, если одна из проверок корректности параметров не пройдена, происходит возврат на исходную страницу без помощи многократного обращения к _redirect()
внутри функции.Шаблон представляет собой простую форму:
zf-tutorial/application/views/scripts/index/delete.phtml:
<?php echo $this->render('header.phtml'); ?>
<h1><?php echo $this->escape($this->title); ?></h1>
<?php if ($this->album) :?>
<form action="<?php echo $this->baseUrl ?>/index/delete" method="post">
<p>Are you sure that you want to delete
'<?php echo $this->escape($this->album->title); ?>' by
'<?php echo $this->escape($this->album->artist); ?>'?
</p>
<div>
<input type="hidden" name="id"
value="<?php echo $this->album->id; ?>" />
<input type="submit" name="del" value="Yes" />
<input type="submit" name="del" value="No" />
</div>
</form>
<?php else: ?>
<p>Cannot find album.</p>
<?php endif;?>
<?php echo $this->render('footer.phtml'); ?>
Устранение неполадок
Если возникают сложности при обращении к любым действиям помимоindex
/index
, скорее всего причина состоит в том, что класс-роутер не может корректно определить, в какой директории находится ваш веб-сайт. Такая ситуация может возникнуть, если URL сайта не соответствует пути к его директории относительно корневого каталога, открытого для доступа из сети.Если приведенный выше код не соответствует вашему случаю, необходимо задать переменной
$baseURL
корректное для вашего сервера значение:zf-tutorial/index.php:
...
// setup controller
$frontController = Zend_Controller_Front::getInstance();
$frontController->throwExceptions(true);
$frontController->setBaseUrl('/mysubdir/zf-tutorial');
$frontController->setControllerDirectory('./application/controllers');
...
Значение “
/mysubdir/zf-tutorial/
” понадобится заменить на действительный путь к файлу index.php
. Например, если ваш URL к index.php
выглядит как http://localhost/~ralle/zf-tutorial/index.php
, корректным значением для переменной $baseUrl
будет “/~ralle/zf-tutorial/
”.Заключение
На этом построение простого но полнофункционального MVC-приложения можно считать завершенным. Надеюсь, этот обзор был полезен и информативен для вас. Любые замечания к оригинальному тексту можно отправлять автору статьи по адресу [email protected]. Комментарии, связанные с русским переводом, отправляйте на [email protected].В данной статье выполнен лишь поверхностный обзор Zend Framework, в котором существует великое множество других классов, помимо перечисленных здесь. Для подробного ознакомления с ними следует обратиться к соответствующим ресурсам:
- Официальная документация:
http://framework.zend.com/manual - Wiki с дополнительными материалами:
http://framework.zend.com/wiki - Wiki для разработчиков:
http://framework.zend.com/developer
При переводе использовались материалы свободной энциклопедии Wikipedia.org.
© habrahabr.ru
« Назад