Code

< 40 %
40-60 %
> 60 %
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