/** * Retrieves opposite reference on selected node also involved in current * reference. * * @example * Consider relation spanning over nodes/models "a", "b" and "c". This * relation consists of two references "a"-"b" and "b"-"c", with the latter * referencing from "b" to "c" ("b"->"c"). * * The opposite reference of its referencing node ("b") is "a"-"b" while the * opposite reference of its referenced node ("c") is missing, thus * returning null here. * * @param model_relation_node $node either end of current reference * @return model_relation_reference|null opposite reference at selected end, * null if missing another reference there */ public function getOppositeReferenceAtNode(model_relation_node $node) { if ($node != $this->predecessorEnd && $node != $this->successorEnd) { throw new \InvalidArgumentException('foreign node rejected'); } if ($node == $this->predecessorEnd && $this->intIndexInRelation == 0) { // there is no opposite of predecessor if current reference is first // one in relation return null; } try { return $this->relation->referenceAtIndex($this->intIndexInRelation + ($node == $this->predecessorEnd ? -1 : +1)); } catch (\OutOfRangeException $e) { return null; } }
public function onSelectingItem(model_editor $editor, model $item, model_editor_field $field) { // bind element's relation with item sharing its data source $this->relation->setDatasource($item->source())->bindNodeOnItem(0, $item); }
/** * @param model_relation $relation * @param $relationSpecification * @return model_relation */ protected static function _compileRelation(model_relation $relation = null, $relationSpecification) { // extract declaration of next reference from provided specs $toNext = is_array(@$relationSpecification['referencing']) ? $relationSpecification['referencing'] : null; $fromNext = is_array(@$relationSpecification['referencedBy']) ? $relationSpecification['referencedBy'] : null; // ensure specs are containing either of the two options, only if (!(is_array($toNext) ^ is_array($fromNext))) { throw new \InvalidArgumentException('invalid reference mode in relation'); } // select found declaration of next reference $referenceToAppend = $toNext ? $toNext : $fromNext; /* * get current end node of relation for appending new reference */ if (is_null($relation)) { // there isn't any "current end node" for relation is empty, yet // -> start new relation on current model $previousNode = model_relation_node::createOnModel(new \ReflectionClass(get_called_class())); // -> apply optionally given global alias if (@$relationSpecification['alias']) { $previousNode->setAlias($relationSpecification['alias']); } } else { // get end node of existing relation $previousNode = $relation->nodeAtIndex(-1); } /* * extract description of model associated with next node to append */ if (array_key_exists('model', $referenceToAppend)) { // got explicit name of model existing in code $model = static::normalizeModel($referenceToAppend['model']); $model = model_relation_model::createOnModel($model); } else { // missing explicit name of model existing in code // -> need to manage virtual model if ($fromNext && @$referenceToAppend['referencing'] && !@$referenceToAppend['referencedBy']) { // next node in relation is of type many-to-many // -> suitable for deriving virtual model $succeedingReference =& $referenceToAppend['referencing']; /* * in a relation a < m > c ... * * ... this code is about to describe virtual model of m * ... $previousNode refers to node a * ... $referenceToAppend provides information on a<m or m more specifically * ... $succeedingReference provides information on m>c or c more specifically */ if (!@$succeedingReference['model']) { // don't support chains of virtual models in relations throw new \InvalidArgumentException('invalid chaining of nodes with virtual models'); } // get model of node succeeding reference is referring to (model of c in example above) $nextModel = model_relation_model::createOnModel(static::normalizeModel($succeedingReference['model'])); /* * get names of either neighbouring model's set * * (don't care for aliases here for these might change occasionally) */ $previousSetName = $previousNode->getName(true); $nextSetName = $nextModel->getSetName(); if (!$previousSetName || !$nextSetName) { throw new \InvalidArgumentException('missing set names of models neighbouring virtual one'); } /* * extract name of virtual model's data set */ if (array_key_exists('dataset', $referenceToAppend)) { // specification provides set name explicitly $setName = $referenceToAppend['dataset']; } else { // specification does not provide set name explicitly // -> derive name from set names of neighbouring models $setName = "{$previousSetName}_{$nextSetName}"; } /* * derive properties and types of virtual model's data set */ // get definitions of properties in either neighbouring model $previousDefinition = $previousNode->getModel()->getDefinition(); $nextDefinition = $nextModel->getDefinition(); // get properties in either neighbouring model to refer to in virtual $previousNames = array_values((array) \de\toxa\txf\_1(@$referenceToAppend['on'], 'id')); $nextNames = array_values((array) \de\toxa\txf\_1(@$succeedingReference['on'], 'id')); if (!count($previousNames) || !count($nextNames)) { throw new \InvalidArgumentException('missing names of properties virtual node is referencing on'); } // get names of properties in virtual model to use on referencing // - try explicitly mentioned names of properties first $namesOnPrevious = array_values((array) @$referenceToAppend['with']); $namesOnNext = array_values((array) @$succeedingReference['with']); // - fall back to implicitly deriving property names from referenced properties if (!count($namesOnPrevious)) { $namesOnPrevious = array_map(function ($name) use($previousSetName) { return "{$previousSetName}_{$name}"; }, $previousNames); $referenceToAppend['with'] = $namesOnPrevious; } if (!count($namesOnNext)) { $namesOnNext = array_map(function ($name) use($nextSetName) { return "{$nextSetName}_{$name}"; }, $nextNames); $succeedingReference['with'] = $namesOnNext; } // ensure either reference is working with uniquely named properties $clashingNames = array_intersect($namesOnPrevious, $namesOnNext); if (count($clashingNames)) { throw new \InvalidArgumentException('invalid clash of implicit property names: ' . implode(', ', $clashingNames)); } // finally compile definition of virtual model's data set $virtualDefinition = array(); foreach ($namesOnPrevious as $index => $name) { $relatedName = $previousNames[$index]; $virtualDefinition[$name] = preg_replace('/\\bprimary\\s+key\\b/i', '', $previousDefinition[$relatedName]); } foreach ($namesOnNext as $index => $name) { $relatedName = $nextNames[$index]; $virtualDefinition[$name] = preg_replace('/\\bprimary\\s+key\\b/i', '', $nextDefinition[$relatedName]); } /* * create virtual model basing on description prepared above */ $model = model_relation_model::createOnVirtualModel($setName, $virtualDefinition, array_keys($virtualDefinition)); } else { // next node isn't of type many-to-many // -> reject implicitly to derive some virtual model throw new \InvalidArgumentException('definition of relation is missing model of node'); } } /* * create node to append */ $nextNode = model_relation_node::createOnModel($model); if ($referenceToAppend['alias']) { $nextNode->setAlias($referenceToAppend['alias']); } /* * establish reference between previous and next node as declared */ $targetProperty = @$referenceToAppend['on']; if (!$targetProperty) { $targetProperty = array('id'); } if ($toNext) { // establish reference from previous node to new node $previousNode->makeReferencingSuccessorIn($referenceToAppend['with']); $nextNode->makeReferencedByPredecessorOn($targetProperty); } else { // establish reference from new node to previous one $previousNode->makeReferencedBySuccessorOn($targetProperty); $nextNode->makeReferencingPredecessorIn($referenceToAppend['with']); } /* * append node to relation */ if (is_null($relation)) { // relation hasn't been created before at all // -> create now $relation = model_relation::create($previousNode); } $expectingAnotherReference = @$referenceToAppend['referencing'] || @$referenceToAppend['referencedBy']; // append next node to relation $relation->add($nextNode, $expectingAnotherReference); /* * recursively add another node on having declaration */ if ($expectingAnotherReference) { return static::_compileRelation($relation, $referenceToAppend); } return $relation; }