CakePHP Containable: загружается слишком много связей? - PullRequest
3 голосов
/ 15 ноября 2011

Я большой поклонник сдерживаемого элемента cakephp, потому что я всегда думал, что он справится с загрузкой дополнительных моделей соответствующим образом.Но в последние дни я копнул глубже и обнаружил, что на самом деле проблема с памятью.

Подумайте о следующей структуре модели:

  • В проекте много Стен
  • В проекте много Участников
  • В стене много Постов
  • Сообщение имеет много комментариев
  • Участник имеет много сообщений
  • Участник имеет много комментариев
  • Участник принадлежит пользователю
  • Участник принадлежит проекту

и наоборот

  • Запись принадлежит участнику
  • Запись принадлежит стене
  • Комментарий принадлежит участнику
  • Комментарий принадлежитПост
  • У пользователя много участников

В контроллере стены у меня есть следующее выражение-поиска:

$this->set(
  "posts", 
  $this->Post->find(
    "all", array(
      "conditions" => array("Post.wall_id" => $wall["Wall"]["id"]), 
      "contain" => array("Participant")
    )
  )
);

Я ожидаю, что cakephp найдет всепосты и включают только соответствующих участников-объектов.Но я получаю список всех сообщений (правильных) и их участников (правильных), а также соответствующей стены (неправильных) и соответствующих комментариев, если таковые имеются (неправильных).Так что с точки зрения производительности: слишком много объектов, что может привести к "FATAL ERROR - перегрузке памяти".

И для теоретически очень сексуальной и интересной части:

$this->set(
  "posts", 
  $this->Post->find(
    "all", array(
      "conditions" => array("Post.wall_id" => $wall["Wall"]["id"]), 
      "contain" => array("Participant.User")
    )
  )
);

Поскольку меня интересуют только объекты Post & Participant.User , я изменяюСодержит массив для Participant.User.Но, опять же, теперь я получаю не только объект User, но и все другие связанные объекты Участнику (проект, публикации, комментарии, ...), и дерево объектов намного больше, чем раньше.

Так что мне было интересно, как правильно это реализовать?Нужно ли явно устанавливать опцию «соединения» или мне нужно установить опцию поля (в корне или в опции-содержимого)?

Привет из Австрии.

Ответы [ 7 ]

1 голос
/ 13 января 2012

Прекратить использование Containable.Это действительно генерирует слишком много запросов.Используйте joins синтаксис.Взгляните на этот вопрос и на кулинарную книгу статья .

ОБНОВЛЕНИЕ

Присоединение к столам

В SQL вы можете комбинировать связанные таблицы, используя оператор JOIN.Это позволяет выполнять сложный поиск по нескольким таблицам (т. Е. Искать сообщения с несколькими тегами).

В CakePHP некоторые ассоциации (belongsTo и hasOne) выполняют автоматическое объединение для извлечения данных, поэтому вы можете выполнить командузапросы для извлечения моделей на основе данных в связанной.

Но это не относится к ассоциациям hasMany и hasAndBelongsToMany.Здесь на помощь приходят форсированные соединения.Вам нужно только определить необходимые объединения, чтобы объединить таблицы и получить желаемые результаты для вашего запроса.

Помните, что для этого нужно установить рекурсию на -1.Т.е.: $this->Channel->recursive = -1;

Для принудительного объединения таблиц необходимо использовать «современный» синтаксис для Model::find(), добавив ключ ‘joins’ к массиву $options.Например:

$options['joins'] = array(
    array('table' => 'channels',
        'alias' => 'Channel',
        'type' => 'LEFT',
        'conditions' => array(
            'Channel.id = Item.channel_id',
        )
    )
);

$Item->find('all', $options);

Обратите внимание, что массивы ‘join’ не имеют ключа.

В приведенном выше примере модель с именем Item остается присоединенной к таблице каналов.Вы можете создать псевдоним таблицы с именем модели, чтобы полученные данные соответствовали структуре данных CakePHP.

Ключами, определяющими объединение, являются следующие:

  • table:Таблица для объединения.
  • alias: псевдоним таблицы.Лучшая ставка - название модели, связанной с таблицей.
  • type: тип объединения: внутреннее, левое или правое.
  • conditions: условия выполненияобъединение.

