Следующее было протестировано с Cake 1.3.
Для начала вы, вероятно, хотите или уже хотите, чтобы отношение HABTM было определено в моделях для всех других обстоятельств, где это обычно применяется:
class Post extends AppModel {
var $hasAndBelongsToMany = 'Tag';
}
class Tag extends AppModel {
var $hasAndBelongsToMany = 'Post';
}
Согласно собственной документации Cake: [ 1 ]
В CakePHP некоторые ассоциации (ownTo и 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);
Убедитесь, что для ForeignKey установлено значение 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, нам нужно было бы добавить предложение 'содержать' [ 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'))
)
);
Я чувствую, что для начинающих, пытающихся обернуть голову вокруг автоматических способностей торта, сделать явные объединения легче увидеть и понять (я знаю, что это было для меня). Другой допустимый и, возможно, более «подходящий» способ сделать это - использовать исключительно unbindModel и bindModel. Teknoid на http://nuts -and-bolts-of-cakephp.com имеет хорошую статью о том, как это сделать: http://nuts -and-bolts-of-cakephp.com / 2008/07/17 / принуждая-ан-SQL-нарисуй в-CakePHP / . Кроме того, Teknoid превратил это в поведение, которое вы можете получить из github: http://nuts -and-bolts-of-cakephp.com / 2009/09/26 / habtamable-поведения /
** Это будет тянуть столбцы в порядке, определенном в базе данных. Поэтому, если первичный ключ не определен первым, он может не применять DISTINCT, как ожидалось. Возможно, вам придется изменить это, чтобы использовать array_diff_key для фильтрации первичного ключа из $ this-> Model-> primaryKey.