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' => ''
    )
Другие вопросы по тегам