Как стать автором
Обновить

Комментарии 44

Когда стояла эта задача я в первую очередь зашел на хабр и ничего подобного не нашел. И и пусть пишут про перепечатывание документации, но эти плоды результат моего дебага библиотеки ZF, это лучший способ заглянуть и узнать как это работает.
Вместо переопределения конструтора и вызова метода addReference можно записать все в протектед свойство:
protected $_referenceMap = array(
'Units' => array(
'columns' => 'unit_id',
'refTableClass' => 'Units'
'refColumns' => 'unit_id',
'onDelete' => self::CASCADE,
'onUpdate' => self::RESTRICT
) //, ну и для второго тоже
);
Можно, но так нагляднее, поскольку в данном случае я имею подсказку от ide.
И да, экстендить свои классы надо от Zend_Db_Table_Abstract а не от Zend_Db_Table
Действительно, признаю своё упущение.
И еще не забывайте определять protected $_primary;
Судя по коду, метаданные таблицы класс получает автоматически с помощью «DESCRIBE» (а это лишний запрос), по этому обратите внимание еще и на кеширование (рус, англ) этих данных… а также их можно "захардкодить" в класс и обойтись без кеша.
стоит ли писать 1ну статью одному нехабравчанину с 1 скриптом по «автозаполнению/обновлению» метадаты в моделях?
Использовать встроенные методы Zend_Db_Table_Abstract — плохая практика с точки зрения моделирования. Лучше добавлять в свой класс методы getAllProducts и т.д.
А вообще, еще лучше не пользоваться напрямую Zend_Db, а отделять модель и маппер.

А кэш к таким моделям лучше делать декоратором — тогда вручную вызывать кэшер не нужно будет.
Данной статья не о том как правильно проектировать, а о том как создавать привязку данных из одной таблицы к данным другой таблицы.
Два последних моих суждения, возможно, и не относятся к теме статьи, но я настаиваю на том, что применение fetchAll — это плохо. И совершенно ни к чему учить новичков сходу такой не очень хорошей практике. Впрочем, статья — ваша, а мнение — мое. Решать вам и вашим читателям.
Вы щас совершенно не по теме человека отчитываете. Всеравно ведь в Вашем getAllProducts будет fetchAll. Человек лишь показал связь, как ей пользоваться, каждому решать самому. Можете добавить метод, можете еще одну абстракцию сверху навесить, а можете и один в один слизать код. Думать тоже надо, а не тупо копировать код один в один (Это я о Ваших словах про новичков).
Как показывает опыт, новички сначала слизывают один в один, а потом уже в процессе работы начинают думать и понимать — в этом и заключается понятие «профессиональный рост». Да, в простейшем случае внутри getAllProducts будет только вызов fetchAll, но если позже потребуется добавить доп. функционал, то разработчик без опыта не станет рефакторить вызовы fetchAll в getAllProducts, а начнет добавлять копипастом новый функционал прямо в контроллер (или где у него будет этот fetchAll вызываться). Так вот если новичок все равно будет слизывать, так пусть он уже слизывает нормальный код и правильные подходы.

И я автора не отчитывал. Просто высказал свое мнение.
Вот как раз, как показывает опыт, если новичку дать сразу готовенькое и правильно, он зажрется. А если дать нечто абстрактное и низкоуровневое, то при первом толчке с проблемой он, хоть и не будет переделывать на правильное решение, но уже хоть задумается что что-то тут не так. На ошибках учиться надежней, чем кушать из ложечки.
Я использую fetchAll для тех целей, чтобы получить Zend_Db_Table_Rowset потом из ровсета получаю отдельные Zend_Db_Table_Row и только эти row могут работать с каскадными удалениями и обновлениями, также они могут находить Parent и Dependent rows.
Если у вас есть вариант получения данных более подходящий для этих целей, предложите.
Если говорить о совсем нормальном варианте, то вот. Это, скажем так, мое видение DDD. Вдохновлялся у Мэтью:

weierophinney.net/matthew/archives/202-Model-Infrastructure.html
weierophinney.net/matthew/archives/201-Applying-ACLs-to-Models.html
weierophinney.net/matthew/archives/200-Using-Zend_Form-in-Your-Models.html

Если же говорить о конкретно вашем случае, с использованием только Table Gateway, то я бы делал так:

Пусть нам изначально ставят задачу вывести список юзеров. Нет ничего проще.

class Users extends Zend_Db_Table_Abstract
{
    protected $_primary = 'id';
    
    public function getAll()
    {
        return $this->fetchAll();
    }
}

Казалось бы, ну что за идиотизм делать такую функцию, кому она нужна, если можно напрямую вызвать fetchAll???
Но тут начальство ставит нам задачу: выводить список юзеров + количество комментариев каждого юзера.
Все, что нам нужно, слегка модифицировать наш метод:
class User extends Zend_Db_Table_Row_Abstract
{
    protected $_commentsCount = 0;

    public function init()
    {
        parent::init();
        if (array_key_exists('commentsCount', $this->_data)) {
            $this->_commentsCount = (int)$this->_data['commentsCount'];
            unset($this->_data['commentsCount']);
        }
    }

    public function getCommentsCount()
    {
        return $this->_commentsCount;
    }
}

class Users extends Zend_Db_Table_Abstract
{
    protected $_primary = 'id';
    
    protected $_rowClass = 'User';

    public function getAll()
    {
        $select = $this->select()
            ->from(array('u' => 'Users'))
            ->joinLeft(array('c' => 'Comments'), 'c.user_id = u.id', array(
                                                   'commentsCount' => new Zend_Db_Expr('COUNT(c.*)')
                                               ))
            ->group('u.id');
        return $this->fetchAll($select);
    }
}

