05. Модель. Адаптер БД и Zend_Db_Table. Связи между таблицами.
Порядок работы с базой данных.
Для того, чтобы мы могли использовать все возможности, которые предоставляет нам ZF, мы должны работать с каждой таблицей БД через отдельный объект класса Zend_Db_Table он предоставляет единый интерфейс для доступа к данным, не смотря на то, какую именно СУБД мы решили использовать. Но поэтому сначала необходимо соединится с базой данных через адаптер. Кстати для этого нам понадобится расширение pdo для php и драйвер pdo_mysql для него, это не экзотика, так что надеюсь проблем не будет. Теперь по порядку.
Соединения с базой. Адаптер.
Существует класс Zend_Db, он выполнен в соответствии с шаблоном проектирования "Фабричный метод" (Factory Method) и содержит всего один статичный метод factory(), призванный для инстанцирования нужного нам класса, в зависимости от используемой СУБД.
Соединиться с базой и получить адаптер, можно следующим образом:
require_once 'Zend/Db.php';
$adapter = Zend_Db::factory(
pdo_mysql, // драйвер
array(
'host' => $config->db->host,
'username' => $config->db->user,
'password' => $config->db->pass,
'dbname' => $config->db->name
)
);
В принципе соединение с базой уже есть и можно выполнять различные запросы и через адаптер, но мы пойдем дальше.
Работа с таблицами. Модель.
Начинается с создания класса для таблицы, например:
require_once 'Zend/Db/Table/Abstract.php';
class User extends Zend_Db_Table_Abstract
{
protected $_name = 'users';
protected $_primary = array('login');
function init()
{ }
}
Затем создание объекта этого класса с передачей адаптера:
$table new Users(array('db' => $adapter));
После чего можно легко работать с данными. Например:
$data = array(
'login' => 'admin',
'name' => 'Ivan',
'email' => 'ivan@2developers.net'
);
$table->insert($data);
И другие операции подробно расписаны в мануале. Есть другой момент, которому уделено, на мой взгляд, мало внимания это связи.
Связи между таблицами.
Ниже приведен пример связи между тремя таблицами: "Комментарии", "Статьи", "Пользователи". С помощью связей добиваемся следующего:
-легко получить по id-пользователя все его статьи, а по id-статьи все комментарии к ней, не составляя SQL-запросов, а просто одним методом;
-сделать так, чтобы при удалении пользователя удалялись все его статьи и соответственно комментарии к ним.
class User extends Zend_Db_Table_Abstract
{
// имя таблицы в БД
protected $_name = 'users';
protected $_primary = array('login');
// Зависимые таблицы, а точнее - их классы
protected $_dependentTables = array('Post', 'Comment');
function init()
{ }
}
class Post extends Zend_Db_Table_Abstract
{
protected $_name = 'posts';
protected $_primary = array('id');
protected $_dependentTables = array('Comment');
// Описание связей
protected $_referenceMap = array(
'User' => array(
'columns' =>'user_id', //поле в таблице posts
'refTableClass' => 'User', //класс для таблицы users
'refColumns' => 'id', //primary key в классе User
)
);
function init()
{ }
}
class Comment extends Zend_Db_Table_Abstract
{
protected $_name = 'comments';
protected $_primary = array('id');
protected $_referenceMap = array(
'Post' => array(
'columns' => 'post_id',
'refTableClass' => 'Post',
'refColumns' => 'id',
'onDelete' => self::CASCADE //каскадное удаление - при удалении статьи, удалять ее каменты
),
'User' => array(
'columns' =>'user_id',
'refTableClass' => 'User',
'refColumns' => 'id'
// нет каскадного удаления - не удалять все каменты удаляемого пользователя
)
);
function init()
{ }
}
Таким образом получить все статьи пользователя можно так:
$users = $user_model->find($login);
$user = $users->current();
// укаываем только класс где искать $_referenceMap с описанием связей
$posts = $user->findDependentRowset('Post');
// Получить все его комментарии
$comments = $user->findDependentRowset('Comment');
Так же и со статьями и комментариями. А с удалением еще легче:
//просто удаляем статью и комментарии к ней автоматически удалятся
$posts = $post_model->find($id);
$posts->current()->delete();
Вместо заключения - создание хэлпера для работы с БД
Для демо приложения я решил написать такой хэлпер, для удобства работы с базой данных.
lib/Divo/Helper/Action/Db.php
require_once 'Zend/Controller/Action/Helper/Abstract.php';
class Divo_Helper_Action_Db extends Zend_Controller_Action_Helper_Abstract
{
protected $db;
public $config;
// Выполняется соединение. Только один раз, при первом вызове помощника
public function __construct()
{
// предварительно нужно добавить в config.ini доступы к БД
$this->config = Divo_Controller::divoLoadConfig('config.ini', 'DB');
require_once 'Zend/Db.php';
$this->db = Zend_Db::factory($this->config->type,
array('host' => $this->config->host,
'username' => $this->config->user,
'password' => $this->config->pass,
'dbname' => $this->config->name
)
);
$this->db->query('SET NAMES UTF8');
// для отладки (отдельная тема на потом)
$this->db->getProfiler()->setEnabled($this->config->debugging);
}
/** @return object Zend_Db_Adapter_Pdo_Mysql
возвращает адаптер */
public function direct()
{
return $this->db;
}
/** @param str Имя класса модели например Post или User
@return object
Возвращает объект класса, созданного для работы с таблицей */
public function getModel($name)
{
require_once(APPPATH."/models/$name.php");
$cache = Zend_Controller_Action_HelperBroker::getStaticHelper('Cache');
return new $name(array('metadataCache' => $cache->getCoreCache(), 'db' => $this->db));
}
}
Выполнить запрос к базе можно следующим образом, в любом контроллере:
$users = $this->_helper->db()->fetchAll("SELECT * FROM users");
Или получить модель и выполнить запрос:
$user_model = $this->_helper->db->getModel('User');
$users = $user_model->fetchAll();
Комментарии
Отличная статья! Действительно связи в мануале обделили вниманием.
Спасибо за отзыв.
Спасибо вам огромное за статью, долго искал подобное решение, головой об стенку бился, а тут на тебе почти готовое решение. Еще раз спасибо.