![]() Server : Apache System : Linux server2.corals.io 4.18.0-348.2.1.el8_5.x86_64 #1 SMP Mon Nov 15 09:17:08 EST 2021 x86_64 User : corals ( 1002) PHP Version : 7.4.33 Disable Function : exec,passthru,shell_exec,system Directory : /home/corals/mautic.corals.io/vendor/doctrine/orm/src/Internal/ |
<?php declare(strict_types=1); namespace Doctrine\ORM\Internal; use Doctrine\ORM\Internal\TopologicalSort\CycleDetectedException; use function array_keys; use function spl_object_id; /** * TopologicalSort implements topological sorting, which is an ordering * algorithm for directed graphs (DG) using a depth-first searching (DFS) * to traverse the graph built in memory. * This algorithm has a linear running time based on nodes (V) and edges * between the nodes (E), resulting in a computational complexity of O(V + E). * * @internal */ final class TopologicalSort { private const NOT_VISITED = 1; private const IN_PROGRESS = 2; private const VISITED = 3; /** * Array of all nodes, indexed by object ids. * * @var array<int, object> */ private $nodes = []; /** * DFS state for the different nodes, indexed by node object id and using one of * this class' constants as value. * * @var array<int, self::*> */ private $states = []; /** * Edges between the nodes. The first-level key is the object id of the outgoing * node; the second array maps the destination node by object id as key. The final * boolean value indicates whether the edge is optional or not. * * @var array<int, array<int, bool>> */ private $edges = []; /** * Builds up the result during the DFS. * * @var list<object> */ private $sortResult = []; /** @param object $node */ public function addNode($node): void { $id = spl_object_id($node); $this->nodes[$id] = $node; $this->states[$id] = self::NOT_VISITED; $this->edges[$id] = []; } /** @param object $node */ public function hasNode($node): bool { return isset($this->nodes[spl_object_id($node)]); } /** * Adds a new edge between two nodes to the graph * * @param object $from * @param object $to * @param bool $optional This indicates whether the edge may be ignored during the topological sort if it is necessary to break cycles. */ public function addEdge($from, $to, bool $optional): void { $fromId = spl_object_id($from); $toId = spl_object_id($to); if (isset($this->edges[$fromId][$toId]) && $this->edges[$fromId][$toId] === false) { return; // we already know about this dependency, and it is not optional } $this->edges[$fromId][$toId] = $optional; } /** * Returns a topological sort of all nodes. When we have an edge A->B between two nodes * A and B, then B will be listed before A in the result. Visually speaking, when ordering * the nodes in the result order from left to right, all edges point to the left. * * @return list<object> */ public function sort(): array { foreach (array_keys($this->nodes) as $oid) { if ($this->states[$oid] === self::NOT_VISITED) { $this->visit($oid); } } return $this->sortResult; } private function visit(int $oid): void { if ($this->states[$oid] === self::IN_PROGRESS) { // This node is already on the current DFS stack. We've found a cycle! throw new CycleDetectedException($this->nodes[$oid]); } if ($this->states[$oid] === self::VISITED) { // We've reached a node that we've already seen, including all // other nodes that are reachable from here. We're done here, return. return; } $this->states[$oid] = self::IN_PROGRESS; // Continue the DFS downwards the edge list foreach ($this->edges[$oid] as $adjacentId => $optional) { try { $this->visit($adjacentId); } catch (CycleDetectedException $exception) { if ($exception->isCycleCollected()) { // There is a complete cycle downstream of the current node. We cannot // do anything about that anymore. throw $exception; } if ($optional) { // The current edge is part of a cycle, but it is optional and the closest // such edge while backtracking. Break the cycle here by skipping the edge // and continuing with the next one. continue; } // We have found a cycle and cannot break it at $edge. Best we can do // is to backtrack from the current vertex, hoping that somewhere up the // stack this can be salvaged. $this->states[$oid] = self::NOT_VISITED; $exception->addToCycle($this->nodes[$oid]); throw $exception; } } // We have traversed all edges and visited all other nodes reachable from here. // So we're done with this vertex as well. $this->states[$oid] = self::VISITED; $this->sortResult[] = $this->nodes[$oid]; } }