1: <?php
2:
3: /**
4: * Copyright 2015, Eduardo Trujillo
5: *
6: * For the full copyright and license information, please view the LICENSE
7: * file that was distributed with this source code.
8: *
9: * This file is part of the Nucleus package
10: */
11:
12: namespace Chromabits\Nucleus\Meditation;
13:
14: use Chromabits\Nucleus\Exceptions\CoreException;
15: use Chromabits\Nucleus\Foundation\BaseObject;
16: use Chromabits\Nucleus\Meditation\Interfaces\CheckableInterface;
17: use Chromabits\Nucleus\Support\Arr;
18:
19: /**
20: * Class SpecGraph.
21: *
22: * @author Eduardo Trujillo <ed@chromabits.com>
23: * @package Chromabits\Nucleus\Meditation
24: */
25: class SpecGraph extends BaseObject implements CheckableInterface
26: {
27: /**
28: * @var Spec[]
29: */
30: protected $nodes;
31:
32: /**
33: * @var array
34: */
35: protected $incomingEdges;
36:
37: /**
38: * @var array
39: */
40: protected $pending;
41:
42: /**
43: * @var array
44: */
45: protected $checked;
46:
47: /**
48: * @var SpecResult[]
49: */
50: protected $results;
51:
52: /**
53: * @var bool
54: */
55: protected $failed;
56:
57: /**
58: * Construct an instance of a SpecGraph.
59: */
60: public function __construct()
61: {
62: parent::__construct();
63:
64: $this->nodes = [];
65: $this->incomingEdges = [];
66: $this->pending = [];
67: $this->checked = [];
68: $this->results = [];
69: $this->failed = false;
70: }
71:
72: /**
73: * Define and create a new spec graph.
74: *
75: * @return SpecGraphFactory
76: */
77: public static function create()
78: {
79: return new SpecGraphFactory();
80: }
81:
82: /**
83: * Add a node to the graph.
84: *
85: * @param string $name
86: * @param string[] $dependencies
87: * @param Spec $node
88: */
89: public function add($name, array $dependencies, Spec $node)
90: {
91: $this->nodes[$name] = $node;
92: $this->incomingEdges[$name] = $dependencies;
93:
94: if (count($dependencies) === 0) {
95: $this->pending[] = $name;
96: }
97: }
98:
99: /**
100: * Run another pass over the graph trying to run all nodes possible at the
101: * moment. The is a very simple CSP/dependency-resolution problem.
102: *
103: * @param array $input
104: */
105: protected function iterate(array $input)
106: {
107: foreach ($this->pending as $name) {
108: $result = $this->nodes[$name]->check($input);
109:
110: if ($result->failed()) {
111: $this->failed = true;
112: }
113:
114: $this->checked[$name] = $result;
115: }
116:
117: $this->pending = [];
118:
119: foreach (Arr::keys($this->nodes) as $name) {
120: if (array_key_exists($name, $this->checked)) {
121: continue;
122: }
123:
124: $free = true;
125: foreach ($this->incomingEdges[$name] as $requirement) {
126: if (!array_key_exists($requirement, $this->checked)) {
127: $free = false;
128: break;
129: }
130: }
131:
132: if ($free) {
133: $this->pending[] = $name;
134: }
135: }
136: }
137:
138: /**
139: * Check an input array against the SpecGraph.
140: *
141: * @param array $input
142: *
143: * @throws CoreException
144: * @throws Exceptions\InvalidArgumentException
145: * @return SpecResult
146: */
147: public function check(array $input)
148: {
149: // Automatic reset
150: if (count($this->pending) === 0) {
151: foreach (Arr::keys($this->nodes) as $name) {
152: if (count($this->incomingEdges[$name]) === 0) {
153: $this->pending[] = $name;
154: }
155: }
156:
157: $this->checked = [];
158: $this->results = [];
159: $this->failed = false;
160: }
161:
162: // Actual process
163: while (count($this->checked) < count($this->nodes)) {
164: if (count($this->pending) === 0) {
165: throw new CoreException('Unable to resolve constraint graph.');
166: }
167:
168: $this->iterate($input);
169:
170: if ($this->failed) {
171: $this->pending = [];
172: break;
173: }
174: }
175:
176: // Aggregate results
177: $missing = [];
178: $failed = [];
179:
180: array_map(function (SpecResult $result) use (&$missing, &$failed) {
181: $missing[] = $result->getMissing();
182: $failed[] = $result->getFailed();
183: }, $this->checked);
184:
185: return new SpecResult(
186: Arr::mergev($missing),
187: Arr::mergev($failed),
188: $this->failed ? SpecResult::STATUS_FAIL : SpecResult::STATUS_PASS
189: );
190: }
191: }
192: