#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.