Pull to refresh

Zend_Db_Table простота в использовании

Reading time6 min
Views5.3K
Мало кто использует этот удивительный класс на полную мощность. О некоторых его скрытых возможностях также мало кто догадывался, как и я до этого момента.

Как известно таблицы в реляционных базах данных связываются отношениями один ко многим и многие ко многим. Возможно кто-то предложит ещё пару-тройку связей, но данная статья не для того чтобы подискутировать по этому поводу, а для того чтобы подсказать и направить мысль в нужное нам русло. Я рассмотрю связь один ко многим в реализации, как вы наверно догадались, Zend Framework'а.

Как известно класс Zend_Db_Table является объектно-ориентированным интерфейсом к таблицам баз данных. Первым полезным открытием было то, что Zend_Db_Table способен производить каскадное удаление и каскадное обновление записей связей, для чего это необходимо, да для того чтобы при удалении/обновлении ссылочного значения удалились/обновились записи в зависимых таблицах. Для примера будем использовать базу данных MySQL с механизмом хранения данных MyISAM, который не поддерживают декларативной ссылочной целостности.

Сейчас небольшое отступление. Нам для примера необходима небольшая база данных состоящая из следующих таблиц: Products, Units и Groups. Речь пойдет о продуктах питания (Products), единицах измерения (Units) и группах продуктов (Groups).

Ниже SQL запросы для создания и заполнения таблиц:
# таблица единиц измерения продуктов
DROP TABLE IF EXISTS `units`;
CREATE TABLE `units` (
  `unit_id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
  `unit_name` VARCHAR(256) NOT NULL DEFAULT '',
  PRIMARY KEY (`unit_id`)
) ENGINE=MYISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

INSERT INTO `units` SET `unit_name` = 'грамм';
INSERT INTO `units` SET `unit_name` = 'миллилитр';

# таблица групп продуктов
DROP TABLE IF EXISTS `groups`;
CREATE TABLE `groups` (
  `group_id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
  `group_name` VARCHAR(256) NOT NULL DEFAULT '',
  PRIMARY KEY (`group_id`)
) ENGINE=MYISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

INSERT INTO `groups` SET `group_name` = 'Овощи';
INSERT INTO `groups` SET `group_name` = 'Фрукты';
INSERT INTO `groups` SET `group_name` = 'Молочные';
INSERT INTO `groups` SET `group_name` = 'Мясные';

