В този вид ситуация съм склонен да не използвам асоциациите на Cake или Containable и сам изработвам съединенията:
$events = $this->Event->find('all', array(
'joins'=>array(
array(
'table' => $this->Schedule->table,
'alias' => 'Schedule',
'type' => 'INNER',
'foreignKey' => false,
'conditions'=> array(
'Schedule.event_id = Event.id',
),
),
array(
'table' => $this->Date->table,
'alias' => 'Date',
'type' => 'INNER',
'foreignKey' => false,
'conditions'=> array(
'Date.schedule_id = Schedule.id',
),
),
),
'conditions'=>array(
'Date.start >=' => $start_date,
'Date.start <=' => $end_date,
),
'order'=>'Event.created DESC',
'limit'=>5
));
Малко е едро, но води до точната заявка, която искам.
АКТУАЛИЗИРАНЕ
Нека разбием вашия код на части и да видим къде можем да го подобрим. Първата част е подготовката за find
. Пренаписах кода ви, опитвайки се да го направя по-кратък и ето какво измислих:
// Default options go here
$defaultOpts = array(
'start' => date('Y-m-d') . ' 00:00:00',
'end' => date('Y-m-d') . ' 23:59:59',
'limit' => 10
)
// Use default options if nothing is passed, otherwise merge passed options with defaults
$opts = is_array($opts) ? array_merge($defaultOpts, $opts) : $defaultOpts;
// Initialize array to hold query conditions
$conditions = array();
//date conditions
$conditions[] = array(
"Date.start >=" => $qOpts['start'],
"Date.start <=" => $qOpts['end'],
));
//cities conditions
if(isset($opts['cities']) && is_array($opts['cities'])) {
$conditions['OR'] = array();
$conditions['OR'][] = array('Venue.city_id'=>$opts['cities']);
$conditions['OR'][] = array('Restaurant.city_id'=>$opts['cities']);
}
//event types conditions
//$opts['event_types'] = array('1');
if(isset($opts['event_types']) && is_array($opts['event_types'])) {
$conditions[] = 'EventTypesEvents.event_type_id' => $opts['event_types']
}
//event sub types conditions
if(isset($opts['event_sub_types']) && is_array($opts['event_sub_types'])) {
$conditions[] = 'EventSubTypesEvents.event_sub_type_id' => $opts['event_sub_types']
}
//event sub sub types conditions
if(isset($opts['event_sub_types']) && is_array($opts['event_sub_sub_types'])) {
$conditions[] = 'EventSubSubTypesEvents.event_sub_sub_type_id' => $opts['event_sub_sub_types']
}
Забележете, че елиминих повечето OR. Това е така, защото можете да предадете масив като стойност в conditions
, а Cake ще го направи IN(...)
израз в SQL заявката. Например:'Model.field' => array(1,2,3)
генерира 'Model.field IN (1,2,3)'
. Това работи точно като ИЛИ, но изисква по-малко код. Така че кодовият блок по-горе прави точно същото като вашият код, но е по-кратък.
Сега идва сложната част, find
себе си.
Обикновено бих препоръчал принудителното свързване самостоятелно, без Containable и с 'recursive'=>false
. Вярвам в това обикновено е най-добрият начин за справяне със сложни находки. С Associations и Containable, Cake изпълнява няколко SQL заявки към базата данни (една заявка на модел/таблица), което обикновено е неефективно. Също така, Containable не винаги връща очакваните резултати (както сте забелязали, когато сте го изпробвали).
Но тъй като във вашия случай има четири сложни асоциации, може би смесеният подход ще бъде идеалното решение - в противен случай би било твърде сложно да се изчистят дублираните данни. (4-те сложни асоциации са:Event hasMany Dates [чрез Event hasMany Schedule, Schedule hasMany Date], Event HABTM EventType, Event HABTM EventSubType, Event HABTM EventSubSubType). Така че бихме могли да оставим Cake да обработва извличането на данни за EventType, EventSubType и EventSubSubType, избягвайки твърде много дублирани.
Ето какво предлагам:използвайте обединения за цялото необходимо филтриране, но не включвайте типовете дата и [Sub[Sub]] в полетата. Поради асоциациите на модела, които имате, Cake автоматично ще изпълнява допълнителни заявки към DB, за да извлече тези битове данни. Не е необходим контейнер.
Кодът:
// We already fetch the data from these 2 models through
// joins + fields, so we can unbind them for the next find,
// avoiding extra unnecessary queries.
$this->unbindModel(array('belongsTo'=>array('Restaurant', 'Venue'));
$data = $this->find('all', array(
// The other fields required will be added by Cake later
'fields' => "
Event.*,
Restaurant.id, Restaurant.name, Restaurant.slug, Restaurant.address, Restaurant.GPS_Lon, Restaurant.GPS_Lat, Restaurant.city_id,
Venue.id, Venue.name, Venue.slug, Venue.address, Venue.GPS_Lon, Venue.GPS_Lat, Venue.city_id,
City.id, City.name, City.url_name
",
'joins' => array(
array(
'table' => $this->Schedule->table,
'alias' => 'Schedule',
'type' => 'INNER',
'foreignKey' => false,
'conditions' => 'Schedule.event_id = Event.id',
),
array(
'table' => $this->Schedule->Date->table,
'alias' => 'Date',
'type' => 'INNER',
'foreignKey' => false,
'conditions' => 'Date.schedule_id = Schedule.id',
),
array(
'table' => $this->EventTypesEvent->table,
'alias' => 'EventTypesEvents',
'type' => 'INNER',
'foreignKey' => false,
'conditions' => 'EventTypesEvents.event_id = Event.id',
),
array(
'table' => $this->EventSubSubTypesEvent->table,
'alias' => 'EventSubSubTypesEvents',
'type' => 'INNER',
'foreignKey' => false,
'conditions' => 'EventSubSubTypesEvents.event_id = Event.id',
),
array(
'table' => $this->Restaurant->table,
'alias' => 'Restaurant',
'type' => 'LEFT',
'foreignKey' => false,
'conditions' => 'Event.restaurant_id = Restaurant.id',
),
array(
'table' => $this->City->table,
'alias' => 'RestaurantCity',
'type' => 'LEFT',
'foreignKey' => false,
'conditions' => 'Restaurant.city_id = city.id',
),
array(
'table' => $this->Venue->table,
'alias' => 'Venue',
'type' => 'LEFT',
'foreignKey' => false,
'conditions' => 'Event.venue_id = Venue.id',
),
array(
'table' => $this->City->table,
'alias' => 'VenueCity',
'type' => 'LEFT',
'foreignKey' => false,
'conditions' => 'Venue.city_id = city.id',
),
),
'conditions' => $conditions,
'limit' => $opts['limit'],
'recursive' => 2
));
Премахнахме contains
, и някои от допълнителните заявки, които Cake изпълняваше заради него. Повечето съединения са от тип INNER
. Това означава, че поне един запис трябва да съществува и в двете таблици, участващи в присъединяването, или ще получите по-малко резултати, отколкото бихте очаквали. Предполагам, че всяко събитие се провежда в ресторантИЛИ място, но не и двете, затова използвах LEFT
за тези таблици (и градове). Ако някои от полетата, използвани в обединяването, са незадължителни, трябва да използвате LEFT
вместо INNER
на свързаните съединения.
Ако използвахме 'recursive'=>false
тук пак ще получим правилните събития и няма да се повтарят данни, но ще липсват дати и [Sub[Sub]]типове. С 2-те нива на рекурсия Cake автоматично ще преглежда върнатите събития и за всяко събитие ще изпълнява необходимите заявки за извличане на свързаните данни за модела.
Това е почти това, което правихте, но без Containable и с няколко допълнителни настройки. Знам, че все още е дълъг, грозен и скучен код, но в края на краищата има замесени 13 таблици на база данни...
Всичко това е непроверен код, но вярвам, че трябва да работи.