Code Coverage  | 
      ||||||||||
Classes and Traits  | 
       Functions and Methods  | 
       Lines  | 
      ||||||||
| Total |         | 
       100.00%  | 
       1 / 1  | 
               | 
       100.00%  | 
       17 / 17  | 
       CRAP |         | 
       100.00%  | 
       62 / 62  | 
      
| NestedGraph |         | 
       100.00%  | 
       1 / 1  | 
               | 
       100.00%  | 
       17 / 17  | 
       33 |         | 
       100.00%  | 
       62 / 62  | 
      
| __construct |         | 
       100.00%  | 
       1 / 1  | 
       2 |         | 
       100.00%  | 
       7 / 7  | 
      |||
| getRootGraph |         | 
       100.00%  | 
       1 / 1  | 
       1 |         | 
       100.00%  | 
       1 / 1  | 
      |||
| getNodes |         | 
       100.00%  | 
       1 / 1  | 
       1 |         | 
       100.00%  | 
       1 / 1  | 
      |||
| getEdges |         | 
       100.00%  | 
       1 / 1  | 
       1 |         | 
       100.00%  | 
       1 / 1  | 
      |||
| createNode |         | 
       100.00%  | 
       1 / 1  | 
       1 |         | 
       100.00%  | 
       1 / 1  | 
      |||
| addNode |         | 
       100.00%  | 
       1 / 1  | 
       2 |         | 
       100.00%  | 
       6 / 6  | 
      |||
| getNode |         | 
       100.00%  | 
       1 / 1  | 
       2 |         | 
       100.00%  | 
       3 / 3  | 
      |||
| hasNode |         | 
       100.00%  | 
       1 / 1  | 
       1 |         | 
       100.00%  | 
       1 / 1  | 
      |||
| removeNode |         | 
       100.00%  | 
       1 / 1  | 
       3 |         | 
       100.00%  | 
       8 / 8  | 
      |||
| createEdge |         | 
       100.00%  | 
       1 / 1  | 
       3 |         | 
       100.00%  | 
       3 / 3  | 
      |||
| addEdge |         | 
       100.00%  | 
       1 / 1  | 
       3 |         | 
       100.00%  | 
       11 / 11  | 
      |||
| getEdge |         | 
       100.00%  | 
       1 / 1  | 
       2 |         | 
       100.00%  | 
       3 / 3  | 
      |||
| hasEdge |         | 
       100.00%  | 
       1 / 1  | 
       1 |         | 
       100.00%  | 
       1 / 1  | 
      |||
| removeEdge |         | 
       100.00%  | 
       1 / 1  | 
       3 |         | 
       100.00%  | 
       8 / 8  | 
      |||
| getParentNode |         | 
       100.00%  | 
       1 / 1  | 
       2 |         | 
       100.00%  | 
       1 / 1  | 
      |||
| nodeAttrChanged |         | 
       100.00%  | 
       1 / 1  | 
       2 |         | 
       100.00%  | 
       3 / 3  | 
      |||
| edgeAttrChanged |         | 
       100.00%  | 
       1 / 1  | 
       2 |         | 
       100.00%  | 
       3 / 3  | 
      |||
| onAttrChanged | n/a  | 
       0 / 0  | 
       1 | n/a  | 
       0 / 0  | 
      |||||
