Следующее было протестировано с 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 Book они используют путь \join Book -> BooksTag -> Tags вместо нашего Tag -> 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);
Убедитесь, что для параметра externalKey установлено значение 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), которые были связаны с Tag1.
Этот метод использования явных соединений для получения желаемого начального набора данных также обсуждается в Краткий совет: выполнение нерегламентированных соединений в 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 вы должны добавить желаемое условие публикации:
$this->Tag->bindModel(array(
'hasAndBelongsToMany' => array(
'Post' => array('conditions' => 'Post.id = 1'))
)
);
Я чувствую, что для новичков, пытающихся понять автоматические способности торта, создание явных соединений легче увидеть и понять (я знаю, что это было для меня). Другим допустимым и, возможно, более «тортовым» способом сделать это было бы использование исключительно 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.
person
Aaron K
schedule
27.10.2011