/** * 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; }
/** * Relate Chapters. * Called after insert and after update. * @param Array $chapters * @param Garp_Model_Db $model The subject model * @param Int $articleId The id of the involved article * @return Void */ public function relateChapters(array $chapters, Garp_Model_Db $model, $articleId) { // Start by unrelating all chapters Garp_Content_Relation_Manager::unrelate(array('modelA' => $model, 'modelB' => 'Model_Chapter', 'keyA' => $articleId)); // Reverse order since the Weighable behavior sorts chapter by weight DESC, // giving each new chapter the highest weight. $chapters = array_reverse($chapters); foreach ($chapters as $chapterData) { $chapterData = $this->_getValidChapterData($chapterData); /** * Insert a new chapter. * The chapter will take care of storing and relating the * content nodes. */ $chapterModel = new Model_Chapter(); $chapterId = $chapterModel->insert(array('type' => $chapterData['type'], 'content' => $chapterData['content'])); Garp_Content_Relation_Manager::relate(array('modelA' => $model, 'modelB' => 'Model_Chapter', 'keyA' => $articleId, 'keyB' => $chapterId)); } }
/** * Unrelate records. * * @param array|Garp_Util_Configuration $options Lots o' options: * 'modelA' string|Garp_Model_Db The first model or classname thereof * 'modelB' string|Garp_Model_Db The second model or classname thereof * 'keyA' mixed Primary key(s) of the first model * 'keyB' mixed Primary key(s) of the second model * 'rule' string The rule that stores this relationship in one of the * reference maps * @return bool Success */ public static function unrelate($options) { self::_normalizeOptionsForUnrelate($options); $modelA = $options['modelA']; $modelB = $options['modelB']; /** * If bindingModel is set, we can safely skip all those difficult checks below. */ if (is_object($options['bindingModel'])) { return self::_unrelateHasAndBelongsToMany($options); } try { /** * If this succeeds, it's a regular relationship where the foreign key * resides inside modelA. Continue as usual. */ $reference = $modelA->getReference(get_class($modelB), $options['rule']); } catch (Exception $e) { if (!self::isInvalidReferenceException($e)) { throw $e; } try { /** * If this succeeds, the foreign key resides in the modelA. * Flip modelA and modelB and keyA and keyB in order to normalize the * given configuration. * Call self::relate() recursively with these new options. */ $reference = $modelB->getReference(get_class($modelA), $options['rule']); $keyA = $options['keyA']; $keyB = $options['keyB']; $options['modelA'] = $modelB; $options['modelB'] = $modelA; $options['keyA'] = $keyB; $options['keyB'] = $keyA; return Garp_Content_Relation_Manager::unrelate($options); } catch (Exception $e) { if (!self::isInvalidReferenceException($e)) { throw $e; } /** * Goody, we're dealing with a hasAndBelongsToMany relationship here. * Try to construct the intersection model and save the relation * that way. */ return self::_unrelateHasAndBelongsToMany($options); } } /** * Figure out which model is leading. This depends on which of the two keys is provided. * This kind of flips the query around. For instance, when keyA is given, the query is * something like this: * UPDATE modelA SET foreignkey = NULL WHERE primarykey = keyA * When keyB is given however, the query goes something like this: * UPDATE modelA SET foreignkey = NULL WHERE foreignkey = keyB */ $query = 'UPDATE `' . $modelA->getName() . '` SET '; $columnsToValues = array(); foreach ($reference['columns'] as $column) { $columnsToValues[] = '`' . $column . '` = NULL'; } $columnsToValues = implode(' AND ', $columnsToValues); $query .= $columnsToValues; $whereColumnsToValues = array(); if ($options['keyA']) { $useColumns = 'refColumns'; $useKeys = 'keyA'; } else { $useColumns = 'columns'; $useKeys = 'keyB'; } foreach ($reference[$useColumns] as $i => $column) { $whereColumnsToValues[] = '`' . $column . '` = ' . $options[$useKeys][$i]; } $whereColumnsToValues = implode(' AND ', $whereColumnsToValues); $query .= ' WHERE '; $query .= $whereColumnsToValues; return $modelA->getAdapter()->query($query); }
/** * Unrelate entities from each other * * @param array $options * @return bool */ public function unrelate(array $options) { $this->_checkAcl('relate'); extract($options); if (!isset($primaryKey) || !isset($model) || !isset($foreignKeys)) { throw new Garp_Content_Exception('Not enough options. "primaryKey", "model" and "foreignKeys" are required.'); } $model = Garp_Content_Api::modelAliasToClass($model); $primaryKey = (array) $primaryKey; $foreignKeys = (array) $foreignKeys; $rule = isset($rule) ? $rule : null; $rule2 = isset($rule2) ? $rule2 : null; $bindingModel = isset($bindingModel) ? 'Model_' . $bindingModel : null; $bidirectional = isset($bidirectional) ? $bidirectional : null; Garp_Content_Relation_Manager::unrelate(array('modelA' => $this->_model, 'modelB' => $model, 'keyA' => $primaryKey, 'keyB' => $foreignKeys, 'rule' => $rule, 'ruleB' => $rule2, 'bindingModel' => $bindingModel, 'bidirectional' => $bidirectional)); }
/** * Relate ContentNodes to a chapter. * @param Array $contentNodeList * @param Int $chapterId * @return Void */ public function relateContentNodes($contentNodeList, $chapterId) { // Reverse node list because the Weighable behavior sorts different from the way // the CMS sends us the nodes. $contentNodeList = array_reverse($contentNodeList); foreach ($contentNodeList as $contentNode) { $node = $this->_getValidContentNodeData($contentNode); // Save ContentNode $node['chapter_id'] = $chapterId; $contentNodeId = $this->_insertContentNode($node); // @todo Move everything below here to Model_ContentNode::afterInsert() // Determine content type $contentTypeModelName = 'Model_' . $node['model']; $contentTypeModel = new $contentTypeModelName(); // Check for existing id $data = $node['data']; if (empty($data['id'])) { // If no id is present, create a new subtype record $contentTypeId = $contentTypeModel->insert($data); } else { // Update the chapter subtype's content $contentTypeModel->update($data, 'id = ' . $contentTypeModel->getAdapter()->quote($data['id'])); $contentTypeId = $data['id']; } // Relate the ContentNode to the subtype record Garp_Content_Relation_Manager::relate(array('modelA' => 'Model_ContentNode', 'modelB' => $contentTypeModel, 'keyA' => $contentNodeId, 'keyB' => $contentTypeId)); } }