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

Связывание таблиц в Model::find()

Время на прочтение 7 мин
Количество просмотров 2.3K
Автор оригинала: Nate
Перевод заметки на Bakery от Nate, одного из авторов CakePHP. Мне она показалась интересной и иллюстрирующей как же работает этот фреймворк, но сложной для понимания на английском языке.

В этой заметке описывается малоизвестный приём, позволяющий осуществлять связывание таблиц (joins) в запросах CakePHP напрямую, не используя методы bind и unbind.

Внимание: Приём сработает только если вы используете новый синтаксис Model::find(), который имеет всего два параметра. В противном случае читайте Cookbook или API.


Одна из «фишек» дизайна CakePHP — «слоёность». Например: множество методов Helper принимают параметр $options, а методы, построеные на их основе (смотрите методы FormHelper или PaginatorHelper), позволяют вам передавать некоторые настройки на нижние уровни, предоставляя возможность тонкой регулировки даже с высоких уровней абстракции.

Так и с моделями: все параметры, переданые в Model::find(), далее передаются для обработки в DboSource, который отвечает за генерацию SQL запросов. То есть, вы можете передавать некоторые параметры, в частности параметр «joins», с более высокого уровня в DboSource.

Одним из наиболее распространненых примеров является поиск по тэгам, которые соотносятся с моделью как «многие-ко-многим» (hasAndBelongsToMany). Обычно это достигается связыванием (binding) моделей или написанием запроса вручную. Но такого же результата можно добиться и просто описав связь с помощью параметра «joins».
В данный момент я работаю над одним проектом, который позволяет оставлять отметки на карте и искать отметки по их тэгам. В форме поиска по тэгам, отправляемой методом «get», есть текстовое поле «q», которое принимает разделенные пробелом тэги. Вот пример кода для поиска в MarkersController:

<?php 
    $markers 
$this->Marker->find('all', array('joins' => array(
        array(
            
'table' => 'markers_tags',
            
'alias' => 'MarkersTag',
            
'type' => 'inner',
            
'foreignKey' => false,
            
'conditions'=> array('MarkersTag.marker_id = Marker.id')
        ),
        array(
            
'table' => 'tags',
            
'alias' => 'Tag',
            
'type' => 'inner',
            
'foreignKey' => false,
            
'conditions'=> array(
                
'Tag.id = MarkersTag.tag_id',
                
'Tag.tag' => explode(' '$this->params['url']['q'])
            )
        )
    )));
?>


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

<?php 
class AppModel extends Model {
    
public function find($type$options = array()) {
        if (!isset(
$options['joins'])) {
            
$options['joins'] = array();
        }
        switch (
$type) {
            case 
'matches':
                if (!isset(
$options['model']) || !isset($options['scope'])) {
                    break;
                }
                
$assoc $this->hasAndBelongsToMany[$options['model']];
                
$bind "{$assoc['with']}.{$assoc['foreignKey']} = {$this->alias}.{$this->primaryKey}";

                
$options['joins'][] = array(
                    
'table' => $assoc['joinTable'],
                    
'alias' => $assoc['with'],
                    
'type' => 'inner',
                    
'foreignKey' => false,
                    
'conditions'=> array($bind)
                );
                
$bind $options['model'] . '.' $this->{$options['model']}->primaryKey ' = ';
                
$bind .= "{$assoc['with']}.{$assoc['associationForeignKey']}";
                
$options['joins'][] = array(
                    
'table' => $this->{$options['model']}->table,
                    
'alias' => $options['model'],
                    
'type' => 'inner',
                    
'foreignKey' => false,
                    
'conditions'=> array($bind) + (array)$options['scope'],
                );
                unset(
$options['model'], $options['scope']);
                
$type 'all';
            break;
        }
        return 
parent::find($type$options);
    }
}
?>


Вот так вместо того, чтобы жестко задавать связи для одной ассоциации, мы делаем код более универсальным и помещаем его внутрь AppModel. Таким образом мы можем его использовать для любой связи «многие-ко-многим» и любого поля связанной таблицы.
Прежде чем разобраться с тем как работает код, давайте посмотрим как его использовать:

<?php 
    $markers 
$this->Marker->find('matches', array(
        
'model' => 'Tag',
        
'scope' => array('Tag.tag' => explode(' '$this->params['url']['q']))
    ));
?>


Теперь, аккуратно «спрятав» логику в модель, мы лишь указываем имя связаной модели и критерии фильтрации при связывании вместо построения запутаных связей в контроллере. Так как указываемая фильтрация используется только для поиска строк и не затрагивает список запрашиваемых полей, данные возвращаются в том же формате что и обычно, не передавая лишней информации.

Переработанный нами метод очень прост, если разобраться. Для каждой связи мы делаем то же, что обычно делает CakePHP, используя указанные для модели ассоциативные связи — заполняем параметр «joins». Переменная $bind используется для формирования строки, определяющей отношение внешних ключей для связываемых таблиц. Так же мы используем INNER JOIN вместо LEFT JOIN, так как нам не нужны записи, не соответствующие нашим критериям фильтрации.

Надеюсь этот совет поможет вам в работе. Если у вас есть любые вопросы или комментарии — пишите.
Теги:
Хабы:
+3
Комментарии 8
Комментарии Комментарии 8

Публикации

Истории

Ближайшие события

Московский туристический хакатон
Дата 23 марта – 7 апреля
Место
Москва Онлайн
Геймтон «DatsEdenSpace» от DatsTeam
Дата 5 – 6 апреля
Время 17:00 – 20:00
Место
Онлайн