Adam Pippin
3 years ago
1 changed files with 123 additions and 0 deletions
@ -0,0 +1,123 @@ |
|||||
|
<?php |
||||
|
|
||||
|
declare(strict_types=1); |
||||
|
|
||||
|
namespace App\Util; |
||||
|
|
||||
|
class DependencyGraph |
||||
|
{ |
||||
|
/** @var DependencyGraphNode[] */ |
||||
|
protected $nodes; |
||||
|
|
||||
|
public function __construct() |
||||
|
{ |
||||
|
$this->nodes = []; |
||||
|
} |
||||
|
|
||||
|
public function add(string $name, array $depends_on = []): void |
||||
|
{ |
||||
|
if (!isset($this->nodes[$name])) |
||||
|
{ |
||||
|
$node = new DependencyGraphNode($name); |
||||
|
$this->nodes[$name] = $node; |
||||
|
$node->DependsOn = $depends_on; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
// Probably unnecessary -- if the same node is added multiple times |
||||
|
// with different dependencies something's probably way messed up, |
||||
|
// but... we'll try our best?! |
||||
|
$this->nodes[$name] = array_unique(array_merge($this->nodes[$name]->DependsOn, $depends_on)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public function link(): void |
||||
|
{ |
||||
|
foreach ($this->nodes as $node) |
||||
|
{ |
||||
|
foreach ($node->DependsOn as $depends_on) |
||||
|
{ |
||||
|
if (!isset($this->nodes[$depends_on])) |
||||
|
{ |
||||
|
throw new \Exception('Unmet dependency on node: '.$depends_on); |
||||
|
} |
||||
|
|
||||
|
if (!in_array($node->Name, $this->nodes[$depends_on]->DependedOnBy)) |
||||
|
{ |
||||
|
$this->nodes[$depends_on]->DependedOnBy[] = $node->Name; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public function flatten(): array |
||||
|
{ |
||||
|
$state = []; |
||||
|
|
||||
|
// Find leaf nodes, add each one to our state array. |
||||
|
foreach ($this->nodes as $node) |
||||
|
{ |
||||
|
if (sizeof($node->DependedOnBy) == 0) |
||||
|
{ |
||||
|
$state[] = $node; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Then walk through the array and insert each node's dependencies before |
||||
|
// it recursively. |
||||
|
$iter = 0; |
||||
|
for ($i = 0; $i < sizeof($state); $i++) |
||||
|
{ |
||||
|
if ($state[$i]->Processed) |
||||
|
{ |
||||
|
continue; |
||||
|
} |
||||
|
$state[$i]->Processed = true; |
||||
|
$depends_on = []; |
||||
|
foreach ($state[$i]->DependsOn as $depends_on_name) |
||||
|
{ |
||||
|
$depends_on[] = $this->nodes[$depends_on_name]; |
||||
|
} |
||||
|
array_splice($state, $i, 0, $depends_on); |
||||
|
--$i; |
||||
|
} |
||||
|
|
||||
|
// Remove all but the first appearance of a node. |
||||
|
// array_unique guarantees that the _first_ element will be retained, |
||||
|
// so this satisfies what we're trying to do. |
||||
|
return array_unique(array_map(static function($node) { return $node->Name; }, $state)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
class DependencyGraphNode |
||||
|
{ |
||||
|
/** @var string */ |
||||
|
public $Name; |
||||
|
|
||||
|
/** @var string */ |
||||
|
public $DependsOn; |
||||
|
|
||||
|
/** @var string */ |
||||
|
public $DependedOnBy; |
||||
|
|
||||
|
/** @var bool */ |
||||
|
public $Processed; |
||||
|
|
||||
|
public function __construct(string $name) |
||||
|
{ |
||||
|
$this->Name = $name; |
||||
|
$this->DependsOn = []; |
||||
|
$this->DependedOnBy = []; |
||||
|
$this->Processed = false; |
||||
|
} |
||||
|
|
||||
|
public function dependsOn(string $node): void |
||||
|
{ |
||||
|
$this->DependsOn[] = $node; |
||||
|
} |
||||
|
|
||||
|
public function dependedOnBy(string $node): void |
||||
|
{ |
||||
|
$this->DependedOnBy[] = $node; |
||||
|
} |
||||
|
} |
Loading…
Reference in new issue