Как работать с базами данных или знакомство с компонентом Zend_Db
Редко какое веб-приложение обходится без работы с базой данных. Компонент Zend_Db предоставляет удобный интерфейс доступа к SQL базам данных. Он использует ряд адаптеров для соединения с различными
базами данных. На полноценную ORM рассчитывать не приходится, да это и не нужно в большинстве случаев, Zend_Db является своего рода конструктором запросов. Разводить холивара на тему того, использовать
ли plain sql или ORM, не буду. Лично я сложные запросы предпочитаю записывать в явном виде, а простые можно генерировать, используя тот же самый Zend_Db.
Итак, вспомним наш каталог книг, который мы делали при помощи компонента Zend_Session_Namespace. Сессии - это, конечно, хорошо, но все-таки хочется иметь возможность сохранить информацию, чтобы потом
в любой момент иметь к ней доступ, а не только во время текущего сеанса ;)
Сначала нам необходимо настроить соединение с базой данных. Идем в application/configs/application.ini и прописываем такие строчки:
[code language="php"]
;DB settings resources.db.adapter = "pdo_mysql" resources.db.params.host = "localhost" resources.db.params.username = "zf" resources.db.params.password = "s3cr3t" resources.db.params.dbname = "books" resources.db.isDefaultTableAdapter = true resources.db.params.driver_options.1002 = "SET NAMES utf8"
[/code]
В качестве адаптера я выбрал pdo_mysql, имя пользователя - zf, пароль - s3cr3t, имя базы данных - books.
Далее необходимо создать базу данных books и выставить привилегии
[code language="bash"]
mysqladmin create books -uroot -p
mysql -uroot -p -e "grant all privileges on books.* to zf@localhost identified by 's3cr3t'"
[/code]
Создаем таблицу
[code language="sql"]
CREATE TABLE books.books (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY ,
name VARCHAR( 255 ) NOT NULL ,
author VARCHAR( 100 ) NOT NULL ,
description VARCHAR( 255 ) NOT NULL
) ENGINE = InnoDB;
[/code]
Опишем модель books, которая будет манипулировать книгами.
[code language="php"]
class Model_Book {
protected $name; protected $author; protected $description; protected $mapper;
public function getName() { return $this->name; }
public function setName($name) { $this->name = $name; return $this; }
public function getAuthor() { return $this->author; }
public function setAuthor($author) { $this->author = $author; return $this; }
public function getDescription() { return $this->description; }
public function setDescription($desc) { $this->description = $desc; return $this; }
public function getMapper() { if( null === $this->mapper) { $this->mapper = new Model_BookMapper(); } return $this->mapper; } public function save() { $this->getMapper()->save($this); }
public function find($id) { return $this->getMapper()->find($id, $this); }
}
[/code]
Для книг имеется три свойства - это название книги (name), автор книги (author) и описание (description). Для них мы определили свои сетеры и гетеры. Еще есть два метода - save и find, которые используют класс маппера для доступа к данным. Используя шаблон Data Mapper, мы как бы абстрагируемся от работы с базой данных и скрываем от модели все взаимодействие с БД. В случае же изменения структуры таблицы, нам просто необходимо будет создать соответствующие свойства в модели и указать для них get и set методы доступа. Класс BookMapper определим пока следующим образом:
[code language="php"]
class Model_BookMapper {
public function save(Model_Book $book) {
}
public function find($id, Model_Book $book) {
}
}
[/code]
Перейдем пока в контроллер BookController.php
[code language="php"]
class BookController extends Zend_Controller_Action {
public function createAction() { $frmBook = new Form_Book(); $frmBook->setMethod('post'); if($this->_request->isPost()) { if($frmBook->isValid($this->_request->getPost())) { $mdlBook = new Model_Book($frmBook->getValues()); $mdlBook->save(); } } $this->view->form = $frmBook; }
}
[/code]
Мы получаем данные из формы и передаем их в конструктор объекта Книга, затем просто сохраняем объект. Красиво получается и совсем мало кода, не правда ли?) Но теперь нам нужно определить конструктор объекта, чтобы двумя строчками кода создавать новый объект.
[code language="php"]
// application/models/Book.php
public function setOptions(array $options) { $methods = get_class_methods($this);
foreach ($options as $key => $value) { $method = 'set' . ucfirst($key); if (in_array($method, $methods)) { $this->$method($value); } } return $this; }
public function __construct(array $options = null) { if (is_array($options)) { $this->setOptions($options); } }
public function __set($name, $value) { $method = 'set' . $name; if (('mapper' == $name) || !method_exists($this, $method)) { throw new Exception('Invalid book property'); } $this->$method($value); }
public function __get($name) { $method = 'get' . $name; if (('mapper' == $name) || !method_exists($this, $method)) { throw new Exception('Invalid book property'); } return $this->$method(); }
[/code]
Теперь осталось реализовать методы в mapper классе.
[code language="php"]
// application/models/BookMapper.php
protected $_dbTable;
public function setDbTable($dbTable) { if (is_string($dbTable)) { $dbTable = new $dbTable(); } if (!$dbTable instanceof Zend_Db_Table_Abstract) { throw new Exception('Invalid table data gateway provided'); } $this->_dbTable = $dbTable; return $this; }
public function getDbTable() { if (null === $this->_dbTable) { $this->setDbTable('Model_DbTable_Book'); } return $this->_dbTable; }
public function save(Model_Book $book) { $data = array( 'name' => $book->getName(), 'author' => $book->getAuthor(), 'description' => $book->getDescription() );
if (null === ($id = $book->getId())) { unset($data['id']); $this->getDbTable()->insert($data); } else { $this->getDbTable()->update($data, array('id = ?' => $id)); } }
public function find($id, Model_Book $book) { $result = $this->getDbTable()->find($id); if (0 == count($result)) { return; } $row = $result->current(); $book->setId($row->id) ->setName($row->name) ->setAuthor($row->author) ->setDescription($row->description); }
[/code]
Ах да, я забыл про идентификатор записи. Так что в модели Book необходимо добавить:
[code language="php"]
protected $id;
public function getId() { return $this->id; }
public function setId($id) { $this->id = $id; return $this; }
[/code]
В маппер классе мы используем шлюз Model_DbTable_Book для манипуляции с данными таблицы. В папке models необходимо создать папку DbTable, внутри которой - файл Book.php.
[code language="php"] class Model_DbTable_Book extends Zend_Db_Table_Abstract { protected $_name = 'books'; } [/code]
Класс Model_DbTable_Book является наследником класса Zend_Db_Table_Abstract, так что методы типа find, insert, update, которые используются в маппер классе уже реализованы.
Можно теперь попробовать добавить книгу в базу данных, должно получиться =). Настало время для отображения всех книг, откроем indexAction контроллера Книги.
[code language="php"]
public function indexAction() { $mdlBook = new Model_BookMapper(); $this->view->books = $mdlBook->fetchAll(); }
[/code]
Добавим метод fetchAll в mapper класс
[code language="php"]
public function fetchAll() { $resultSet = $this->getDbTable()->fetchAll(); $entries = array(); foreach ($resultSet as $row) { $book = new Model_Book(); $book->setId($row->id) ->setName($row->name) ->setAuthor($row->author) ->setDescription($row->description); $entries[] = $book; } return $entries; }
[/code]
Создадим view для отображения всех книг
[code language="html"]
<!-- application/views/scripts/book/index.phtml -->
<h2>List of all books</h2> <?php if(count($this->books)) { ?> <dl> <?php foreach($this->books as $book) { ?> <dt> <?=$book->name?> | <small>author: <?=$book->author?></small> | <a href="/book/update/id/<?=$book->id?>">update</a> <a href="/book/delete/id/<?=$book->id?>">delete</a> </dt> <dd> <?=$book->description?> </dd> <?php } ?> </dl> <?php }
[/code]
Можно насладиться результатом, открыв страницу /book. На этом, пожалуй, я сегодня закончу.
Реализацию методов update и delete оставлю вам в качестве упражнения ;) Если не получится, задавайте вопросы, с радостью отвечу.
На всякий случай прикрепил исходники проекта.
Ссылки: