V této situaci mám tendenci nepoužívat Cakeovy asociace nebo Containable a vytvářet spojení sám:
$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
));
Je to trochu robustní, ale výsledkem je přesně ten dotaz, který chci.
AKTUALIZACE
Rozdělme váš kód na části a uvidíme, kde bychom jej mohli vylepšit. První částí je příprava na find
. Přepsal jsem váš kód, abych ho zkrátil, a přišel jsem na toto:
// 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']
}
Všimněte si, že jsem odstranil většinu OR. Je to proto, že pole můžete předat jako hodnotu v conditions
a Cake z něj udělá IN(...)
příkazu v dotazu SQL. Například:'Model.field' => array(1,2,3)
vygeneruje 'Model.field IN (1,2,3)'
. Funguje to stejně jako OR, ale vyžaduje méně kódu. Blok kódu výše tedy dělá přesně to samé, co dělal váš kód, ale je kratší.
Nyní přichází složitá část, find
sám.
Obvykle bych doporučil vynucená připojení samotná, bez obsahu a s 'recursive'=>false
. Věřím tomu obvykle je nejlepší způsob, jak se vypořádat se složitými nálezy. Pomocí Asociace a Containable spouští Cake několik SQL dotazů na databázi (jeden dotaz na model/tabulku), což bývá neefektivní. Containable také ne vždy vrátí očekávané výsledky (jak jste si všimli, když jste to zkusili).
Ale protože ve vašem případě jsou čtyři komplexních asociací, možná bude ideálním řešením smíšený přístup – jinak by bylo příliš komplikované vyčistit duplicitní data. (Tato 4 komplexní přidružení jsou:Event hasMany Dates [přes Event hasMany Schedule, Schedule hasMany Date], Event HABTM EventType, Event HABTM EventSubType, Event HABTM EventSubSubType). Mohli bychom tedy nechat Cake, aby se staral o načítání dat EventType, EventSubType a EventSubSubType, čímž bychom se vyhnuli příliš velkému počtu duplicit.
Zde je to, co navrhuji:pro veškeré požadované filtrování použijte spojení, ale nezahrnujte do polí Datum a [Sub[Sub]]Typy. Kvůli přidružením modelů, které máte, bude Cake automaticky spouštět další dotazy proti DB, aby načetl tyto bity dat. Není potřeba žádný obsah.
Kód:
// 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
));
Odstranili jsme contains
a některé další dotazy, které Cake kvůli tomu spouštěl. Většina spojení je typu INNER
. To znamená, že na obou tabulkách zapojených do spojení musí existovat alespoň jeden záznam, jinak získáte méně výsledků, než byste očekávali. Předpokládám, že každá akce se koná v restauraci NEBO místo, ale ne obojí, proto jsem použil LEFT
pro ty tabulky (a města). Pokud jsou některá pole použitá ve spojeních volitelná, měli byste použít LEFT
místo INNER
na souvisejících spojeních.
Pokud bychom použili 'recursive'=>false
zde bychom stále získali správné události a žádné opakování dat, ale data a [Sub[Sub]]typy by chyběly. Díky 2 úrovním rekurze bude Cake automaticky procházet vrácené události a pro každou událost spustí nezbytné dotazy k načtení dat souvisejících s modelem.
To je téměř to, co jste dělali, ale bez obsahu a s několika dalšími vylepšeními. Vím, že je to stále dlouhý, ošklivý a nudný kus kódu, ale koneckonců je to 13 databázových tabulek...
To vše je netestovaný kód, ale věřím, že by měl fungovat.