Исключить пустые результаты для отношения HABTM в CakePHP

#cakephp #cakephp-1.3 #cakephp-model

#cakephp #cakephp-1.3 #cakephp-модель

Вопрос:

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

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

 posts ( id, title )
tags ( id, name )
posts_tags ( post_id, tag_id )
 

Как вы находите только те теги, с которыми связано одно или несколько сообщений (т. Е. Исключаете теги, которые не возвращают сообщений)?

Идеальный набор результатов будет выглядеть примерно так (кавычки добавлены для форматирования):

 Array (
    [0] => Array (
            [Tag] => Array (
                      [id] => 1
                      [name] => 'Tag1' )
            [Post] => Array (
                      [0] => Array (
                              [id] => 1
                              [title] => 'Post1' )
                      [1] => Array (
                              [id] => 4
                              [title] => 'Post4' ) )
    )
    [1] => Array (
            [Tag] => Array (
                      [id] => 4
                      [name] => 'Tag5' )
            [Post] => Array (
                      [0] => Array (
                              [id] => 4
                              [title] => 'Post4' )
                      [1] => Array (
                              [id] => 5
                              [title] => 'Post5' )
                      [2] => Array (
                              [id] => 6
                              [title] => 'Post6' ) )
    ) )
 

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

1. этот пост , похоже, решает эту проблему; но для 1.3.

Ответ №1:

Единственный способ, который я когда-либо находил, чтобы сделать это надежным способом, — использовать специальные соединения. Используя их, вы можете указать тип внутреннего соединения и получить именно то, что вы хотите.

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

1. Возможно, я что-то пропустил, но после опробования синтаксиса в ссылке я получаю массив, в котором да, в тегах есть сообщения, но тег появляется несколько раз (один раз для каждого связанного сообщения). Кроме того, заголовок сообщения не ссылается. Мысли?

Ответ №2:

Следующее было протестировано с помощью Cake 1.3.

Для начала вы, вероятно, хотите или уже делаете, чтобы отношение HABTM было определено в моделях для всех других обстоятельств, когда это обычно применяется:

 class Post extends AppModel {
    var $hasAndBelongsToMany = 'Tag';
}

class Tag extends AppModel {
    var $hasAndBelongsToMany = 'Post';
}
 

Согласно собственной документации Cake:[1]

В CakePHP некоторые ассоциации (belongsTo и hasOne) выполняют автоматические объединения для извлечения данных, поэтому вы можете отправлять запросы для извлечения моделей на основе данных в связанной модели.

Но это не относится к ассоциациям hasMany и hasAndBelongsToMany. Вот где на помощь приходит принудительное объединение. Вам нужно только определить необходимые соединения для объединения таблиц и получения желаемых результатов для вашего запроса.

Исключение пустых результатов HABTM является одним из таких случаев. В этом же разделе книги Cake объясняется, как это сделать, но я не нашел слишком очевидным из чтения текста, что результат достигает этого. В примере в книге Cake они используют путь join Book -> BooksTag -> Теги вместо нашего тега -> PostsTag -> Posts . Для нашего примера мы бы настроили его следующим образом из в TagController:

 $options['joins'] = array(
    array(
        'table'      => 'posts_tags',
        'alias'      => 'PostsTag',
        'type'       => 'INNER',
        'foreignKey' => false,
        'conditions' => 'PostsTag.tag_id = Tag.id'
    ),
    array(
        'table'      => 'posts',
        'alias'      => 'Post',
        'type'       => 'INNER',
        'foreignKey' => false,
        'conditions' => 'Post.id = PostsTag.post_id'
    )
);

$tagsWithPosts = $this->Tag->find('all', $options);
 

Убедитесь, что для внешнего ключа установлено значение false. Это говорит Cake, что он не должен пытаться выяснить условие соединения и вместо этого использовать только условие, которое мы предоставили.

