I den här typen av situation brukar jag inte använda Cakes associationer, eller Containable, och skapa fogarna själv:
$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
));
Det är lite tjockt, men resulterar i exakt den fråga jag vill ha.
UPPDATERA
Låt oss dela upp din kod i delar och se var vi kan förbättra den. Den första delen är förberedelserna för find
. Jag har skrivit om din kod för att försöka göra den kortare, och det här är vad jag kom fram till:
// 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']
}
Lägg märke till att jag eliminerade de flesta yttersta randområdena. Det beror på att du kan skicka en array som ett värde i conditions
, och Cake kommer att göra den till en IN(...)
uttalande i SQL-frågan. Till exempel:'Model.field' => array(1,2,3)
genererar 'Model.field IN (1,2,3)'
. Detta fungerar precis som OR, men kräver mindre kod. Så kodblocket ovan gör exakt samma som din kod gjorde, men det är kortare.
Nu kommer den komplexa delen, find
sig själv.
Vanligtvis skulle jag rekommendera de forcerade anslutningarna ensamma, utan Containable och med 'recursive'=>false
. Jag tror det vanligtvis är det bästa sättet att hantera komplexa fynd. Med Associations and Containable kör Cake flera SQL-frågor mot databasen (en fråga per modell/tabell), vilket tenderar att vara ineffektivt. Dessutom ger Containable inte alltid de förväntade resultaten (som du märkte när du provade det).
Men eftersom det i ditt fall är fyra komplexa föreningar inblandade, kanske ett blandat tillvägagångssätt är den idealiska lösningen - annars skulle det vara för komplicerat att rensa upp dubblettdata. (De fyra komplexa associationerna är:Event hasMany Dates [genom Event hasMany Schedule, Schedule hasMany Date], Event HABTM EventType, Event HABTM EventSubType, Event HABTM EventSubSubType). Så vi kunde låta Cake hantera datahämtning av EventType, EventSubType och EventSubSubType och undvika för många dubbletter.
Så här är vad jag föreslår:använd joins för all nödvändig filtrering, men inkludera inte datum och [Sub[Sub]]typer i fälten. På grund av de modellassociationer du har kommer Cake automatiskt att köra extra frågor mot databasen för att hämta dessa databitar. Ingen behållare behövs.
Koden:
// 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
));
Vi har tagit bort contains
, och några av de extra frågorna som Cake körde på grund av det. De flesta anslutningar är av typen INNER
. Det betyder att minst en post måste finnas på båda tabellerna som är involverade i anslutningen, annars får du färre resultat än du förväntar dig. Jag antar att varje evenemang äger rum på en restaurang ELLER en plats, men inte båda, det var därför jag använde LEFT
för dessa bord (och städer). Om några av fälten som används i kopplingarna är valfria bör du använda LEFT
istället för INNER
på relaterade kopplingar.
Om vi använde 'recursive'=>false
här skulle vi fortfarande få rätt händelser och ingen dataupprepning, men datum och [Sub[Sub]]typer skulle saknas. Med de två rekursionsnivåerna kommer Cake automatiskt att gå igenom de returnerade händelserna, och för varje händelse kommer den att köra de nödvändiga frågorna för att hämta tillhörande modelldata.
Detta är nästan vad du gjorde, men utan Containable, och med några extra justeringar. Jag vet att det fortfarande är en lång, ful och tråkig kod, men trots allt är det 13 databastabeller inblandade...
Allt detta är opestad kod, men jag tror att det borde fungera.