From 68585a0b30ec425bbf133b97ea1f097f8eeb641c Mon Sep 17 00:00:00 2001 From: abijah Date: Mon, 1 Jun 2009 02:36:26 +0000 Subject: [PATCH] I've been around and around with paging. I started using the Containable behavior, which is fantastic for something like 'view', but is highly problematic for listings, especially with pagination. I discovered the Linkable behavior, and it's much more database efficient, and I actually can get it to work with pagination (except when using GROUP BY). I did have to tweak it though, to handle some of the more complex queries that I intend. This checkin includes a bunch of garbage, but given the amount of time I've spent on this crap, I'm afraid to lose anything that might later be useful information. I'll clean up on the next checkin. git-svn-id: file:///svn-source/pmgr/branches/initial_20090526/site@49 97e9348a-65ac-dc4b-aefc-98561f571b83 --- app_model.php | 61 +++++++++ controllers/contacts_controller.php | 188 +++++++++++++++++++++++++--- controllers/units_controller.php | 88 ++++++++++++- models/behaviors/linkable.php | 125 ++++++++++++++---- models/contact.php | 13 -- 5 files changed, 419 insertions(+), 56 deletions(-) diff --git a/app_model.php b/app_model.php index a4e1079..b4cc264 100644 --- a/app_model.php +++ b/app_model.php @@ -38,6 +38,8 @@ */ class AppModel extends Model { + //var $actsAs = array('Containable'); + var $actsAs = array('Linkable'); /** * Get Enum Values @@ -75,4 +77,63 @@ class AppModel extends Model { )); } //end getEnumValues + + // Overriding pagination, since the stupid count mechanism blows. + // This is also a crappy solution, especially if the query returns + // a really large number of rows. However, trying to untagle this + // without changing a bunch of core code is near impossible. + + var $paginate_rows_save; + function paginate($conditions, $fields, $order, $limit, $page = 1, $recursive = null) { +/* pr("paginate"); */ +/* pr(array_merge(compact('conditions', 'fields', 'order', 'limit', 'page', 'recursive'))); */ + if ($page > 1 && isset($limit)) + $offset = ($page - 1) * $limit; + else + $offset = 0; + return array_slice($this->paginate_rows_save, $offset, $limit); + } + function paginateCount($conditions = null, $recursive = null, $extra = null) { +/* pr("paginateCount"); */ + + if (!isset($recursive)) + $recursive = $this->recursive; + $parameters = array_merge(compact('conditions', 'recursive'), $extra); + $results = $this->find('all', $parameters); + + $this->paginate_rows_save = $results; + return count($this->paginate_rows_save); + } + +// Code as found in the controller +/* function paginate($conditions, $fields, $order, $limit, $page = 1, $recursive = null) { */ +/* pr("paginate"); */ +/* pr(array_merge(compact('conditions', 'fields', 'order', 'limit', 'page', 'recursive'))); */ + +/* $parameters = compact('conditions', 'fields', 'order', 'limit', 'page'); */ +/* if ($recursive != $this->recursive) { */ +/* $parameters['recursive'] = $recursive; */ +/* } */ + +/* $results = $this->find($type, array_merge($parameters, $extra)); */ +/* pr("paginate end"); */ +/* pr($results); */ +/* } */ + +/* function paginateCount($conditions = null, $recursive = null, $extra = null) { */ +/* pr("paginateCount"); */ +/* pr(array_merge(compact('conditions', 'recursive'), $extra)); */ + +/* $parameters = compact('conditions'); */ +/* if ($recursive != $this->recursive) { */ +/* $parameters['recursive'] = $recursive; */ +/* } */ +/* $count = $this->find('count', array_merge($parameters, $extra)); */ + +/* pr("paginateCount end: $count"); */ +/* return $count; */ +/* } */ + + + } diff --git a/controllers/contacts_controller.php b/controllers/contacts_controller.php index acf3eb0..2ef5300 100644 --- a/controllers/contacts_controller.php +++ b/controllers/contacts_controller.php @@ -89,18 +89,124 @@ class ContactsController extends AppController { */ function past() { - $this->Contact->recursive = 0; - $this->Contact->bindModel(array('hasOne' => array('ContactsLease', - 'Lease' => array( - 'foreignKey' => false, - 'conditions' => array('Lease.id = ContactsLease.lease_id') - ))), - false); - +/* $this->Contact->contain */ +/* (array(// Models */ +/* 'ContactPhone', */ +/* 'ContactEmail', */ +/* 'ContactAddress', */ +/* 'Lease' => */ +/* array('order' => 'movein_date', */ +/* 'conditions' => array('Lease.close_date IS NOT NULL', */ +/* 'ContactsLease.type !=' => 'ALTERNATE'), */ +/* // Models */ +/* 'Unit' => */ +/* array('order' => array('sort_order'), */ +/* 'fields' => array('id', 'name'), */ +/* ), */ +/* /\* 'Charge' => *\/ */ +/* /\* array('order' => array('charge_date'), *\/ */ +/* /\* // Models *\/ */ +/* /\* 'ChargeType', *\/ */ +/* /\* 'Receipt', *\/ */ +/* /\* ) *\/ */ +/* ) */ +/* ) */ +/* ); */ + +/* $contact = $this->Contact->find('all', */ +/* array('order' => 'id DESC', */ +/* 'conditions' => array('Lease.id IS NOT NULL'), */ +/* 'contain' => */ +/* array( */ +/* // Models */ +/* 'Lease' => */ +/* array('order' => 'movein_date', */ +/* 'conditions' => array('Lease.lease_date IS NOT NULL', */ +/* 'Lease.close_date IS NOT NULL', */ +/* 'ContactsLease.type !=' => 'ALTERNATE'), */ +/* ) */ +/* ) */ +/* )); */ + +/* $contacts = $this->Contact->find */ +/* ('all', array */ +/* ('contain' => array */ +/* ('Lease' => array */ +/* ('conditions' => array('lease_date >' => "2009-04-01"), */ +/* ) */ +/* ), */ +/* 'order' => 'id DESC', */ +/* 'conditions' => array('Lease.id IS NOT NULL') */ +/* )); */ + +/* $contacts = $this->Contact->find('all', */ +/* array('order' => 'id DESC', */ +/* 'contain' => 'Lease.lease_date > "2009-04-01"' */ +/* )); */ +/* pr($contacts); */ +/* $this->set('contacts', $contacts); */ + +/* $this->Contact->Behaviors->attach('Containable'); */ +/* $this->Contact->contain */ +/* (array(// Models */ +/* 'ContactPhone', */ +/* 'ContactEmail', */ +/* 'ContactAddress', */ +/* 'Lease' => */ +/* array('order' => 'movein_date', */ +/* 'conditions' => array('Lease.close_date IS NOT NULL', */ +/* 'ContactsLease.type !=' => 'ALTERNATE'), */ +/* // Models */ +/* 'Unit' => */ +/* array('order' => array('sort_order'), */ +/* 'fields' => array('id', 'name'), */ +/* ), */ +/* /\* 'Charge' => *\/ */ +/* /\* array('order' => array('charge_date'), *\/ */ +/* /\* // Models *\/ */ +/* /\* 'ChargeType', *\/ */ +/* /\* 'Receipt', *\/ */ +/* /\* ) *\/ */ +/* ) */ +/* ) */ +/* ); */ + + + $this->paginate = + array('link' => + array(// Models + 'ContactPhone' => array('fields' => array('phone'), + //'type' => 'INNER' + ), + //'ContactEmail', + //'ContactAddress', + 'Lease' => + array('fields' => array(), + 'type' => 'LEFT', + // Models + 'Unit' => array('fields' => array('name'), + //'type' => 'INNER', + ), + ) + ), + 'order' => 'Contact.last_name, Contact.first_name', + 'conditions' => array(//'ContactsLease.type !=' => 'ALTERNATE', + //'Lease.id IS NOT NULL', + //'Lease.lease_date IS NOT NULL', + //'Lease.close_date IS NOT NULL' + ), + 'group' => 'Contact.id', + 'fields' => array('id', 'first_name', 'last_name', 'company_name', 'comment'), + ); + + $title = 'Past Tenants'; $this->set('title', $title); $this->set('heading', $title); - $this->set('contacts', $this->paginate(array('Lease.close_date IS NOT NULL', - 'ContactsLease.type != "ALTERNATE"'))); + $this->set('contacts', $this->paginate()); + //$this->set('contacts', $this->Contact->find('all', array('order' => 'id DESC'))); + //$this->set('contacts', $this->Contact->find('all')); +/* $this->set('contacts', $this->Contact->find('all')); */ +/* $this->set('contacts', $this->Contact->find('all')); */ $this->render('index'); } @@ -131,15 +237,61 @@ class ContactsController extends AppController { function view($id = null) { if (!$id) { $this->Session->setFlash(__('Invalid Item.', true)); - $this->redirect(array('action'=>'')); + $this->redirect(array('action'=>'index')); } - $this->Contact->recursive = 4; - $this->Contact->Lease->LeaseType->unbindModel(array('hasMany' => array('Lease'))); - $this->Contact->Lease->Charge->unbindModel(array('belongsTo' => array('Lease'))); - $this->Contact->Lease->Charge->ChargeType->unbindModel(array('hasMany' => array('Charge'))); - $this->Contact->Lease->Charge->Receipt->unbindModel(array('hasMany' => array('ChargesReceipt'))); - $contact = $this->Contact->read(null, $id); + //pr($this->Contact); + //pr($this->Contact->ContactPhone); + + $this->Contact->Behaviors->attach('Containable'); + $this->Contact->contain + (array(// Models + 'ContactPhone', + 'ContactEmail', + 'ContactAddress', + 'Lease' => + array('order' => 'movein_date', + 'conditions' => array('Lease.lease_date IS NOT NULL', + 'ContactsLease.type !=' => 'ALTERNATE'), + // Models + 'Unit' => + array('order' => array('sort_order'), + 'fields' => array('id', 'name'), + ), + 'Charge' => + array('order' => array('charge_date'), + // Models + 'ChargeType', + 'Receipt', + ) + ) + ) + ); + $contact = $this->Contact->read(null, $id); + +/* $contact = $this->Contact->find */ +/* ('first', */ +/* array('link' => array */ +/* (// Models */ +/* 'ContactPhone', */ +/* 'ContactEmail', */ +/* 'ContactAddress', */ +/* 'Lease' => */ +/* array(// Models */ +/* 'Unit' => array('fields' => array('id', 'name')), */ +/* 'Charge' => array(// Models */ +/* 'ChargeType', */ +/* 'Receipt', */ +/* ) */ +/* ) */ +/* ), */ +/* 'order' => 'Lease.movein_date, Unit.sort_order, Charge.charge_date', */ +/* 'conditions' => array('Lease.lease_date IS NOT NULL', */ +/* 'ContactsLease.type !=' => 'ALTERNATE'), */ +/* )); */ + +/* pr($contact); */ + $title = $contact['Contact']['display_name']; $outstanding_deposit = 0; @@ -163,7 +315,7 @@ class ContactsController extends AppController { $this->sidemenu_links[] = array('name' => 'Move-Out', 'url' => array('controller' => 'units', 'action' => 'move-out')); - $this->set(compact('contact', 'title', 'mytstval', + $this->set(compact('contact', 'title', 'outstanding_balance', 'outstanding_deposit')); } diff --git a/controllers/units_controller.php b/controllers/units_controller.php index 43a15ca..60a6b20 100644 --- a/controllers/units_controller.php +++ b/controllers/units_controller.php @@ -34,7 +34,93 @@ class UnitsController extends AppController { */ function index() { - $this->all(); + //$this->Unit->recursive = 0; + + // $units = $this->Unit->find + // ('all', + $this->paginate = + array( + 'link' => array + ('UnitSize' //=> array('fields' => array('name')), + //=> array('fields' => true), + => array('fields' => array('name')), + //=> array('fields' => array()), + //=> array(), + + //'Lease', + ), + 'conditions' => array('UnitSize.name =' => "10x30", + 'UnitSize.id IS NOT NULL'), + //'fields' => true + 'fields' => array('id', 'name', 'status', 'comment') +// ) + ); + + $units = $this->paginate(); + + +/* Array */ +/* ( */ +/* [joins] => Array */ +/* ( */ +/* [0] => Array */ +/* ( */ +/* [type] => LEFT */ +/* [alias] => UnitSize */ +/* [conditions] => `UnitSize`.`id` = `Unit`.`unit_size_id` */ +/* [table] => `pmgr_unit_sizes` */ +/* ) */ + +/* ) */ + +/* [conditions] => Array */ +/* ( */ +/* [UnitSize.name =] => 10x30 */ +/* [0] => UnitSize.id IS NOT NULL */ +/* ) */ + +/* [fields] => Array */ +/* ( */ +/* [0] => id */ +/* [1] => name */ +/* [2] => status */ +/* [3] => comment */ +/* [4] => `UnitSize`.`name` */ +/* ) */ + +/* [limit] => */ +/* [offset] => */ +/* [order] => Array */ +/* ( */ +/* [0] => */ +/* ) */ + +/* [page] => 1 */ +/* [group] => */ +/* [callbacks] => 1 */ +/* [link] => Array */ +/* ( */ +/* [UnitSize] => Array */ +/* ( */ +/* [fields] => Array */ +/* ( */ +/* [0] => name */ +/* ) */ + +/* ) */ + +/* ) */ + +/* [recursive] => -1 */ +/* ) */ + + + pr($units); + $title = 'Index Units'; + $this->set('title', $title); $this->set('heading', $title); + $this->set('units', $units); + + //$this->all(); } diff --git a/models/behaviors/linkable.php b/models/behaviors/linkable.php index 428fc9b..8b6c306 100644 --- a/models/behaviors/linkable.php +++ b/models/behaviors/linkable.php @@ -34,7 +34,7 @@ class LinkableBehavior extends ModelBehavior { protected $_key = 'link'; protected $_options = array( - 'type' => true, 'table' => true, 'alias' => true, + 'type' => true, 'table' => true, 'alias' => true, 'joins' => true, 'conditions' => true, 'fields' => true, 'reference' => true, 'class' => true, 'defaults' => true ); @@ -42,7 +42,7 @@ class LinkableBehavior extends ModelBehavior { protected $_defaults = array('type' => 'LEFT'); public function beforeFind(&$Model, $query) { - //pr("Linkable::beforeFind() begin"); pr($query); +/* pr("Linkable::beforeFind() begin"); pr($query); */ if (isset($query[$this->_key])) { $optionsDefaults = $this->_defaults + array('reference' => $Model->alias, $this->_key => array()); $optionsKeys = $this->_options + array($this->_key => true); @@ -79,8 +79,12 @@ class LinkableBehavior extends ModelBehavior { } $_Model =& ClassRegistry::init($options['class']); // the incoming model to be linked in query $Reference =& ClassRegistry::init($options['reference']); // the already in query model that links to $_Model +/* pr("_Model: $options[class] : $_Model->name ($_Model->alias)"); */ +/* pr("Reference: $options[reference] : $Reference->name ($Reference->alias)"); */ $db =& $_Model->getDataSource(); $associations = $_Model->getAssociated(); +/* pr("Assocations of $_Model->name"); */ +/* pr($associations); */ if (isset($associations[$Reference->alias])) { $type = $associations[$Reference->alias]; $association = $_Model->{$type}[$Reference->alias]; @@ -97,32 +101,94 @@ class LinkableBehavior extends ModelBehavior { $referenceKey = $Reference->escapeField($Reference->primaryKey); $options['conditions'] = "{$referenceKey} = {$modelKey}"; } elseif ($type === 'hasAndBelongsToMany') { - if (isset($association['with'])) { +/* pr("Association"); */ +/* pr($association); */ + if (isset($association['with'])) $Link =& $_Model->{$association['with']}; - if (isset($Link->belongsTo[$_Model->alias])) { - $modelLink = $Link->escapeField($Link->belongsTo[$_Model->alias]['foreignKey']); - } - if (isset($Link->belongsTo[$Reference->alias])) { - $referenceLink = $Link->escapeField($Link->belongsTo[$Reference->alias]['foreignKey']); - } - } else { + else $Link =& $_Model->{Inflector::classify($association['joinTable'])}; - } - if (empty($modelLink)) { + + // Get the foreign key fields (for the link table) directly from + // the defined model associations, if they exists. This is the + // users direct specification, and therefore definitive if present. + $modelLink = $Link->escapeField($association['foreignKey']); + $referenceLink = $Link->escapeField($association['associationForeignKey']); + + // If we haven't figured out the foreign keys, see if there is a + // model for the link table, and if it has the appropriate + // associations with the two tables we're trying to join. + if (empty($modelLink) && isset($Link->belongsTo[$_Model->alias])) + $modelLink = $Link->escapeField($Link->belongsTo[$_Model->alias]['foreignKey']); + if (empty($referenceLink) && isset($Link->belongsTo[$Reference->alias])) + $referenceLink = $Link->escapeField($Link->belongsTo[$Reference->alias]['foreignKey']); + + // We're running quite thin here. None of the models spell + // out the appropriate linkages. We'll have to SWAG it. + if (empty($modelLink)) $modelLink = $Link->escapeField(Inflector::underscore($_Model->alias) . '_id'); - } - if (empty($referenceLink)) { + if (empty($referenceLink)) $referenceLink = $Link->escapeField(Inflector::underscore($Reference->alias) . '_id'); - } + + // Get the primary key from the tables we're joining. $referenceKey = $Reference->escapeField(); - $query['joins'][] = array( - 'alias' => $Link->alias, - 'table' => $Link->getDataSource()->fullTableName($Link), - 'conditions' => "{$referenceLink} = {$referenceKey}", - 'type' => 'LEFT' - ); $modelKey = $_Model->escapeField(); - $options['conditions'] = "{$modelLink} = {$modelKey}"; + +/* pr("bf pre options:"); */ +/* pr($options); */ + +/* [joins] => Array */ +/* ( */ +/* [0] => Array */ +/* ( */ +/* [type] => LEFT */ +/* [table] => `pmgr_contacts_leases` */ +/* [alias] => ContactsLease */ +/* [joins] => Array */ +/* ( */ +/* [0] => Array */ +/* ( */ +/* [type] => INNER */ +/* [table] => `pmgr_leases` */ +/* [alias] => Lease */ +/* [conditions] => `Lease`.`id` = `ContactsLease`.`lease_id` */ +/* ) */ +/* ) */ +/* [conditions] => `ContactsLease`.`contact_id` = `Contact`.`id` */ +/* ) */ +/* ) */ +/* LEFT JOIN (`pmgr_contacts_leases` AS `ContactsLease` */ +/* INNER JOIN `pmgr_leases` AS `Lease` */ +/* ON (`Lease`.`id` = `ContactsLease`.`lease_id`) */ +/* ) ON (`ContactsLease`.`contact_id` = `Contact`.`id`) */ + + + // Join the linkage table to our model. We'll use an inner join, + // as the whole purpose of the linkage table is to make this + // connection. As we are embedding this join, the INNER will not + // cause any problem with the overall query, should the user not + // be concerned with whether or not the join has any results. + // They control that with the 'type' parameter which will be at + // the top level join. + $options['joins'][] = array('type' => 'INNER', + 'alias' => $options['alias'], + 'conditions' => "{$modelKey} = {$modelLink}", + 'table' => $db->fullTableName($_Model, true)); + + // The user may have specified conditions directly in the model + // for this join. Make sure to adhere to those conditions. + if (isset($association['conditions']) && is_array($association['conditions'])) + $options['conditions'] = $association['conditions']; + elseif (!empty($association['conditions'])) + $options['conditions'] = array($association['conditions']); + + // Now for the top level join. This will be added into the list + // of joins down below, outside of the HABTM specific code. + $options['alias'] = $Link->alias; + $options['table'] = $Link->getDataSource()->fullTableName($Link); + $options['conditions'][] = "{$referenceLink} = {$referenceKey}"; + +/* pr("bf post options:"); */ +/* pr($options); */ } else { $referenceKey = $Reference->escapeField($association['foreignKey']); $modelKey = $_Model->escapeField($_Model->primaryKey); @@ -148,13 +214,24 @@ class LinkableBehavior extends ModelBehavior { if (!empty($options[$this->_key])) { $iterators[] = $options[$this->_key] + array('defaults' => array_merge($defaults, array('reference' => $options['class']))); } - $query['joins'][] = array_intersect_key($options, array('type' => true, 'alias' => true, 'table' => true, 'conditions' => true)); +/* pr("bf pre query:"); */ +/* pr($query); */ +/* $aik = array('type' => true, 'alias' => true, 'table' => true, 'joins' => true, 'conditions' => true); */ +/* pr("aik"); */ +/* pr($aik); */ +/* $ojoin = array_intersect_key($options, $aik); */ +/* pr("ojoin"); */ +/* pr($ojoin); */ +/* $query['joins'][] = $ojoin; */ +/* pr("bf post query:"); */ +/* pr($query); */ + $query['joins'][] = array_intersect_key($options, array('type' => true, 'alias' => true, 'table' => true, 'joins' => true, 'conditions' => true)); } ++$cont; $notDone = isset($iterators[$cont]); } while ($notDone); } - //pr("Linkable::beforeFind() end"); pr($query); +/* pr("Linkable::beforeFind() end"); pr($query); */ return $query; } } \ No newline at end of file diff --git a/models/contact.php b/models/contact.php index 96d12bb..5aab7f3 100644 --- a/models/contact.php +++ b/models/contact.php @@ -72,18 +72,5 @@ class Contact extends AppModel { ), ); - // Overriding pagination, since the controller using HABTM joins that - // result in duplicate rows. This may not be the best way to handle - // things (especially if the query return a really large number of rows, - // but it does seem to work. - function paginate($conditions, $fields, $order, $limit, $page = 1, $recursive = null) { - $conditions[] ="1=1 GROUP BY Contact.id"; - return $this->findAll($conditions, $fields, $order, $limit, $page, $recursive); - } - function paginateCount($conditions = null, $recursive = 0) { - $rows = $this->paginate($conditions, null, null, null, null, $recursive); - return count($rows); - } - } ?> \ No newline at end of file