Completed
Push — master ( d9c7f5...aa86fb )
by jean
16s
created

StepRunner   A

Complexity

Total Complexity 32

Size/Duplication

Total Lines 265
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
eloc 127
dl 0
loc 265
ccs 120
cts 120
cp 1
rs 9.84
c 0
b 0
f 0
wmc 32

11 Methods

Rating   Name   Duplication   Size   Complexity  
A setNotifier() 0 3 1
A buildConfigurationProcess() 0 15 3
A stop() 0 9 2
A __construct() 0 12 3
A run() 0 20 2
A finalizeStep() 0 13 2
A runSteps() 0 21 4
A configureOptions() 0 8 1
B runStep() 0 77 9
A finalizeSteps() 0 18 4
A configureOptionsWithoutResolve() 0 4 1
1
<?php
2
3
namespace Darkilliant\ProcessBundle\Runner;
4
5
use Darkilliant\ProcessBundle\ProcessNotifier\ChainProcessNotifier;
6
use Psr\Log\LoggerInterface;
7
use Symfony\Component\OptionsResolver\OptionsResolver;
8
use Darkilliant\ProcessBundle\Configuration\ConfigurationProcess;
9
use Darkilliant\ProcessBundle\Configuration\ConfigurationStep;
10
use Darkilliant\ProcessBundle\Registry\LoggerRegistry;
11
use Darkilliant\ProcessBundle\Registry\StepRegistry;
12
use Darkilliant\ProcessBundle\Resolver\OptionDynamicValueResolver;
13
use Darkilliant\ProcessBundle\State\ProcessState;
14
use Darkilliant\ProcessBundle\Step\IterableStepInterface;
15
use Darkilliant\ProcessBundle\Step\StepInterface;
16
17
class StepRunner
18
{
19
    /** @var LoggerInterface */
20
    protected $logger;
21
22
    /** @var StepRegistry */
23
    protected $registry;
24
25
    /** @var LoggerRegistry */
26
    protected $loggerRegistry;
27
28
    /** @var OptionDynamicValueResolver */
29
    protected $dynamicValueResolver;
30
31
    /** @var array */
32
    protected $configuration;
33
34
    /** @var ChainProcessNotifier */
35
    protected $notifier;
36
37
    private $shouldStop = false;
38
39
    private $pcntlSupported = false;
40
41
    private $countStopScheduled = 0;
42
43
    /**
44
     * @internal
45
     */
46 14
    public function __construct(LoggerRegistry $loggerRegistry, StepRegistry $registry, OptionDynamicValueResolver $dynamicValueResolver, array $configuration, LoggerInterface $logger, ChainProcessNotifier $notifier)
47
    {
48 14
        $this->configuration = $configuration;
49 14
        $this->logger = $logger;
50 14
        $this->dynamicValueResolver = $dynamicValueResolver;
51 14
        $this->registry = $registry;
52 14
        $this->loggerRegistry = $loggerRegistry;
53 14
        $this->notifier = $notifier;
54 14
        $this->pcntlSupported = 'cli' === PHP_SAPI && extension_loaded('pcntl');
55
56 14
        if ($this->pcntlSupported) {
57 14
            pcntl_signal(SIGINT, [$this, 'stop']);
58
        }
59 14
    }
60
61
    /**
62
     * @codeCoverageIgnore
63
     */
64
    public function stop()
65
    {
66
        $this->logger->error('step runner stop scheduled');
67
        $this->shouldStop = true;
68
        ++$this->countStopScheduled;
69
70
        if ($this->countStopScheduled >= 3) {
71
            $this->logger->error('step runner stop forced');
72
            exit(1);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
73
        }
74
    }
75
76 1
    public function setNotifier($notifier)
77
    {
78 1
        $this->notifier = $notifier;
79 1
    }
80
81
    /**
82
     * @param string $processName
83
     *
84
     * @throws \Exception
85
     *
86
     * @return ConfigurationProcess
87
     */
88 12
    public function buildConfigurationProcess(string $processName, string $logger = null): ConfigurationProcess
89
    {
90 12
        if (empty($this->configuration['process'][$processName])) {
91 2
            throw new \Exception(sprintf(
92 2
                'process %s not found, available (%s)',
93 2
                $processName,
94 2
                implode(', ', array_keys($this->configuration['process']))
95
            ));
96
        }
97
98 10
        if (null !== $logger) {
99 2
            $this->configuration['process'][$processName]['logger'] = $logger;
100
        }
101
102 10
        return ConfigurationProcess::create($processName, $this->configuration['process'][$processName]);
103
    }
104
105 8
    public function run(ConfigurationProcess $process, array $context = [], $data = [], $dryRun = false): bool
106
    {
107 8
        $processState = new ProcessState(
108 8
            $context,
109 8
            $this->loggerRegistry->resolveService($process->getLogger()),
110 8
            $this
111
        );
112 8
        $processState->setData($data);
113 8
        $processState->setDryRun($dryRun);
114 8
        $processState->setName($process->getName());
115
116 8
        if ($process->getDeprecated()) {
117 1
            $processState->warning('DEPRECATED STEPS USED', ['deprecated' => $process->getDeprecated()]);
118
        }
119
120 8
        $this->notifier->onStartRunner($processState);
121 8
        $isSucessFull = $this->runSteps($processState, $process->getSteps());
122 8
        $this->notifier->onEndRunner($processState, $isSucessFull);
123
124 8
        return $isSucessFull;
125
    }
126
127 4
    public function finalizeStep(ProcessState $processState, ConfigurationStep $step)
128
    {
129 4
        $processState->markSuccess();
130
131 4
        $service = $this->registry->resolveService($step->getService());
132 4
        $this->configureOptionsWithoutResolve($service, $step, $processState);
133 4
        $service->finalize($processState);
134
135 3
        if (ProcessState::RESULT_OK !== $processState->getResult()) {
136 1
            return false;
137
        }
138
139 2
        return true;
140
    }
141
142 8
    public function runSteps(ProcessState $processState, array $steps): bool
143
    {
144 8
        $processState->setContext('current_error', null);
145
146 8
        foreach ($steps as $step) {
147
            try {
148 8
                if (!$this->runStep($processState, $step)) {
149 7
                    return false;
150
                }
151 1
            } catch (\Throwable $exception) {
152 1
                $processState->setContext('current_error', $exception, false);
153 1
                $processState->getLogger()->error('fail step', array_merge([
154 1
                    'message' => $exception->getMessage(),
155 1
                    'step' => $step->getService(),
156 1
                ], $processState->getRawContext()));
157
158 7
                return false;
159
            }
160
        }
161
162 6
        return true;
163
    }
164
165 7
    protected function configureOptions(StepInterface $service, ConfigurationStep $step, ProcessState $processState): ProcessState
166
    {
167 7
        return $processState->setOptions(
168 7
            $this->dynamicValueResolver->resolve(
169 7
                $service->configureOptionResolver(new OptionsResolver())->resolve($step->getOptions()),
170
                [
171 7
                    'data' => $processState->getData(),
172 7
                    'context' => $processState->getRawContext(),
173
                ]
174
            )
175
        );
176
    }
177
178 4
    protected function configureOptionsWithoutResolve(StepInterface $service, ConfigurationStep $step, ProcessState $processState): ProcessState
179
    {
180 4
        return $processState->setOptions(
181 4
            $service->configureOptionResolver(new OptionsResolver())->resolve($step->getOptions())
182
        );
183
    }
184
185 8
    protected function runStep(ProcessState $processState, ConfigurationStep $step): int
186
    {
187 8
        $processState->markSuccess();
188
189 8
        if (!$step->isEnabled()) {
190 1
            return true;
191
        }
192
193
        /**
194
         * @var ConfigurationStep
195
         */
196 8
        $service = $this->registry->resolveService($step->getService());
197
198 8
        $processState = $this->configureOptions($service, $step, $processState);
199 8
        $options = $processState->getOptions();
200
201 8
        $this->notifier->onStartProcess($processState, $service);
202
203 8
        $service->execute($processState);
204
205 7
        $this->notifier->onExecutedProcess($processState, $service);
206
207 7
        if (ProcessState::RESULT_OK !== $processState->getResult()) {
208 2
            return false;
209
        }
210 6
        if ($service instanceof IterableStepInterface) {
211 4
            $this->notifier->onStartIterableProcess($processState, $service);
212
213 4
            $iterator = $processState->getIterator();
214 4
            $loopContext = $processState->getLoopContext();
215
216 4
            $count = $service->count($processState);
217
218 4
            while ($service->valid($processState)) {
219 4
                if ($this->pcntlSupported) {
220
                    // check ctrl+c (SIGINT)
221 4
                    pcntl_signal_dispatch();
222
                }
223
224 4
                $currentIndex = $service->getProgress($processState);
225
226 4
                $service->next($processState);
227 4
                $this->notifier->onUpdateIterableProcess($processState, $service);
228
229 4
                if ($this->shouldStop || ProcessState::RESULT_BREAK === $processState->getResult()) {
230 1
                    $processState->noLoop();
231 1
                    $this->finalizeSteps($processState, $step->getChildren());
232 1
                    $this->notifier->onEndProcess($processState, $service);
233
234 1
                    return true;
235
                }
236
237
                // Add metadata information of the current iteration of loop
238 4
                $processState->loop($currentIndex, $count, !$service->valid($processState));
239
240 4
                $isSuccessful = $this->runSteps($processState, $step->getChildren());
241 4
                $processState->setIterator($iterator);
242 4
                $processState->setOptions($options);
243 4
                $processState->setLoopContext($loopContext);
244
245 4
                if ($isSuccessful) {
246 3
                    $service->onSuccessLoop($processState);
247 3
                    $this->notifier->onSuccessLoop($processState, $service);
248 3
                    $processState->info('successful');
249
                } else {
250 1
                    $service->onFailedLoop($processState);
251 1
                    $this->notifier->onFailedLoop($processState, $service);
252
                }
253
            }
254 3
            $processState->noLoop();
255
256 3
            $this->finalizeSteps($processState, $step->getChildren());
257
        }
258
259 6
        $this->notifier->onEndProcess($processState, $service);
260
261 6
        return true;
262
    }
263
264 4
    private function finalizeSteps(ProcessState $processState, array $steps)
265
    {
266 4
        foreach ($steps as $step) {
267
            try {
268 4
                if (!$this->finalizeStep($processState, $step)) {
269 3
                    return false;
270
                }
271 1
            } catch (\Exception $exception) {
272 1
                $processState->error('fail step', [
273 1
                    'message' => $exception->getMessage(),
274 1
                    'step' => $step->getService(),
275
                ]);
276
277 3
                return false;
278
            }
279
        }
280
281 2
        return true;
282
    }
283
}
284