Test Failed
Pull Request — master (#39)
by Dmitriy
34:22 queued 14:17
created

RoadRunnerHttpApplicationRunner::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 29
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 14
nc 1
nop 13
dl 0
loc 29
ccs 15
cts 15
cp 1
crap 1
rs 9.7998
c 0
b 0
f 0

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Yii\Runner\RoadRunner;
6
7
use ErrorException;
8
use Exception;
9
use JsonException;
10
use Psr\Container\ContainerExceptionInterface;
11
use Psr\Container\ContainerInterface;
12
use Psr\Container\NotFoundExceptionInterface;
13
use Psr\Http\Message\ResponseInterface;
14
use RuntimeException;
15
use Spiral\RoadRunner\Environment;
16
use Spiral\RoadRunner\Environment\Mode;
17
use Spiral\RoadRunner\Http\PSR7WorkerInterface;
18
use Temporal\Worker\WorkerFactoryInterface;
19
use Throwable;
20
use Yiisoft\Definitions\Exception\CircularReferenceException;
21
use Yiisoft\Definitions\Exception\InvalidConfigException;
22
use Yiisoft\Definitions\Exception\NotInstantiableException;
23
use Yiisoft\Di\NotFoundException;
24
use Yiisoft\Di\StateResetter;
25
use Yiisoft\ErrorHandler\ErrorHandler;
0 ignored issues
show
Bug introduced by
The type Yiisoft\ErrorHandler\ErrorHandler was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
26
use Yiisoft\ErrorHandler\Renderer\HtmlRenderer;
27
use Yiisoft\Log\Logger;
28
use Yiisoft\Log\Target\File\FileTarget;
29
use Yiisoft\Yii\Http\Application;
30
use Yiisoft\Yii\Runner\ApplicationRunner;
31
use Yiisoft\Yii\Runner\RoadRunner\Temporal\TemporalDeclarationProvider;
32
33
use function gc_collect_cycles;
34
35
/**
36
 * `RoadRunnerHttpApplicationRunner` runs the Yii HTTP application using RoadRunner.
37
 */
38
final class RoadRunnerHttpApplicationRunner extends ApplicationRunner
39
{
40
    private ?ErrorHandler $temporaryErrorHandler = null;
41
    private ?PSR7WorkerInterface $psr7Worker = null;
42
    private bool $isTemporalEnabled = false;
43
44
    /**
45
     * @param string $rootPath The absolute path to the project root.
46
     * @param bool $debug Whether the debug mode is enabled.
47
     * @param bool $checkEvents Whether to check events' configuration.
48
     * @param string|null $environment The environment name.
49
     * @param string $bootstrapGroup The bootstrap configuration group name.
50
     * @param string $eventsGroup The events' configuration group name.
51
     * @param string $diGroup The container definitions' configuration group name.
52
     * @param string $diProvidersGroup The container providers' configuration group name.
53
     * @param string $diDelegatesGroup The container delegates' configuration group name.
54
     * @param string $diTagsGroup The container tags' configuration group name.
55
     * @param string $paramsGroup The configuration parameters group name.
56
     * @param array $nestedParamsGroups Configuration group names that are included into configuration parameters group.
57 9
     * This is needed for recursive merging of parameters.
58
     * @param array $nestedEventsGroups Configuration group names that are included into events' configuration group.
59
     * This is needed for reverse and recursive merge of events' configurations.
60
     *
61
     * @psalm-param list<string> $nestedParamsGroups
62
     * @psalm-param list<string> $nestedEventsGroups
63
     */
64
    public function __construct(
65
        string $rootPath,
66
        bool $debug = false,
67
        bool $checkEvents = false,
68
        ?string $environment = null,
69
        string $bootstrapGroup = 'bootstrap-web',
70
        string $eventsGroup = 'events-web',
71
        string $diGroup = 'di-web',
72 9
        string $diProvidersGroup = 'di-providers-web',
73 9
        string $diDelegatesGroup = 'di-delegates-web',
74 9
        string $diTagsGroup = 'di-tags-web',
75 9
        string $paramsGroup = 'params-web',
76 9
        array $nestedParamsGroups = ['params'],
77 9
        array $nestedEventsGroups = ['events'],
78 9
    ) {
79 9
        parent::__construct(
80 9
            $rootPath,
81 9
            $debug,
82 9
            $checkEvents,
83 9
            $environment,
84 9
            $bootstrapGroup,
85 9
            $eventsGroup,
86 9
            $diGroup,
87
            $diProvidersGroup,
88
            $diDelegatesGroup,
89
            $diTagsGroup,
90
            $paramsGroup,
91
            $nestedParamsGroups,
92
            $nestedEventsGroups,
93
        );
94
    }
95
96
    /**
97 2
     * Returns a new instance with the specified temporary error handler instance {@see ErrorHandler}.
98
     *
99 2
     * A temporary error handler is needed to handle the creation of configuration and container instances,
100 2
     * then the error handler configured in your application configuration will be used.
101 2
     *
102
     * @param ErrorHandler $temporaryErrorHandler The temporary error handler instance.
103
     */
104
    public function withTemporaryErrorHandler(ErrorHandler $temporaryErrorHandler): self
105
    {
106
        $new = clone $this;
107
        $new->temporaryErrorHandler = $temporaryErrorHandler;
108
        return $new;
109 9
    }
110
111 9
    /**
112 9
     * Returns a new instance with the specified PSR-7 worker instance {@see PSR7WorkerInterface}.
113 9
     *
114
     * @param PSR7WorkerInterface $worker The PSR-7 worker instance.
115
     */
116
    public function withPsr7Worker(PSR7WorkerInterface $worker): self
117
    {
118
        $new = clone $this;
119
        $new->psr7Worker = $worker;
120
        return $new;
121
    }
122 8
123
    /**
124
     * Returns a new instance with enabled temporal support.
125 8
     */
126 8
    public function withEnabledTemporal(bool $value): self
127
    {
128 8
        if (!$this->isTemporalSDKInstalled()) {
129
            throw new Exception('Temporal SDK is not installed. To install the SDK run `composer require temporal/sdk`.');
130
        }
131
        $new = clone $this;
132 8
        $new->isTemporalEnabled = $value;
133 8
        return $new;
134
    }
135 8
136 8
    /**
137
     * {@inheritDoc}
138 7
     *
139
     * @throws CircularReferenceException|ErrorException|InvalidConfigException|JsonException
140
     * @throws ContainerExceptionInterface|NotFoundException|NotFoundExceptionInterface|NotInstantiableException
141 7
     */
142 7
    public function run(): void
143
    {
144 7
        // Register temporary error handler to catch error while container is building.
145 7
        $temporaryErrorHandler = $this->createTemporaryErrorHandler();
146 7
        $this->registerErrorHandler($temporaryErrorHandler);
147
148 7
        $container = $this->getContainer();
149 7
150
        // Register error handler with real container-configured dependencies.
151
        /** @var ErrorHandler $actualErrorHandler */
152 6
        $actualErrorHandler = $container->get(ErrorHandler::class);
153 1
        $this->registerErrorHandler($actualErrorHandler, $temporaryErrorHandler);
154 1
155 1
        $this->runBootstrap();
156
        $this->checkEvents();
157
158
        $env = Environment::fromGlobals();
159 5
160 4
        if ($env->getMode() === Mode::MODE_TEMPORAL) {
161 1
            if (!$this->isTemporalEnabled) {
162 1
                throw new RuntimeException(
163
                    'Temporal support is disabled. You should call `withEnabledTemporal(true)` to enable temporal support.',
164 5
                );
165
            }
166
            $this->runTemporal($container);
167
            return;
168 7
        }
169
        if ($env->getMode() === Mode::MODE_HTTP) {
170
            $this->runRoadRunner($container);
171 8
            return;
172
        }
173 8
174 1
        throw new RuntimeException(sprintf(
175
            'Unsupported mode "%s", modes are supported: "%s".',
176
            $env->getMode(),
177 7
            implode('", "', [Mode::MODE_HTTP, Mode::MODE_TEMPORAL]),
178 7
        ));
179
    }
180
181
    private function createTemporaryErrorHandler(): ErrorHandler
182
    {
183
        if ($this->temporaryErrorHandler !== null) {
184 8
            return $this->temporaryErrorHandler;
185
        }
186 8
187
        $logger = new Logger([new FileTarget("$this->rootPath/runtime/logs/app.log")]);
188 8
        return new ErrorHandler($logger, new HtmlRenderer());
189 8
    }
190
191
    /**
192 8
     * @throws ErrorException
193
     */
194
    private function registerErrorHandler(ErrorHandler $registered, ErrorHandler $unregistered = null): void
195 6
    {
196
        $unregistered?->unregister();
197
198
        if ($this->debug) {
199
            $registered->debug();
200 6
        }
201
202 6
        $registered->register();
203 6
    }
204 6
205 6
    private function afterRespond(
206
        Application $application,
207
        ContainerInterface $container,
208
        ?ResponseInterface $response,
209
    ): void {
210
        $application->afterEmit($response);
211
        /** @psalm-suppress MixedMethodCall */
212
        $container
213
            ->get(StateResetter::class)
214
            ->reset(); // We should reset the state of such services every request.
215
        gc_collect_cycles();
216
    }
217
218
    private function runRoadRunner(ContainerInterface $container): void
219
    {
220
        $worker = new RoadRunnerHttpWorker($container, $this->psr7Worker);
221
222
        /** @var Application $application */
223
        $application = $container->get(Application::class);
224
        $application->start();
225
226
        while (true) {
227
            $request = $worker->waitRequest();
228
            $response = null;
229
230
            if ($request === null) {
231
                break;
232
            }
233
234
            if ($request instanceof Throwable) {
235
                $response = $worker->respondWithError($request);
236
                $this->afterRespond($application, $container, $response);
237
                continue;
238
            }
239
240
            try {
241
                $response = $application->handle($request);
242
                $worker->respond($response);
243
            } catch (Throwable $t) {
244
                $response = $worker->respondWithError($t, $request);
245
            } finally {
246
                $this->afterRespond($application, $container, $response);
247
            }
248
        }
249
250
        $application->shutdown();
251
    }
252
253
    private function runTemporal(ContainerInterface $container): void
254
    {
255
        /** @var TemporalDeclarationProvider $temporalDeclarationProvider */
256
        $temporalDeclarationProvider = $container->get(TemporalDeclarationProvider::class);
257
258
        /** @var WorkerFactoryInterface $factory */
259
        $factory = $container->get(WorkerFactoryInterface::class);
260
        $worker = $factory->newWorker('default');
261
262
        $workflows = $temporalDeclarationProvider->getWorkflows();
263
        $activities = $temporalDeclarationProvider->getActivities();
264
265
        $worker->registerWorkflowTypes(...$workflows);
266
267
        /** @psalm-suppress MixedReturnStatement,MixedInferredReturnType */
268
        $activityFactory = static fn (\ReflectionClass $class): object => $container->get($class->getName());
269
        $activityFinalizer = static function () use ($container): void {
270
            /** @psalm-suppress MixedMethodCall */
271
            $container
272
                ->get(StateResetter::class)
273
                ->reset(); // We should reset the state of such services every request.
274
            gc_collect_cycles();
275
        };
276
277
        foreach ($activities as $activity) {
278
            $worker->registerActivity($activity, $activityFactory);
279
        }
280
        $worker->registerActivityFinalizer($activityFinalizer);
281
282
        $factory->run();
283
    }
284
285
    private function isTemporalSDKInstalled(): bool
286
    {
287
        return class_exists(WorkerFactoryInterface::class);
288
    }
289
}
290