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