| <?php declare(strict_types = 1); | |
| /* | |
| * Copyright (c) 2017, Josef Kufner <josef@kufner.cz> | |
| * | |
| * Licensed under the Apache License, Version 2.0 (the "License"); | |
| * you may not use this file except in compliance with the License. | |
| * You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| * See the License for the specific language governing permissions and | |
| * limitations under the License. | |
| * | |
| */ | |
| namespace Smalldb\Graph; | |
| class NestedGraph extends AbstractElement | |
| { | |
| /** | |
| * Nodes | |
| * | |
| * @var Node[] | |
| */ | |
| private array $nodes = []; | |
| /** | |
| * @var Edge[] | |
| */ | |
| private array $edges = []; | |
| /** | |
| * Node owning this graph if the graph is nested. | |
| */ | |
| private ?Node $parentNode = null; | |
| /** | |
| * Root graph | |
| */ | |
| protected ?Graph $rootGraph = null; | |
| /** | |
| * Node index | |
| */ | |
| protected ElementAttrIndex $nodeAttrIndex; | |
| /** | |
| * Edge index | |
| */ | |
| protected ElementAttrIndex $edgeAttrIndex; | |
| /** | |
| * Constructor. | |
| */ | |
| protected function __construct(Node $parentNode = null, array $attrs = []) | |
| { | |
| parent::__construct($attrs); | |
| if ($parentNode) { | |
| $this->parentNode = $parentNode; | |
| $this->rootGraph = $this->parentNode->getGraph()->getRootGraph(); | |
| $this->nodeAttrIndex = $this->rootGraph->nodeAttrIndex; | |
| $this->edgeAttrIndex = $this->rootGraph->edgeAttrIndex; | |
| } | |
| } | |
| public function getRootGraph(): Graph | |
| { | |
| return $this->rootGraph; | |
| } | |
| /** | |
| * Get all nodes in the graph | |
| * | |
| * @return Node[] | |
| */ | |
| public function getNodes(): array | |
| { | |
| return $this->nodes; | |
| } | |
| /** | |
| * Get all edges in the graph | |
| * | |
| * @return Edge[] | |
| */ | |
| public function getEdges(): array | |
| { | |
| return $this->edges; | |
| } | |
| /** | |
| * Create node and add it to the graph. | |
| * | |
| * @param string $id | |
| * @param array $attrs | |
| * @return Node | |
| */ | |
| public function createNode(string $id, array $attrs = []): Node | |
| { | |
| return new Node($this, $id, $attrs); | |
| } | |
| /** | |
| * Add existing node to the graph. | |
| * | |
| * @param Node $node | |
| * @return NestedGraph | |
| */ | |
| public function addNode(Node $node): self | |
| { | |
| $id = $node->getId(); | |
| if (isset($this->nodes[$id])) { | |
| throw new DuplicateNodeException(sprintf("Node \"%s\" already present in the graph.", $id)); | |
| } | |
| // Add element | |
| $this->nodes[$id] = $node; | |
| // Update attr indexes | |
| $this->nodeAttrIndex->insertElement($node); | |
| return $this; | |
| } | |
| /** | |
| * Get node by its ID. | |
| * | |
| * @param string $id | |
| * @return Node | |
| */ | |
| public function getNode(string $id): Node | |
| { | |
| if (isset($this->nodes[$id])) { | |
| return $this->nodes[$id]; | |
| } else { | |
| throw new MissingNodeException(sprintf("Node \"%s\" not found in the graph.", $id)); | |
| } | |
| } | |
| /** | |
| * Returns true if a given node exists within the graph. | |
| */ | |
| public function hasNode(string $id): bool | |
| { | |
| return isset($this->nodes[$id]); | |
| } | |
| /** | |
| * Remove node and connected edges from the graph. | |
| * | |
| * @param Node $node | |
| * @return NestedGraph | |
| */ | |
| public function removeNode(Node $node): self | |
| { | |
| $id = $node->getId(); | |
| if (isset($this->nodes[$id])) { | |
| foreach ($node->getConnectedEdges() as $e) { | |
| $e->remove(); | |
| } | |
| unset($this->nodes[$id]); | |
| $this->nodeAttrIndex->removeElement($node); | |
| return $this; | |
| } else { | |
| throw new MissingNodeException(sprintf("Node \"%s\" not found in the graph.", $id)); | |
| } | |
| } | |
| /** | |
| * Create a new edge and add it to the graph. | |
| * | |
| * @param string|null $id ID of the edge, generated automatically if null. | |
| * @param array $attrs Key-value storage of the edge attributes. | |
| * @param Node $start | |
| * @param Node $end | |
| * @return Edge | |
| */ | |
| public function createEdge(?string $id, Node $start, Node $end, array $attrs = []): Edge | |
| { | |
| if ($id === null) { | |
| $id = '_' . count($this->getRootGraph()->getAllEdges()); | |
| } | |
| return new Edge($this, $id, $start, $end, $attrs); | |
| } | |
| /** | |
| * Add existing edge to the graph. | |
| * | |
| * @param Edge $edge | |
| * @return NestedGraph | |
| */ | |
| public function addEdge(Edge $edge): self | |
| { | |
| $id = $edge->getId(); | |
| $start = $edge->getStart(); | |
| $end = $edge->getEnd(); | |
| if (isset($this->rootGraph->edges[$id])) { | |
| throw new DuplicateEdgeException(sprintf("Edge \"%s\" already present in the graph.", $id)); | |
| } | |
| // Connect the edge to the nodes | |
| $start->connectEdge($edge); | |
| if ($start !== $end) { | |
| $end->connectEdge($edge); | |
| } | |
| // Add element | |
| $this->edges[$id] = $edge; | |
| // Update attr indexes | |
| $this->edgeAttrIndex->insertElement($edge); | |
| return $this; | |
| } | |
| /** | |
| * Get Edge by its ID. | |
| * | |
| * @param string $id | |
| * @return Edge | |
| */ | |
| public function getEdge(string $id): Edge | |
| { | |
| if (isset($this->edges[$id])) { | |
| return $this->edges[$id]; | |
| } else { | |
| throw new MissingEdgeException(sprintf("Edge \"%s\" not found in the graph.", $id)); | |
| } | |
| } | |
| /** | |
| * Returns true if a given edge exists within the graph. | |
| */ | |
| public function hasEdge(string $id): bool | |
| { | |
| return isset($this->edges[$id]); | |
| } | |
| /** | |
| * Remove edge from the graph and disconnect it from nodes. | |
| * | |
| * @param Edge $edge | |
| * @return NestedGraph | |
| */ | |
| public function removeEdge(Edge $edge): self | |
| { | |
| $id = $edge->getId(); | |
| if (isset($this->edges[$id])) { | |
| if ($this->edges[$id] !== $edge) { | |
| throw new MissingEdgeException(sprintf("Edge \"%s\" found in the graph is not the expected edge.", $id)); // @codeCoverageIgnore | |
| } | |
| $this->edges[$id]->disconnectNodes(); | |
| unset($this->edges[$id]); | |
| $this->edgeAttrIndex->removeElement($edge); | |
| return $this; | |
| } else { | |
| throw new MissingEdgeException(sprintf("Edge \"%s\" not found in the graph.", $id)); | |
| } | |
| } | |
| public function getParentNode(): ?Node | |
| { | |
| return $this->parentNode; | |
| } | |
| /** | |
| * @internal | |
| */ | |
| public function nodeAttrChanged($node, $key, $oldValue, $newValue) | |
| { | |
| if ($this->nodeAttrIndex->hasAttrIndex($key)) { | |
| $this->nodeAttrIndex->update($key, $oldValue, $newValue, $node); | |
| } | |
| } | |
| /** | |
| * @internal | |
| */ | |
| public function edgeAttrChanged($node, $key, $oldValue, $newValue) | |
| { | |
| if ($this->edgeAttrIndex->hasAttrIndex($key)) { | |
| $this->edgeAttrIndex->update($key, $oldValue, $newValue, $node); | |
| } | |
| } | |
| /** | |
| * Handle change of an attribute. | |
| * | |
| * @codeCoverageIgnore | |
| */ | |
| protected function onAttrChanged(string $key, $oldValue, $newValue) | |
| { | |
| // No-op. | |
| } | |
| } | |