Обычно это приводит к возврату повторяющихся строк из-за характера соединений. Чтобы уменьшить возвращаемый SQL, используйте DISTINCT для полей по мере необходимости. Если вы хотите, чтобы все поля, как обычно, возвращались с помощью find(‘all’), это усложняет то, что вам нужно жестко кодировать каждый столбец. (Конечно, структура вашей таблицы не должна меняться так часто, но это может произойти, или если у вас может быть просто много столбцов). Чтобы захватить все столбцы программно, добавьте следующее перед вызовом метода find:

 $options['fields'] = array('DISTINCT Tag.'
                   . implode(', Tag.', array_keys($this->Tag->_schema)));
// **See note
 

Важно отметить, что отношение HABTM выполняется ПОСЛЕ основного выбора. По сути, Cake получает список подходящих тегов, а затем выполняет еще один раунд операторов SELECT для получения связанных записей; вы можете видеть это из дампа SQL. «Соединения», которые мы настраиваем вручную, применяются к первому выбору, предоставляя нам желаемый набор тегов. Затем встроенный HABTM снова запустится, чтобы предоставить нам ВСЕ связанные записи с этими тегами. У нас не будет тегов, в которых нет записей, наша цель, но мы можем получить записи, связанные с тегом, которые не являются частью ни одного из наших начальных «условий», если они были добавлены.

Например, добавив следующее условие:

 $options['conditions'] = 'Post.id = 1';
 

Даст следующий результат:

 Array (
    [0] => Array (
            [Tag] => Array (
                      [id] => 1
                      [name] => 'Tag1' )
            [Post] => Array (
                      [0] => Array (
                              [id] => 1
                              [title] => 'Post1' )
                      [1] => Array (
                              [id] => 4
                              [title] => 'Post4' ) )
    )
)
 

Основываясь на образце данных в вопросе, только Tag1 был связан с нашим оператором «условия». Итак, это был единственный результат, возвращенный «объединениями». Однако, поскольку HABTM запускался после этого, он захватывал все сообщения (Post1 и Post4), которые были связаны с тегом1.

Этот метод использования явных объединений для получения желаемого начального набора данных также обсуждается в кратком руководстве — выполнение специальных объединений в Model::find(). В этой статье также показано, как обобщить метод и добавить его в AppModel, расширяющий find() .

Если бы мы действительно хотели видеть только Post1, нам нужно было бы добавить предложение опции ‘contain’ [2]:

 $this->Tag->Behaviors->attach('Containable');
$options['contain'] = 'Post.id = 1';
 

Предоставление результата:

 Array (
    [0] => Array (
            [Tag] => Array (
                      [id] => 1
                      [name] => 'Tag1' )
            [Post] => Array (
                      [0] => Array (
                              [id] => 1
                              [title] => 'Post1' ) )
    )
)
 

Вместо использования Containable вы можете использовать bindModel для переопределения отношения HABTM с этим экземпляром find() . В bindModel вы должны добавить желаемое условие Post:

 $this->Tag->bindModel(array(
    'hasAndBelongsToMany' => array(
        'Post' => array('conditions' => 'Post.id = 1'))
    )
);
 

Я чувствую, что для новичков, пытающихся разобраться в автоматических способностях cake, создание явных объединений легче увидеть и понять (я знаю, что это было для меня). Другим допустимым и, возможно, более простым способом сделать это было бы использовать исключительно unbindModel и bindModel . Teknoid на http://nuts-and-bolts-of-cakephp.com есть хорошая статья о том, как это сделать: http://nuts-and-bolts-of-cakephp.com/2008/07/17/forcing-an-sql-join-in-cakephp /. Кроме того, teknoid превратил это в поведение, которое вы можете получить из github: http://nuts-and-bolts-of-cakephp.com/2009/09/26/habtamable-behavior /

** Это приведет к извлечению столбцов в порядке, определенном в базе данных. Таким образом, если первичный ключ не определен первым, он может не применять DISTINCT, как ожидалось. Возможно, вам потребуется изменить это, чтобы использовать array_diff_key для фильтрации первичного ключа из $this->Model-> PrimaryKey.