# таблица продуктов
DROP TABLE IF EXISTS `products`;
CREATE TABLE `products` (
  `product_id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
  `group_id` INT(10) DEFAULT NULL,
  `unit_id` INT(10) DEFAULT NULL,
  `product_name` VARCHAR(256) NOT NULL DEFAULT '',
  PRIMARY KEY (`product_id`)
) ENGINE=MYISAM DEFAULT CHARSET=utf8;

# Овощи
INSERT INTO `products` (`group_id`, `unit_id`, `product_name`) VALUES (1, 1, 'Картофель');
INSERT INTO `products` (`group_id`, `unit_id`, `product_name`) VALUES (1, 1, 'Помидор');
# Фрукты
INSERT INTO `products` (`group_id`, `unit_id`, `product_name`) VALUES (2, 1, 'Абрикос');
INSERT INTO `products` (`group_id`, `unit_id`, `product_name`) VALUES (2, 1, 'Яблоко');
# Молочные
INSERT INTO `products` (`group_id`, `unit_id`, `product_name`) VALUES (3, 1, 'Брынза');
INSERT INTO `products` (`group_id`, `unit_id`, `product_name`) VALUES (3, 2, 'Молоко');
# Мясные
INSERT INTO `products` (`group_id`, `unit_id`, `product_name`) VALUES (4, 1, 'Телятина');
INSERT INTO `products` (`group_id`, `unit_id`, `product_name`) VALUES (4, 1, 'Свинина');

Теперь когда таблицы созданы и заполнены тестовыми данными, приступаем к созданию классов для наших таблиц:

class Products extends Zend_Db_Table_Abstract
{
    protected $_name = 'products';
    protected $_primary = array('product_id');
}
        
class Units extends Zend_Db_Table_Abstract
{
    protected $_name = 'units';
    protected $_primary = array('unit_id');
}
 
class Groups extends Zend_Db_Table_Abstract
{
    protected $_name = 'groups';
    protected $_primary = array('group_id');
}

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

$productsTable = new Products;
$productsRowset = $productsTable->fetchAll();
foreach($productsRowset as $row) {
    echo '<pre>' . print_r($row->toArray(), true) . '</pre>' . PHP_EOL;
}

Как видим метод fetchAll возвращает нам набор строк (rowset), где каждый $row соответствует строке из нашей таблицы. Если добавить следующий код в foreach, то он способен изменить конкретную запись:

if ($row->product_name == 'Свинина') {
    $row->product_name = 'Шашлык из свинины';
    $row->save();
}

К нашей «строке» $row применим метод save из абстрактного класса Zend_Db_Table_Row_Abstract, поскольку foreach разбирает наш rowset, который является экземпляром класса Zend_Db_Table_Rowset, наследуемый от Zend_Db_Table_Row_Abstract на отдельные $row. Сами же $row ничто иное как экземпляры класса Zend_Db_Table_Row. Манипуляции с экземплярами класса Zend_Db_Table_Row не требуют глубоких познаний, поэтому мы не будем на них останавливаться, а поговорим о следующем.

Допустим вам не нравится порядок первичных ключей в таблице groups и вы хотите чтобы они начинались, например со 100, не проблема, но сколько действий надо совершить чтобы это реализовать? Голова начинает идти кругом. Посмотрите на таблицу products, в ней мы предусмотрели внешнюю связь с таблицей groups, по ключу group_id. И после наших изменений продукты должны соответствовать своим группам. Возможно это не тот пример, который вы хотели бы увидеть и он не претендует на овации, но он способен показать каким образом реализован механизм взаимодействия классов Zend_Db_Table в Zend Framework'e.
Итак приступим. В объектной модели для родительских таблиц, необходимо указать зависимые таблицы. В классе Groups необходимо указать зависимый класс Products, добавив следующее свойство

protected $_dependentTables = array('Products');

также это необходимо сделать в классе Units. Эти нам говорит о том, что записи в зависимых таблицах будут браться в учёт при изменении родительских. Но для этого нам необходимо в классе Products добавить связи с таблицами Units и Groups. Реализовать это можно несколькими способами, например добавить свойство $_referenceMap в класс Products:

protected $_referenceMap = array(
    'refUnits' => array(
        self::COLUMNS => 'unit_id',
        self::REF_TABLE_CLASS => 'Units',
        self::REF_COLUMNS => 'unit_id',
        self::ON_DELETE => self::CASCADE,
        self::ON_UPDATE => self::CASCADE
    ),
    'refGroups' => array(
        self::COLUMNS => 'group_id',
        self::REF_TABLE_CLASS => 'Groups',
        self::REF_COLUMNS => 'group_id',
        self::ON_DELETE => self::CASCADE,
        self::ON_UPDATE => self::CASCADE
    )
);

Свойство $_referenceMap позволяет добавить ссылку на внешнюю таблицу, таким образом мы получаем связь многие к одному. В нём необходимо определить следующие параметры

1. ассоциативный ключ
2. имя поля внешнего ключа в ссылающейся таблице
3. имя класса таблицы на которую ссылаемся
4. имя поля в таблице на которую ссылаемся
5. действие которое произойдет при удалении
6. действие которое произойдет при обновлении

Код целиком:
class Products extends Zend_Db_Table_Abstract
{
    protected $_name = 'products';
    protected $_primary = array('product_id');

    protected $_referenceMap = array(
        'refUnits' => array(
            self::COLUMNS => 'unit_id',
            self::REF_TABLE_CLASS => 'Units',
            self::REF_COLUMNS => 'unit_id',
            self::ON_DELETE => self::CASCADE,
            self::ON_UPDATE => self::CASCADE
        ),
        'refGroups' => array(
            self::COLUMNS => 'group_id',
            self::REF_TABLE_CLASS => 'Groups',
            self::REF_COLUMNS => 'group_id',
            self::ON_DELETE => self::CASCADE,
            self::ON_UPDATE => self::CASCADE
        )
    );
}

class Units extends Zend_Db_Table_Abstract
{
    protected $_name = 'units';
    protected $_primary = array('unit_id');

    protected $_dependentTables = array('Products');
}

class Groups extends Zend_Db_Table_Abstract
{
    protected $_name = 'groups';
    protected $_primary = array('group_id');

    protected $_dependentTables = array('Products');
}

Теперь у нас есть связанные таблицы и мы можем не беспокоиться и спокойно апдейтить наши id ключи.
Кстати, забыл упомянуть, что при удалении какой-нибудь группы продуктов удаляться и сами продукты, которые соответствуют этим группам. Можете поэкспериментировать и удалить любую из них. Если вам интересно, то методы _cascadeUpdate и _cascadeDelete абстрактного класса Zend_Db_Table_Abstract отвечают за обновление и удаление и вызываются они при вызове $row->save() и $row->delete() соответственно. Также вы можете получить родительскую запись (row) вызвав метод findParentRow

$productsTable = new Products;
$productsRowset = $productsTable->fetchAll();
foreach($productsRowset as $row) {
    echo '-----------------------' . PHP_EOL;
    echo '<pre>' . print_r($row->toArray(), true) . '</pre>' . PHP_EOL;
    echo '<pre>' . print_r($row->findParentRow('Units')->toArray(), true) . '</pre>' . PHP_EOL;
    echo '<pre>' . print_r($row->findParentRow('Groups')->toArray(), true) . '</pre>' . PHP_EOL;
    echo '-----------------------' . PHP_EOL;
}

Либо получить все зависимые записи (rows) вызвав метод findDependentRowset

$groupsRowset = $groupsTable->fetchAll();
foreach($groupsRowset as $row) {
    echo '-----------------------' . PHP_EOL;
    echo '<pre>' . print_r($row->toArray(), true) . '</pre>' . PHP_EOL;
    echo '<pre>' . print_r($row->findDependentRowset('Products')->toArray(), true) . '</pre>' . PHP_EOL;
    echo '-----------------------' . PHP_EOL;
}

С полученными строками (rows) вы можете также свободно работать как с экземплярами класса Zend_Db_Table_Row. Удачи!

p.s. Привет Джос, учтены все твои пожелания.
Tags:
Hubs:
Total votes 29: ↑17 and ↓12+5
Comments44

Articles