1 | <?php |
||
2 | |||
3 | namespace SRIO\ChainOfResponsibility; |
||
4 | |||
5 | use PlasmaConduit\DependencyGraph; |
||
6 | use PlasmaConduit\dependencygraph\DependencyGraphNode; |
||
7 | use PlasmaConduit\either\Left; |
||
8 | use SRIO\ChainOfResponsibility\Exception\CircularDependencyException; |
||
9 | use SRIO\ChainOfResponsibility\Exception\UnresolvedDependencyException; |
||
10 | |||
11 | final class ChainBuilder extends ProcessCollection |
||
12 | { |
||
13 | /** |
||
14 | * Name of the root node in dependency graph. |
||
15 | * |
||
16 | * @var string |
||
17 | */ |
||
18 | const ROOT_NODE_NAME = '_root'; |
||
19 | |||
20 | /** |
||
21 | * @param array $processes |
||
22 | */ |
||
23 | 5 | public function __construct(array $processes) |
|
24 | { |
||
25 | 5 | $this->add($processes); |
|
26 | 5 | } |
|
27 | |||
28 | /** |
||
29 | * Get runner for given processes. |
||
30 | * |
||
31 | * @return ChainRunner |
||
32 | * |
||
33 | * @throws UnresolvedDependencyException |
||
34 | */ |
||
35 | 5 | public function getRunner() |
|
36 | { |
||
37 | 5 | return new ChainRunner($this->getOrderedProcesses()); |
|
38 | } |
||
39 | |||
40 | /** |
||
41 | * Get processes ordered based on their dependencies. |
||
42 | * |
||
43 | * @return array |
||
44 | * |
||
45 | * @throws CircularDependencyException |
||
46 | * @throws UnresolvedDependencyException |
||
47 | */ |
||
48 | 5 | public function getOrderedProcesses() |
|
49 | { |
||
50 | 5 | $graph = new DependencyGraph(); |
|
51 | 5 | $root = new DependencyGraphNode(self::ROOT_NODE_NAME); |
|
52 | 5 | $graph->addRoot($root); |
|
53 | |||
54 | 5 | $processes = $this->getProcesses(); |
|
55 | 5 | $nodes = []; |
|
56 | 5 | foreach ($processes as $process) { |
|
57 | 5 | $processName = $this->getProcessName($process); |
|
58 | 5 | $node = new DependencyGraphNode($processName); |
|
59 | 5 | $graph->addDependency($root, $node); |
|
60 | 5 | $nodes[$processName] = [$process, $node]; |
|
61 | 5 | } |
|
62 | |||
63 | 5 | foreach ($nodes as $processName => $nodeDescription) { |
|
64 | 5 | list($process, $node) = $nodeDescription; |
|
65 | 5 | if (!$process instanceof DependentChainProcessInterface) { |
|
66 | 1 | $graph->addDependency($root, $node); |
|
67 | 1 | continue; |
|
68 | } |
||
69 | |||
70 | 4 | foreach ($process->dependsOn() as $dependencyName) { |
|
71 | 4 | if (!array_key_exists($dependencyName, $nodes)) { |
|
72 | 1 | throw new UnresolvedDependencyException(sprintf( |
|
73 | 1 | 'Process "%s" is dependent of "%s" which is not found', |
|
74 | 1 | $processName, |
|
75 | $dependencyName |
||
76 | 1 | )); |
|
77 | } |
||
78 | |||
79 | 3 | if ($graph->addDependency($node, $nodes[$dependencyName][1]) instanceof Left) { |
|
80 | 1 | throw new CircularDependencyException(sprintf( |
|
81 | 1 | 'Circular dependency found: %s already depends on %s', |
|
82 | 1 | $dependencyName, $processName |
|
83 | 1 | )); |
|
84 | } |
||
85 | 3 | } |
|
86 | 4 | } |
|
87 | |||
88 | return array_map(function ($nodeName) use ($nodes) { |
||
89 | 3 | return $nodes[$nodeName][0]; |
|
90 | 3 | }, array_filter($graph->flatten(), function ($nodeName) { |
|
91 | 3 | return $nodeName !== self::ROOT_NODE_NAME; |
|
92 | 3 | })); |
|
93 | } |
||
94 | |||
95 | /** |
||
96 | * @param ChainProcessInterface $process |
||
97 | * |
||
98 | * @return string |
||
99 | */ |
||
100 | 5 | private function getProcessName(ChainProcessInterface $process) |
|
101 | { |
||
102 | 5 | return $process instanceof NamedChainProcessInterface ? $process->getName() : spl_object_hash($process); |
|
103 | } |
||
104 | } |
||
105 |