/** * Retrieves properties' names and associated model's set name or alias from * node at either end of reference. * * @param bool $blnUsePredecessor true to operate on node at predecessor's end * @param bool $blnWantSetNames true to extract set name of selected node's model * @return array two-element array consisting of properties' names and model's set name/alias or null */ protected function _getNames($blnUsePredecessor, $blnWantSetNames) { if ($blnUsePredecessor) { return array($this->predecessorEnd->getSuccessorNames(), $blnWantSetNames ? $this->predecessorEnd->getName() : null); } return array($this->successorEnd->getPredecessorNames(), $blnWantSetNames ? $this->successorEnd->getName() : null); }
/** * Adds node to current relation. * * Relation's definition is extended by adding nodes. Added nodes must be * declaring reference on preceding node. Relation is completed by adding * non-partial node not declaring reference on another succeeding node . * Adding further nodes is rejected then. * * @param model_relation_node $node * @param bool $isPartialNode true if node is considered partially defined, yet * @return $this */ public function add(model_relation_node $node, $isPartialNode = false) { if ($this->isComplete()) { throw new \LogicException('adding to completed relation rejected'); } if (!$node->isValid()) { throw new \InvalidArgumentException('invalid relational node'); } if (!$node->wantsPredecessor()) { throw new \InvalidArgumentException('node is not suitable for linking with preceding node'); } $predecessor = $this->nodeAtIndex(-1); if ($predecessor->getSuccessorReferenceWidth() != $node->getPredecessorReferenceWidth()) { throw new \InvalidArgumentException('mismatching width of reference'); } $bindThere = $predecessor->canBindOnSuccessor(); $bindHere = $node->canBindOnPredecessor(); if (!(($bindThere && !$bindHere) ^ (!$bindThere && $bindHere))) { throw new \InvalidArgumentException('node is not compatible with predecessor in binding reference'); } $this->nodes[] = $node; if (!$isPartialNode && !$node->wantsSuccessor()) { // transfer set of nodes into set of references $this->references = array(); for ($i = 1; $i < count($this->nodes); $i++) { $this->references[] = new model_relation_reference($this->nodes[$i - 1], $this->nodes[$i], $this, count($this->references)); } } return $this; }
/** * @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; }