С помощью объединений вы можете добавлять условия на основе связанных полей модели:

$options['joins'] = array(
    array('table' => 'channels',
        'alias' => 'Channel',
        'type' => 'LEFT',
        'conditions' => array(
            'Channel.id = Item.channel_id',
        )
    )
);

$options['conditions'] = array(
    'Channel.private' => 1
);

$privateItems = $Item->find('all', $options);

В hasBelongsToMany можно выполнить несколько объединений, если необходимо:

ПредположимКнига hasAndBelongsToMany Тег ассоциации.Это отношение использует таблицу books_tags в качестве таблицы объединения, поэтому вам необходимо присоединить таблицу books к таблице books_tags, и это с таблицей тегов:

$options['joins'] = array(
    array('table' => 'books_tags',
        'alias' => 'BooksTag',
        'type' => 'inner',
        'conditions' => array(
            'Books.id = BooksTag.books_id'
        )
    ),
    array('table' => 'tags',
        'alias' => 'Tag',
        'type' => 'inner',
        'conditions' => array(
            'BooksTag.tag_id = Tag.id'
        )
    )
);

$options['conditions'] = array(
    'Tag.tag' => 'Novel'
);

$books = $Book->find('all', $options);

Использование объединений с поведением Containable может привести к некоторым ошибкам SQL(дубликаты таблиц), поэтому вам нужно использовать метод соединений в качестве альтернативы для Containable, если ваша главная цель - выполнять поиск на основе связанных данных.Containable лучше всего подходит для ограничения количества связанных данных, передаваемых оператором find.

0 голосов
/ 24 августа 2015

Первое, что нужно сделать, это в вашей родительской модели, установить рекурсивную привязку равной -1, вот так $this->recursive = -1; при этом торт загружает только те модели, которые мы установили в array (). Спасибо

0 голосов
/ 13 января 2012

Я решал ту же проблему, а затем обнаружил, что в какой-то модели я установил обратный вызов afterFind и в другой запрос SQL, что приводило к путанице в содержании поведения.Поэтому я предлагаю переписать внутренние запросы в обратных вызовах на простой SQL, помог мне и решил содержать в себе цепочки.

0 голосов
/ 15 ноября 2011

Я никогда не использовал синтаксис точки.Вы пробовали

"contain" => array('Participant'=>array('User'))

?

Для меня это выглядит так, как будто сдерживаемое поведение как-то не привязано.Как вы прикрепили / активировали свое поведение?

0 голосов
/ 15 ноября 2011

Я использую ContainableBehaviour в одном из моих проектов, и он работает точно так же, как и ожидалось, даже с большим количеством отношений. Я считаю, что это не работает правильно в вашем случае из-за следующих отношений:

  • Участник имеет много сообщений
  • Участник имеет много комментариев
  • Участник принадлежит к Почте
  • Участник принадлежит комментарию

Почему участник принадлежит посту? Почему участник принадлежит комментарию?

Содержит ли работа должным образом, когда вы избавляетесь от этих отношений?

Если вам нужны все эти отношения и вы правильно настроили свои отношения HABTM . Проверьте этот пост , который объясняет, как использовать ContainableBehaviour с HABTM-отношением.

0 голосов
/ 15 ноября 2011

Может ли это помочь:

$this->loadModel('Participant');
                $this->Participant->bindModel(array(
                    'belongsTo' => array(
                        'User' => array(
                            'className' => 'Wish',
                            'foreignKey' => 'user_id'
                        )
                    )
                ),0);
$this->set(
  "posts", 
  $this->Post->find(
    "all", array(
      "conditions" => array("Post.wall_id" => $wall["Wall"]["id"]), 
      "contain" => false
    )
  )
);
0 голосов
/ 15 ноября 2011

Теоретически, это должно работать нормально. Убедитесь, что вы установили

var $actsAs = array('Containable');

на каждой модели, на которую вы ссылаетесь.

Редактировать: Если присмотреться, возможно, нет. Попробуйте следующее ...

$this->set(
  "posts", 
  $this->Post->find(
    "all", array(
      "conditions" => array("Post.wall_id" => $wall["Wall"]["id"]), 
      "contain" => array("Participant" => array("User"))
    )
  )
);
...