При таком решении никаких изменений в контроллере не потребуется.
Не нравятся join'ы??? Можно и без них, если правильно настроено кэширование:
class User extends Zend_Db_Table_Row_Abstract
{
    protected $_commentsCount;

    public function getCommentsCount()
    {
        if ($this->_commentsCount === null) {
            /**
             * @var Zend_Db_Table_Abstract $comments
             */
            $comments = $this->getTable()->getReference('Comments');
            $select = $comments->select()
                ->columns(array('commentsCount' => new Zend_Db_Expr('COUNT(*)')))
                ->where('user_id = ?', $this->id);
            $row = $comments->fetchRow($select);
            $this->_commentsCount = (int)$row->commentsCount;
        }
        return $this->_commentsCount;
    }
}

class Users extends Zend_Db_Table_Abstract
{
    protected $_primary = 'id';

    protected $_rowClass = 'User';

    public function getAll()
    {
        return $this->fetchAll();
    }
}
Боже упаси меня использовать подсчёт COUNT по depended rows. Если вы переживаете на этот счёт, то я этого не делаю. Мой вариант с join отличается от вашего, только лишь тем, что я не расширяю Zend_Db_Table_Row_Abstract, а выполняю select с join'ами в самой моделе. Тем не менее спасибо за пример.
А где же скрытые возможности? Пока в статье я вижу базовый функционал.
Да, действительно, пожалуй здесь бы подошло выражение «неочевидные-малоизвестные возможности»
Чем они неочевидны, если очевидно описаны в документации?
Что за странная манера у вас пересказывать ман?
а кто её читает?
Тот, кто пишет веб-приложения на ZF
Ман по zf одним чтением не заканчивается. Только перечитывание и рабочий кусок кода может дать понимание происходящего. Только после этого можно взглянуть в документацию и сказать: «Да чё там, всё же и так понятно!»
Чем чистый PDO не угодил?

Да и вообще, смотря на бенчмарки от ZF — плакать хочется.
Плакать больше хочется, когда смотришь решения этой проблемы на других технологиях. Сколько же нам еще в таком каменном языке жить %(
В zf предоставлено достойное и грамотно спроектированное решение связки таблиц. И вполне удобное в применении. И было бы интересно узнать, что вы подразумеваете под выражением «чистый PDO»?
Дайте, пожалуйста, линк и сорец где плакать от бенчмарка хочется. А то сколько смотрел, обычно бенчмарки выкладывают конкуренты, которые нахваливают только свой, типа у него по сравнению с другими +100500% производительность, безопасность, архитектура и бла бла бла. Если вы уделите время и откроете 5-10 офф сайтов разных фреймворков на разных языках вы увидите эту картину.
А разные блоггеры из тех что я видел не имеют должного уровня и опыта работы с фреймворком чтобы делать подобные бенчмарки. На каждую задачу свой инструмент, вот пусть кто-то сделает бенчмарки того же ZF под разные задачи, с использованием роутингов, моделей, автолоадом, кэшированием внутренним/сторонним и без каждого пункта и сравнит с такими же результатами коханы, уии или любым по вкусу. Вот тогда я скажу, да, действительно вот тут у ZF узкое место, но и будет видно почему. А так по опыту могу сказать, что люди просто не умеют его готовить. Не всегда нужно делать по примерам из документации, я бы даже сказал чаще всего это ресурсозатратно по причине неограниченной и ненужной расширяемости для среднестатистического проекта.
Вот
Также в одном из проектов пробовал использовать Zend_Db, Zend_Table_Abstract — ну долго, долго…
class Default_Models_Games_Table extends Zend_Db_Table_Abstract
{
protected $_name = 'games';
protected $_primary = array('game_id');
}

class Default_Models_Games
{
public function getGames()
{
$gamesModel = new Default_Models_Games_Table;
return $gamesModel->fetchAll();
}
}
это долго?..
возможно пример не самый подходящий, но это просто
Под долго я подразумевал скорость работы адаптера.
По вашей ссылке, Zend_Db показал очень неплохой результат.
Я не имею ничего против представленных адаптеров, но скорость PDO не оставляет мне выбора.
Первый минус, который я заметил в этом тесте, убрать все require_once, это дорогая операция для таких тестов.
У меня готова ещё одна статья про кэширование моделей, опубликовать её из-за минусовой кармы не имею возможности. Данная статья на данный момент имеет плюсовой рейтинг. Кому не безразлична судьба раздела Zend Framework на хабре и моя в частности, прошу плюсануть.
сааамый первый пример использования
>> $productsRowset = $productsTable->fetchRow();
видимо, должно быть
$productsRowset = $productsTable->fetchAll();
Спасибо, исправил.
Совет: вместо
echo '
' . print_r($row->toArray(), true) . '
' . PHP_EOL;

можно использовать
print Zend_Debug::dump($row);
Можно было бы рассказать, что в класе который расширяет Zend_Db_Table_Abstract можно переопределить поле primary $_rowClass = «Model_Custom_Row», в который можно какие — нибудь вкусности поместить.

class Model_Custom_Row extends Zend_Db_Table_Row_Abstract
{
//Переведем поля, которые можно перевести
public function getTranslate($lang, $field)
{
}
}

а далее
$result = $model->fetchRow($where);
$translatedDescription = $result->getTranslate(«en», «description»);

можно еще $_rowsetClass тоже переопределить, но это намного реже используется.
Ад как он есть.

Зачем эта вкусность? чтобы вместо

$translatedDescription = $translator->translate($result->description);

писать

$translatedDescription = $result->getTranslate(«en», «description»);

?? да ещё и язык за собой таскать
Речь идет про перевод динамического контента!
Вы же не будете при изменение контента в ДБ, лезть в массив с ключами и переводить ручками.
ааа… сообразил теперь ) тады разумно
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории