Passed
Push — master ( e068ba...e3765f )
by Alexander
03:47 queued 01:29
created

RoadRunnerApplicationRunner::withoutEvent()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 3
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 5
ccs 0
cts 4
cp 0
crap 2
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Yii\Runner\RoadRunner;
6
7
use Spiral\RoadRunner;
8
use ErrorException;
9
use Psr\Container\ContainerInterface;
10
use Psr\Http\Message\ResponseInterface;
11
use Psr\Http\Message\ServerRequestFactoryInterface;
12
use Psr\Http\Message\StreamFactoryInterface;
13
use Psr\Http\Message\UploadedFileFactoryInterface;
14
use Throwable;
15
use Yiisoft\Config\Config;
16
use Yiisoft\Di\Container;
17
use Yiisoft\Di\StateResetter;
18
use Yiisoft\ErrorHandler\ErrorHandler;
19
use Yiisoft\ErrorHandler\Middleware\ErrorCatcher;
20
use Yiisoft\ErrorHandler\Renderer\PlainTextRenderer;
21
use Yiisoft\Definitions\Exception\CircularReferenceException;
22
use Yiisoft\Definitions\Exception\InvalidConfigException;
23
use Yiisoft\Definitions\Exception\NotFoundException;
24
use Yiisoft\Definitions\Exception\NotInstantiableException;
25
use Yiisoft\Log\Logger;
26
use Yiisoft\Log\Target\File\FileTarget;
27
use Yiisoft\Yii\Event\ListenerConfigurationChecker;
28
use Yiisoft\Yii\Runner\BootstrapRunner;
29
use Yiisoft\Yii\Runner\ConfigFactory;
30
use Yiisoft\Yii\Runner\RunnerInterface;
31
use Yiisoft\Yii\Runner\ThrowableHandler;
32
use Yiisoft\Yii\Web\Application;
33
34
use function gc_collect_cycles;
35
use function microtime;
36
37
final class RoadRunnerApplicationRunner implements RunnerInterface
38
{
39
    private bool $debug;
40
    private string $rootPath;
41
    private ?string $environment;
42
    private ?Config $config = null;
43
    private ?ContainerInterface $container = null;
44
    private ?string $bootstrapGroup = 'bootstrap-web';
45
    private ?string $eventGroup = 'event-web';
46
47
    public function __construct(string $rootPath, bool $debug, ?string $environment)
48
    {
49
        $this->rootPath = $rootPath;
50
        $this->debug = $debug;
51
        $this->environment = $environment;
52
    }
53
54
    public function withBootstrap(string $bootstrapGroup): self
55
    {
56
        $new = clone $this;
57
        $new->bootstrapGroup = $bootstrapGroup;
58
        return $new;
59
    }
60
61
    public function withoutBootstrap(): self
62
    {
63
        $new = clone $this;
64
        $new->bootstrapGroup = null;
65
        return $new;
66
    }
67
68
    public function withEvent(string $eventGroup): self
69
    {
70
        $new = clone $this;
71
        $new->eventGroup = $eventGroup;
72
        return $new;
73
    }
74
75
    public function withoutEvent(): self
76
    {
77
        $new = clone $this;
78
        $new->eventGroup = null;
79
        return $new;
80
    }
81
82
    public function withConfig(Config $config): self
83
    {
84
        $new = clone $this;
85
        $new->config = $config;
86
        return $new;
87
    }
88
89
    public function withContainer(ContainerInterface $container): self
90
    {
91
        $new = clone $this;
92
        $new->container = $container;
93
        return $new;
94
    }
95
96
    /**
97
     * @throws CircularReferenceException|ErrorException|InvalidConfigException
98
     * @throws NotFoundException|NotInstantiableException
99
     */
100
    public function run(): void
101
    {
102
        // Register temporary error handler to catch error while container is building.
103
        $temporaryErrorHandler = $this->createTemporaryErrorHandler();
104
        $this->registerErrorHandler($temporaryErrorHandler);
105
106
        $config = $this->config ?? ConfigFactory::create($this->rootPath, $this->environment);
107
108
        $container = $this->container ?? new Container(
109
            $config->get('web'),
110
            $config->get('providers-web'),
111
            [],
112
            $this->debug,
113
            $config->get('delegates-web')
114
        );
115
116
        // Register error handler with real container-configured dependencies.
117
        /** @var ErrorHandler $actualErrorHandler */
118
        $actualErrorHandler = $container->get(ErrorHandler::class);
119
        $this->registerErrorHandler($actualErrorHandler, $temporaryErrorHandler);
120
121
        if ($container instanceof Container) {
122
            $container = $container->get(ContainerInterface::class);
123
        }
124
125
        // Run bootstrap
126
        if ($this->bootstrapGroup !== null) {
127
            $this->runBootstrap($container, $config->get($this->bootstrapGroup));
128
        }
129
130
        if ($this->debug && $this->eventGroup !== null) {
131
            /** @psalm-suppress MixedMethodCall */
132
            $container->get(ListenerConfigurationChecker::class)->check($config->get($this->eventGroup));
133
        }
134
135
        $worker = RoadRunner\Worker::create();
136
        /** @var ServerRequestFactoryInterface $serverRequestFactory */
137
        $serverRequestFactory = $container->get(ServerRequestFactoryInterface::class);
138
        /** @var StreamFactoryInterface $streamFactory */
139
        $streamFactory = $container->get(StreamFactoryInterface::class);
140
        /** @var UploadedFileFactoryInterface $uploadsFactory */
141
        $uploadsFactory = $container->get(UploadedFileFactoryInterface::class);
142
        $worker = new RoadRunner\Http\PSR7Worker($worker, $serverRequestFactory, $streamFactory, $uploadsFactory);
143
144
        /** @var Application */
145
        $application = $container->get(Application::class);
146
        $application->start();
147
148
        while ($request = $worker->waitRequest()) {
149
            $request = $request->withAttribute('applicationStartTime', microtime(true));
150
            $response = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $response is dead and can be removed.
Loading history...
151
            try {
152
                $response = $application->handle($request);
153
                $worker->respond($response);
154
            } catch (Throwable $t) {
155
                $handler = new ThrowableHandler($t);
156
                /**
157
                 * @var ResponseInterface
158
                 * @psalm-suppress MixedMethodCall
159
                 */
160
                $response = $container->get(ErrorCatcher::class)->process($request, $handler);
161
                $worker->respond($response);
162
            } finally {
163
                $application->afterEmit($response ?? null);
164
                /** @psalm-suppress MixedMethodCall */
165
                $container->get(StateResetter::class)->reset(); // We should reset the state of such services every request.
166
                gc_collect_cycles();
167
            }
168
        }
169
170
        $application->shutdown();
171
    }
172
173
    private function createTemporaryErrorHandler(): ErrorHandler
174
    {
175
        $logger = new Logger([new FileTarget("$this->rootPath/runtime/logs/app.log")]);
176
        return new ErrorHandler($logger, new PlainTextRenderer());
177
    }
178
179
    /**
180
     * @throws ErrorException
181
     */
182
    private function registerErrorHandler(ErrorHandler $registered, ErrorHandler $unregistered = null): void
183
    {
184
        $unregistered?->unregister();
185
186
        if ($this->debug) {
187
            $registered->debug();
188
        }
189
190
        $registered->register();
191
    }
192
193
    private function runBootstrap(ContainerInterface $container, array $bootstrapList): void
194
    {
195
        (new BootstrapRunner($container, $bootstrapList))->run();
196
    }
197
}
198