Passed
Pull Request — 0.4 (#35)
by jean
03:13
created

StepRunner::stop()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 6
nc 2
nop 0
dl 0
loc 9
ccs 0
cts 0
cp 0
crap 6
rs 10
c 0
b 0
f 0
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($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
115 8
        if ($process->getDeprecated()) {
116 1
            $processState->warning('DEPRECATED STEPS USED', ['deprecated' => $process->getDeprecated()]);
117
        }
118
119 8
        return $this->runSteps($processState, $process->getSteps());
120
    }
121
122 4
    public function finalizeStep(ProcessState $processState, ConfigurationStep $step)
123
    {
124 4
        $processState->markSuccess();
125
126 4
        $service = $this->registry->resolveService($step->getService());
127 4
        $this->configureOptions($service, $step, $processState);
128 3
        $service->finalize($processState);
129
130 2
        if (ProcessState::RESULT_OK !== $processState->getResult()) {
131
            return false;
132
        }
133
134 2
        return true;
135
    }
136
137 8
    public function runSteps(ProcessState $processState, array $steps): bool
138
    {
139 8
        foreach ($steps as $step) {
140
            try {
141 8
                if (!$this->runStep($processState, $step)) {
142 7
                    return false;
143
                }
144 2
            } catch (\Throwable $exception) {
145 2
                $processState->getLogger()->error('fail step', array_merge([
146 2
                    'message' => $exception->getMessage(),
147 2
                    'step' => $step->getService(),
148 2
                ], $processState->getRawContext()));
149
150 7
                return false;
151
            }
152
        }
153
154 6
        return true;
155
    }
156
157 7
    protected function configureOptions(StepInterface $service, ConfigurationStep $step, ProcessState $processState): ProcessState
158
    {
159 7
        return $processState->setOptions(
160 7
            $this->dynamicValueResolver->resolve(
161 7
                $service->configureOptionResolver(new OptionsResolver())->resolve($step->getOptions()),
162
                [
163 7
                    'data' => $processState->getData(),
164 7
                    'context' => $processState->getRawContext(),
165
                ]
166
            )
167
        );
168
    }
169
170 8
    protected function runStep(ProcessState $processState, ConfigurationStep $step): int
171
    {
172 8
        $processState->markSuccess();
173
174 8
        if (!$step->isEnabled()) {
175 1
            return true;
176
        }
177
178
        /**
179
         * @var ConfigurationStep
180
         */
181 8
        $service = $this->registry->resolveService($step->getService());
182
183 8
        $processState = $this->configureOptions($service, $step, $processState);
184 8
        $options = $processState->getOptions();
185
186 8
        $this->notifier->onStartProcess($processState, $service);
187
188 8
        $service->execute($processState);
189
190 7
        $this->notifier->onExecutedProcess($processState, $service);
191
192 7
        if (ProcessState::RESULT_OK !== $processState->getResult()) {
193 2
            return false;
194
        }
195 6
        if ($service instanceof IterableStepInterface) {
196 4
            $this->notifier->onStartIterableProcess($processState, $service);
197
198 4
            $iterator = $processState->getIterator();
199
200 4
            $count = $service->count($processState);
201
202 4
            while ($service->valid($processState)) {
203 4
                if ($this->pcntlSupported) {
204
                    // check ctrl+c (SIGINT)
205 4
                    pcntl_signal_dispatch();
206
                }
207
208 4
                $currentIndex = $service->getProgress($processState);
209
210 4
                $service->next($processState);
211 4
                $this->notifier->onUpdateIterableProcess($processState, $service);
212
213 4
                if ($this->shouldStop || ProcessState::RESULT_BREAK === $processState->getResult()) {
214 1
                    $processState->noLoop();
215 1
                    $this->finalizeSteps($processState, $step->getChildren());
216 1
                    $this->notifier->onEndProcess($processState, $service);
217
218 1
                    return true;
219
                }
220
221
                // Add metadata information of the current iteration of loop
222 4
                $processState->loop($currentIndex, $count, !$service->valid($processState));
223
224 4
                $isSuccessful = $this->runSteps($processState, $step->getChildren());
225 4
                $processState->setIterator($iterator);
226 4
                $processState->setOptions($options);
227
228 4
                if ($isSuccessful) {
229 3
                    $service->onSuccessLoop($processState);
230 3
                    $processState->getLogger()->info('successful', $processState->getRawContext());
231
                } else {
232 1
                    $service->onFailedLoop($processState);
233
                }
234
            }
235 3
            $processState->noLoop();
236
237 3
            $this->finalizeSteps($processState, $step->getChildren());
238
        }
239
240 6
        $this->notifier->onEndProcess($processState, $service);
241
242 6
        return true;
243
    }
244
245 4
    private function finalizeSteps(ProcessState $processState, array $steps)
246
    {
247 4
        foreach ($steps as $step) {
248
            try {
249 4
                if (!$this->finalizeStep($processState, $step)) {
250 2
                    return false;
251
                }
252 2
            } catch (\Exception $exception) {
253 1
                $processState->getLogger()->error('fail step', array_merge([
254 1
                    'message' => $exception->getMessage(),
255 1
                    'step' => $step->getService(),
256 1
                ], $processState->getRawContext()));
257
258 3
                return false;
259
            }
260
        }
261
262 2
        return true;
263
    }
264
}
265