CakePHP: несколько объединений, отношения ownTo и hasMany выполнены в двух запросах
Мне нужна помощь с CakePHP 2.2.3.
Что я имею
У меня есть следующие настройки на данный момент:
Post
имеет много Attachment
Он работает нормально, и страница генерируется с 2 запросами:
SELECT *, `Post`.`id`
FROM `posts` AS `Post`
WHERE 1 = 1
ORDER BY `Post`.`created` DESC
SELECT
`Attachment`.`id`,
`Attachment`.`post_id`,
`Attachment`.`created`
FROM
`attachments` AS `Attachment`
WHERE
`Attachment`.`post_id` IN (1, 2, 3, ..., n)
Что я хочу
Я хочу расширить отношение следующим образом:
Post
имеет много Attachment
; каждый Attachment
принадлежит Type
И я не знаю, как заставить CakePHP следовать ей.
По сути, мне нужно:
SELECT *, `Post`.`id`
FROM `posts` AS `Post`
WHERE 1 = 1
ORDER BY `Post`.`created` DESC
SELECT
`Attachment`.`id`,
`Attachment`.`post_id`,
`Attachment`.`created`,
`Type`.`title`, `Type`.`icon`
FROM
`attachments` AS `Attachment`
LEFT JOIN
`types` AS `Type`
ON (`Attachment`.`type_id`=`Type`.`id`)
WHERE
`Attachment`.`post_id` IN (1, 2, 3, ..., n)
Обратите внимание LEFT JOIN types
добавлено.
Таким образом, я получаю данные соответствующего типа во втором запросе. Я знаю, что мог бы получить данные в цикле или используя ->query()
Позвоните, но я хочу, чтобы это было максимально эффективно и гибко.
Эта проблема
Я попробовал трюк с контейнерами, отсоединение модели ( и этот), но безуспешно. Я пробовал разные комбинации опций, я думаю, что я даже удалил соединения. Вот что мой PostsController
выглядит как сейчас.
class PostsController extends AppController {
public function index() {
$this->Post->unbindModel(array('hasMany' => array('Attachment')));
$this->Post->Attachment->unbindModel(array('belongsTo' => array('Type')));
$this->Post->bindModel(array(
'hasMany' => array(
'Attachment' => array(
'className' => 'Attachment',
// when uncommented, throws the "Unknown column Post.id" SQLSTATE error
// 'conditions' => array('Post.id' => 'Attachment.post_id'),
'foreignKey' => false,
),
),
));
$this->Post->Attachment->bindModel(array(
'belongsTo' => array(
'Filetype' => array(
'className' => 'Filetype',
// 'conditions' => array('Type.id' => 'Attachment.type_id'),
'foreignKey' => false,
),
),
));
$all = $this->Post->find('all', array(
'joins' => array(
array(
'table' => 'users',
'prefix' => '',
'alias' => 'User',
'type' => 'INNER',
'conditions' => array(
'User.id = Post.user_id',
)
),
),
'contain' => array('Attachment', 'Type'),
'conditions' => array(),
'fields' => array('*'),
'order' => 'Post.created ASC'
));
var_dump($all);exit;
}
}
Но он просто выполняет дополнительный запрос для каждой итерации в цикле и получает все вложения:
SELECT `Attachment`.`id`, ...
FROM `attachments` AS `Attachment`
WHERE 1 = 1
Когда я раскомментирую условие для этой ассоциации, он выдает мне SQLSTATE "Столбец Post.id not found error" - я думаю, потому что здесь нет присоединенной таблицы Post.
Мне нужна помощь в настройке этого.
Пожалуйста помоги! Спасибо
ОБНОВИТЬ
Я изменил контроллер следующим образом. Обратите внимание, что нет bindModel
/ unbindModel
код, отношение устанавливается в классах моделей (это правильно в этом случае?).
class PostsController extends AppController {
public function index() {
$options = array(
'contain' => array(
'Post',
'Type'
),
'order' => 'Post.created DESC',
'conditions' => array(
// 'Post.title LIKE' => 'my post'
)
);
// The following throws "Fatal error: Call to a member function find() on a non-object"
// $posts = $this->Attachment->find('all', $options);
// So I had to use $this->Post->Attachment instead of $this->Attachment
$posts = $this->Post->Attachment->find('all', $options);
$this->set(compact('posts'));
}
}
Это Attachment
модель:
class Attachment extends AppModel {
public $belongsTo = array(
'Type' => array(
'className' => 'Type',
'foreignKey' => 'type_id',
),
'Post' => array(
'className' => 'Post',
'foreignKey' => 'post_id',
),
);
}
Приведенный выше код выполняет этот запрос:
SELECT
`Attachment`.`id`, `Attachment`.`type_id`, `Attachment`.`post_id`, `Attachment`.`created`,
`Type`.`id`, `Type`.`title`,
`Post`.`id`, `Post`.`text`, `Post`.`created`
FROM
`attachments` AS `Attachment`
LEFT JOIN `types` AS `Type` ON (`Attachment`.`type_id` = `Type`.`id`)
LEFT JOIN `posts` AS `Post` ON (`Attachment`.`post_id` = `Post`.`id`)
WHERE
1 = 1
ORDER BY
`Post`.`created` ASC
Все о вложениях здесь. Я имею в виду, что сообщения присоединяются к вложениям, поэтому, если сообщение не имеет вложений, оно не возвращается. Это, вероятно, потому что вызов Attachment->find()
так что это с точки зрения вложения. Я думаю, это просто должно быть:
// ...
FROM
`posts` AS `Post`
LEFT JOIN `attachments` AS `Attachment` ON (`Attachment`.`post_id` = `Post`.`id`)
LEFT JOIN `types` AS `Type` ON (`Attachment`.`type_id` = `Type`.`id`)
// ...
Но это не сработает, не так ли? Вы видите, что есть posts
, attachments
а также types
, но у них есть разные типы отношений. Первоначально я разместил эти два отдельных запроса, выполняемых CakePHP - для этого должны быть причины.
UPDATE2
Я до сих пор считаю, что все дело в изменении второго запроса на Attachment
модель в начальной настройке (см. раздел " Что я хочу "). Поэтому я буду получать типы вложений вместе с самими вложениями. Я имею в виду в этом случае оставил присоединиться к types
стол к attachments
не нарушит ли логики отношений с базами данных, не так ли?
Я просто хочу убедиться, что нет способа сделать это с одним комплексом, но одним find()
вызов.
2 ответа
Всякий раз, когда Cake видит отношение hasMany, он автоматически создает несколько запросов для извлечения данных. При построении этих запросов он ищет отношения, которые могут быть ЛЕВЫМИ, присоединенными к нему (hasOne и serveTo).
Поскольку Cake не может сделать это за вас, вам нужно объединить их самостоятельно.
public function index() {
$posts = $this->Post->find('all');
// get all attachments for all found posts
$attachments = $this->Post->Attachment->find('all', array(
'contain' => array('Type'),
'conditions' => array('Post.id' => Set::extract('/Post/id', $posts)
));
// now join them to the posts array
foreach ($posts as $key => $data) {
$postId = $data['Post']['id'];
// append any data related to this post to the post's array
$posts[$key] += Set::extract("/Attachment[post_id=$postId]/..", $attachments);
}
$this->set(compact('posts'));
}
Это не самый эффективный способ сделать это, так как вы будете перебирать $attachments
массив несколько раз, но я уверен, что вы поняли.
Попробуйте поиск запросов в hasMany. Например: в модели Post,
public $hasMany = array(
'Attachment' => array(
'className' => 'Attachment',
'foreignKey' => 'post_id',
'dependent' => false,
'conditions' => '',
'fields' => '',
'order' => '',
'limit' => '',
'offset' => '',
'exclusive' => '',
'finderQuery' => '
SELECT
`Attachment`.`id`,
`Attachment`.`post_id`,
`Attachment`.`created`,
`Type`.`title`,
`Type`.`icon`
FROM
`attachments` AS `Attachment`
LEFT JOIN
`types` AS `Type`
ON (`Attachment`.`type_id`=`Type`.`id`)
WHERE
`Attachment`.`post_id` IN (1, 2, 3, ..., n)
',
'counterQuery' => ''
)