Modified Linkable to be recursive, which is needed to support certain queries that have models joined based on prior joined models. The old way went breadth first, which prevented such a join from working

git-svn-id: file:///svn-source/pmgr/branches/yafr_20090716/site@355 97e9348a-65ac-dc4b-aefc-98561f571b83
This commit is contained in:
abijah
2009-07-20 20:22:06 +00:00
parent 7181bc7f9b
commit bfbf119aca

View File

@@ -130,49 +130,52 @@ class LinkableBehavior extends ModelBehavior {
} }
public function beforeFind(&$Model, $query) { public function beforeFind(&$Model, $query) {
return $this->buildQuery($Model, $query);
}
function buildQuery(&$Model, $query) {
$this->pr(10,
array('function' => 'Linkable::buildQuery',
'args' => array('Model->alias' => '$Model->alias') + compact('query'),
));
if (!isset($query[$this->_key])) if (!isset($query[$this->_key]))
return $query; return $query;
$optionsDefaults = $this->_defaults + array('reference' =>
array('class' => $Model->alias,
'alias' => $Model->alias),
$this->_key => array());
$optionsKeys = $this->_options + array($this->_key => true);
if (!isset($query['fields']) || $query['fields'] === true) { if (!isset($query['fields']) || $query['fields'] === true) {
//$query['fields'] = array_keys($Model->_schema);
$query['fields'] = $Model->getDataSource()->fields($Model); $query['fields'] = $Model->getDataSource()->fields($Model);
} elseif (!is_array($query['fields'])) { } elseif (!is_array($query['fields'])) {
$query['fields'] = array($query['fields']); $query['fields'] = array($query['fields']);
} }
$query = am(array('joins' => array()), $query, array('recursive' => -1)); $query = am(array('joins' => array()), $query, array('recursive' => -1));
$iterators[] = $query[$this->_key];
$cont = 0; $reference = array('class' => $Model->alias,
do { 'alias' => $Model->alias,
$iterator = $iterators[$cont]; );
$defaults = $optionsDefaults;
if (isset($iterator['defaults'])) { $result = array_diff_key($query, array($this->_key => 1));
$defaults = array_merge($defaults, $iterator['defaults']); $this->buildQuery($Model, $Model->alias, $Model->alias, $query[$this->_key], $result);
unset($iterator['defaults']); return $result;
} }
$iterations = Set::normalize($iterator);
$this->pr(25, function buildQuery(&$Reference, $referenceClass, $referenceAlias, $links, &$result) {
array('checkpoint' => 'Iterations', if (empty($links))
compact('cont', 'iterator', 'iterations'), return;
$this->pr(10,
array('begin' => 'Linkable::buildQuery',
'args' => compact('referenceClass', 'referenceAlias', 'links'),
)); ));
foreach ($iterations as $alias => $options) {
//$defaults = $this->_defaults;// + array($this->_key => array());
//$optionsKeys = $this->_options + array($this->_key => true);
$links = Set::normalize($links);
$this->pr(24,
array('checkpoint' => 'Normalized links',
compact('links'),
));
foreach ($links as $alias => $options) {
if (is_null($options)) { if (is_null($options)) {
$options = array(); $options = array();
} }
$options = am($defaults, compact('alias'), $options); //$options = array_intersect_key($options, $optionsKeys);
//$options = am($this->_defaults, compact('alias'), $options);
$options += compact('alias');
$options += $this->_defaults;
if (empty($options['alias'])) { if (empty($options['alias'])) {
throw new InvalidArgumentException(sprintf('%s::%s must receive aliased links', get_class($this), __FUNCTION__)); throw new InvalidArgumentException(sprintf('%s::%s must receive aliased links', get_class($this), __FUNCTION__));
} }
@@ -181,31 +184,28 @@ class LinkableBehavior extends ModelBehavior {
$options['class'] = $alias; $options['class'] = $alias;
if (!isset($options['conditions'])) if (!isset($options['conditions']))
$options['conditions'] = array(); $options['conditions'] = null;
elseif (!is_array($options['conditions'])) elseif (!is_array($options['conditions']))
$options['conditions'] = array($options['conditions']); $options['conditions'] = array($options['conditions']);
$this->pr(20, $this->pr(20,
array('checkpoint' => 'Begin Model Work', array('checkpoint' => 'Begin Model Work',
compact('alias', 'options'), compact('referenceAlias', 'alias', 'options'),
)); ));
$modelClass = $options['class']; $modelClass = $options['class'];
$modelAlias = $options['alias']; $modelAlias = $options['alias'];
$referenceClass = $options['reference']['class']; $Model =& ClassRegistry::init($modelClass); // the incoming model to be linked in query
$referenceAlias = $options['reference']['alias'];
$_Model =& ClassRegistry::init($modelClass); // the incoming model to be linked in query
$Reference =& ClassRegistry::init($referenceClass); // the already in query model that links to $_Model
$this->pr(12, $this->pr(12,
array('checkpoint' => 'Aliases Established', array('checkpoint' => 'Model Established',
'Model' => ($modelAlias .' : '. $modelClass .
' ('. $_Model->alias .' : '. $_Model->name .')'),
'Reference' => ($referenceAlias .' : '. $referenceClass . 'Reference' => ($referenceAlias .' : '. $referenceClass .
' ('. $Reference->alias .' : '. $Reference->name .')'), ' ('. $Reference->alias .' : '. $Reference->name .')'),
'Model' => ($modelAlias .' : '. $modelClass .
' ('. $Model->alias .' : '. $Model->name .')'),
)); ));
$db =& $_Model->getDataSource(); $db =& $Model->getDataSource();
$associatedThroughReference = 0; $associatedThroughReference = 0;
$association = null; $association = null;
@@ -213,26 +213,26 @@ class LinkableBehavior extends ModelBehavior {
// Figure out how these two models are related, creating // Figure out how these two models are related, creating
// a relationship if one doesn't otherwise already exists. // a relationship if one doesn't otherwise already exists.
if (($associations = $Reference->getAssociated()) && if (($associations = $Reference->getAssociated()) &&
isset($associations[$_Model->alias])) { isset($associations[$Model->alias])) {
$this->pr(12, array('checkpoint' => "Reference defines association to _Model")); $this->pr(12, array('checkpoint' => "Reference ($referenceClass) defines association to Model ($modelClass)"));
$associatedThroughReference = 1; $associatedThroughReference = 1;
$type = $associations[$_Model->alias]; $type = $associations[$Model->alias];
$association = $Reference->{$type}[$_Model->alias]; $association = $Reference->{$type}[$Model->alias];
} }
elseif (($associations = $_Model->getAssociated()) && elseif (($associations = $Model->getAssociated()) &&
isset($associations[$Reference->alias])) { isset($associations[$Reference->alias])) {
$this->pr(12, array('checkpoint' => "_Model defines association to Reference")); $this->pr(12, array('checkpoint' => "Model ($modelClass) defines association to Reference ($referenceClass)"));
$type = $associations[$Reference->alias]; $type = $associations[$Reference->alias];
$association = $_Model->{$type}[$Reference->alias]; $association = $Model->{$type}[$Reference->alias];
} }
else { else {
// No relationship... make our best effort to create one. // No relationship... make our best effort to create one.
$this->pr(12, array('checkpoint' => "No assocation between _Model and Reference")); $this->pr(12, array('checkpoint' => "No assocation between Reference ($referenceClass) and Model ($modelClass)"));
$type = 'belongsTo'; $type = 'belongsTo';
$_Model->bind($Reference->alias); $Model->bind($Reference->alias);
// Grab the association now, since we'll unbind in a moment. // Grab the association now, since we'll unbind in a moment.
$association = $_Model->{$type}[$Reference->alias]; $association = $Model->{$type}[$Reference->alias];
$_Model->unbindModel(array('belongsTo' => array($Reference->alias))); $Model->unbindModel(array('belongsTo' => array($Reference->alias)));
} }
// Determine which model holds the foreign key // Determine which model holds the foreign key
@@ -240,11 +240,11 @@ class LinkableBehavior extends ModelBehavior {
$primaryAlias = $referenceAlias; $primaryAlias = $referenceAlias;
$foreignAlias = $modelAlias; $foreignAlias = $modelAlias;
$primaryModel = $Reference; $primaryModel = $Reference;
$foreignModel = $_Model; $foreignModel = $Model;
} else { } else {
$primaryAlias = $modelAlias; $primaryAlias = $modelAlias;
$foreignAlias = $referenceAlias; $foreignAlias = $referenceAlias;
$primaryModel = $_Model; $primaryModel = $Model;
$foreignModel = $Reference; $foreignModel = $Reference;
} }
@@ -287,7 +287,7 @@ class LinkableBehavior extends ModelBehavior {
else else
$linkClass = Inflector::classify($association['joinTable']); $linkClass = Inflector::classify($association['joinTable']);
$Link =& $_Model->{$linkClass}; $Link =& $Model->{$linkClass};
if (isset($options['linkalias'])) if (isset($options['linkalias']))
$linkAlias = $options['linkalias']; $linkAlias = $options['linkalias'];
@@ -321,21 +321,21 @@ class LinkableBehavior extends ModelBehavior {
// If we haven't figured out the foreign keys, see if there is a // 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 // model for the link table, and if it has the appropriate
// associations with the two tables we're trying to join. // associations with the two tables we're trying to join.
if (empty($modelLink) && isset($Link->belongsTo[$_Model->alias])) if (empty($modelLink) && isset($Link->belongsTo[$Model->alias]))
$modelLink = $Link->escapeField($Link->belongsTo[$_Model->alias]['foreignKey'], $linkAlias); $modelLink = $Link->escapeField($Link->belongsTo[$Model->alias]['foreignKey'], $linkAlias);
if (empty($referenceLink) && isset($Link->belongsTo[$Reference->alias])) if (empty($referenceLink) && isset($Link->belongsTo[$Reference->alias]))
$referenceLink = $Link->escapeField($Link->belongsTo[$Reference->alias]['foreignKey'], $linkAlias); $referenceLink = $Link->escapeField($Link->belongsTo[$Reference->alias]['foreignKey'], $linkAlias);
// We're running quite thin here. None of the models spell // We're running quite thin here. None of the models spell
// out the appropriate linkages. We'll have to SWAG it. // out the appropriate linkages. We'll have to SWAG it.
if (empty($modelLink)) if (empty($modelLink))
$modelLink = $Link->escapeField(Inflector::underscore($_Model->alias) . '_id', $linkAlias); $modelLink = $Link->escapeField(Inflector::underscore($Model->alias) . '_id', $linkAlias);
if (empty($referenceLink)) if (empty($referenceLink))
$referenceLink = $Link->escapeField(Inflector::underscore($Reference->alias) . '_id', $linkAlias); $referenceLink = $Link->escapeField(Inflector::underscore($Reference->alias) . '_id', $linkAlias);
// Get the primary key from the tables we're joining. // Get the primary key from the tables we're joining.
$referenceKey = $Reference->escapeField(null, $referenceAlias); $referenceKey = $Reference->escapeField(null, $referenceAlias);
$modelKey = $_Model->escapeField(null, $modelAlias); $modelKey = $Model->escapeField(null, $modelAlias);
$this->pr(21, $this->pr(21,
array('checkpoint' => 'HABTM links/keys', array('checkpoint' => 'HABTM links/keys',
@@ -353,7 +353,7 @@ class LinkableBehavior extends ModelBehavior {
$options['joins'][] = array('type' => 'INNER', $options['joins'][] = array('type' => 'INNER',
'alias' => $modelAlias, 'alias' => $modelAlias,
'conditions' => "{$modelKey} = {$modelLink}", 'conditions' => "{$modelKey} = {$modelLink}",
'table' => $db->fullTableName($_Model, true)); 'table' => $db->fullTableName($Model, true));
// Now for the top level join. This will be added into the list // Now for the top level join. This will be added into the list
// of joins down below, outside of the HABTM specific code. // of joins down below, outside of the HABTM specific code.
@@ -393,46 +393,35 @@ class LinkableBehavior extends ModelBehavior {
)); ));
if (empty($options['table'])) { if (empty($options['table'])) {
$options['table'] = $db->fullTableName($_Model, true); $options['table'] = $db->fullTableName($Model, true);
} }
if (!isset($options['fields']) || !is_array($options['fields'])) if (!isset($options['fields']) || !is_array($options['fields']))
$options['fields'] = $db->fields($_Model, $modelAlias); $options['fields'] = $db->fields($Model, $modelAlias);
elseif (!empty($options['fields'])) elseif (!empty($options['fields']))
$options['fields'] = $db->fields($_Model, $modelAlias, $options['fields']); $options['fields'] = $db->fields($Model, $modelAlias, $options['fields']);
$query['fields'] = array_merge($query['fields'], $options['fields']); $result['fields'] = array_merge($result['fields'], $options['fields']);
$options[$this->_key] = am($options[$this->_key], array_diff_key($options, $optionsKeys)); $result['joins'][] = array_intersect_key($options,
$options = array_intersect_key($options, $optionsKeys); array('type' => true,
if (!empty($options[$this->_key])) { 'alias' => true,
$this->pr(24, 'table' => true,
array('checkpoint' => 'Add new iterator', 'joins' => true,
'options[this->_key]' => $options[$this->_key], 'conditions' => true));
compact('defaults', 'modelClass', 'modelAlias'),
)); $sublinks = array_diff_key($options, $this->_options);
$iterators[] = $options[$this->_key] + $this->buildQuery($Model, $modelClass, $modelAlias, $sublinks, $result);
array('defaults' =>
array_merge($defaults,
array('reference' =>
array('class' => $modelClass,
'alias' => $modelAlias))));
}
$query['joins'][] = array_intersect_key($options, array('type' => true, 'alias' => true, 'table' => true, 'joins' => true, 'conditions' => true));
$this->pr(19, $this->pr(19,
array('checkpoint' => 'Model Join Complete', array('checkpoint' => 'Model Join Complete',
compact('options', 'modelClass', 'modelAlias', 'query'), compact('referenceAlias', 'modelAlias', 'options', 'result'),
)); ));
} }
++$cont;
$notDone = isset($iterators[$cont]);
} while ($notDone);
$this->pr(20, $this->pr(20,
array('function' => 'Linkable::beforeFind', array('return' => 'Linkable::buildQuery',
'return' => compact('query'), compact('referenceAlias'),
)); ));
return $query;
} }
} }