Exemplo n.º 1
0
 /**
  * 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);
 }
Exemplo n.º 2
0
 /**
  * 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;
 }
Exemplo n.º 3
0
 /**
  * @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;
 }