RoadRunnerHttpApplicationRunner::__construct()   A
last analyzed

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

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

288
        $factory->/** @scrutinizer ignore-call */ 
289
                  run($host);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
289
    }
290
291 2
    private function isTemporalSDKInstalled(): bool
292
    {
293 2
        return interface_exists(WorkerFactoryInterface::class);
294
    }
295
}
296