/** * Add a JOIN clause to a Zend_Db_Select object * * @param Zend_Db_Select $select The select object * @param array $related Collection of related models * @param string $rule Used to figure out the relationship metadata from the referencemap * @param string $bindingModel Binding model used in HABTM relations * @param bool $bidirectional * @return void */ protected function _addJoinClause(Zend_Db_Select $select, array $related, $rule = null, $bindingModel = null, $bidirectional = true) { foreach ($related as $filterModelName => $filterValue) { $fieldInfo = explode('.', $filterModelName, 2); $filterModelName = Garp_Content_Api::modelAliasToClass($fieldInfo[0]); $filterColumn = $fieldInfo[1]; $filterModel = new $filterModelName(); /** * Determine wether a negation clause (e.g. !=) is requested * and normalize the filterColumn. */ $negation = strpos($filterColumn, '<>') !== false; $filterColumn = str_replace(' <>', '', $filterColumn); if ($filterModelName === get_class($this->_model)) { /* This is a homophile relation and the current condition touches the homophile model. The following condition prevents a 'relatable' list to include the current record, because a record cannot be related to itself. */ $select->where($this->_getTableName($filterModel) . '.' . $filterColumn . ' != ?', $filterValue); } try { // the other model is a child $reference = $filterModel->getReference(get_class($this->_model), $rule); $this->_addHasManyClause(array('select' => $select, 'filterModel' => $filterModel, 'reference' => $reference, 'filterColumn' => $filterColumn, 'filterValue' => $filterValue, 'negation' => $negation)); } catch (Zend_Db_Table_Exception $e) { try { // the other model is the parent $reference = $this->_model->getReference(get_class($filterModel), $rule); $this->_addBelongsToClause(array('select' => $select, 'reference' => $reference, 'filterColumn' => $filterColumn, 'filterValue' => $filterValue, 'negation' => $negation)); } catch (Zend_Db_Table_Exception $e) { try { // the models are equal; a binding model is needed $this->_addHasAndBelongsToManyClause(array('select' => $select, 'filterModel' => $filterModel, 'filterColumn' => $filterColumn, 'filterValue' => $filterValue, 'negation' => $negation, 'bindingModel' => $bindingModel, 'bidirectional' => $bidirectional)); } catch (Zend_Db_Table_Exception $e) { throw $e; } } } } }
/** * Find a related recordset. * @param Garp_Model $model The model that spawned this data * @param Garp_Db_Row $row The row object * @param Garp_Util_Configuration $options Various relation options * @return String The name of the method. */ protected function _getRelatedRowset(Garp_Model $model, Garp_Db_Table_Row $row, Garp_Util_Configuration $options) { /** * An optional passed SELECT object will be passed by reference after every query. * This results in an error when 'clone' is not used, because correlation names will be * used twice (since they were set during the first iteration). Using 'clone' makes sure * a brand new SELECT object is used every time that hasn't been soiled by a possible * previous query. */ $conditions = is_null($options['conditions']) ? null : clone $options['conditions']; $otherModel = $options['modelClass']; if (!$otherModel instanceof Zend_Db_Table_Abstract) { $otherModel = new $otherModel(); } /** * Do not cache related queries. The "outside" query should be the only * query that's cached. */ $originalCacheQueriesFlag = $otherModel->getCacheQueries(); $otherModel->setCacheQueries(false); $modelName = get_class($otherModel); $relatedRowset = null; // many to many if (!empty($options['bindingModel'])) { $relatedRowset = $row->findManyToManyRowset($otherModel, $options['bindingModel'], $options['rule2'], $options['rule'], $conditions); } else { /** * 'mode' is used to clear ambiguity with homophilic relationships. For example, * a Model_Doc can have have child Docs and one parent Doc. The conditionals below can never tell * which method to call (findParentRow or findDependentRowset) from the referenceMap. * Therefore, we can help the decision-making by passing "mode". This can either be * "parent" or "dependent", which will then force a call to findParentRow and findDependentRowset, * respectively. */ if (is_null($options['mode'])) { // belongs to try { $model->getReference($modelName, $options['rule']); $relatedRowset = $row->findParentRow($otherModel, $options['rule'], $conditions); } catch (Exception $e) { if (!Garp_Content_Relation_Manager::isInvalidReferenceException($e)) { throw $e; } try { // one to many - one to one // The following line triggers an exception if no reference is available $otherModel->getReference(get_class($model), $options['rule']); $relatedRowset = $row->findDependentRowset($otherModel, $options['rule'], $conditions); } catch (Exception $e) { if (!Garp_Content_Relation_Manager::isInvalidReferenceException($e)) { throw $e; } $bindingModel = $model->getBindingModel($modelName); $relatedRowset = $row->findManyToManyRowset($otherModel, $bindingModel, $options['rule2'], $options['rule'], $conditions); } } } else { switch ($options['mode']) { case 'parent': $relatedRowset = $row->findParentRow($otherModel, $options['rule'], $conditions); break; case 'dependent': $relatedRowset = $row->findDependentRowset($otherModel, $options['rule'], $conditions); break; default: throw new Garp_Model_Exception('Invalid value for "mode" given. Must be either "parent" or ' . '"dependent", but "' . $options['mode'] . '" was given.'); break; } } } // Reset the cacheQueries value. It's a static property, // so leaving it FALSE will affect all future fetch() calls to this // model. Not good. $otherModel->setCacheQueries($originalCacheQueriesFlag); return $relatedRowset; }