Passed
Push — master ( e3765f...ac273f )
by Alexander
03:56 queued 01:53
created

withTemporaryErrorHandler()   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 1
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 ?ErrorHandler $temporaryErrorHandler = null;
45
    private ?string $bootstrapGroup = 'bootstrap-web';
46
    private ?string $eventGroup = 'event-web';
47
48
    public function __construct(string $rootPath, bool $debug, ?string $environment)
49
    {
50
        $this->rootPath = $rootPath;
51
        $this->debug = $debug;
52
        $this->environment = $environment;
53
    }
54
55
    public function withBootstrap(string $bootstrapGroup): self
56
    {
57
        $new = clone $this;
58
        $new->bootstrapGroup = $bootstrapGroup;
59
        return $new;
60
    }
61
62
    public function withoutBootstrap(): self
63
    {
64
        $new = clone $this;
65
        $new->bootstrapGroup = null;
66
        return $new;
67
    }
68
69
    public function withEvent(string $eventGroup): self
70
    {
71
        $new = clone $this;
72
        $new->eventGroup = $eventGroup;
73
        return $new;
74
    }
75
76
    public function withoutEvent(): self
77
    {
78
        $new = clone $this;
79
        $new->eventGroup = null;
80
        return $new;
81
    }
82
83
    public function withConfig(Config $config): self
84
    {
85
        $new = clone $this;
86
        $new->config = $config;
87
        return $new;
88
    }
89
90
    public function withContainer(ContainerInterface $container): self
91
    {
92
        $new = clone $this;
93
        $new->container = $container;
94
        return $new;
95
    }
96
97
    public function withTemporaryErrorHandler(ErrorHandler $temporaryErrorHandler): self
98
    {
99
        $new = clone $this;
100
        $new->temporaryErrorHandler = $temporaryErrorHandler;
101
        return $new;
102
    }
103
104
    /**
105
     * @throws CircularReferenceException|ErrorException|InvalidConfigException
106
     * @throws NotFoundException|NotInstantiableException
107
     */
108
    public function run(): void
109
    {
110
        // Register temporary error handler to catch error while container is building.
111
        $temporaryErrorHandler = $this->createTemporaryErrorHandler();
112
        $this->registerErrorHandler($temporaryErrorHandler);
113
114
        $config = $this->config ?? ConfigFactory::create($this->rootPath, $this->environment);
115
116
        $container = $this->container ?? new Container(
117
            $config->get('web'),
118
            $config->get('providers-web'),
119
            [],
120
            $this->debug,
121
            $config->get('delegates-web')
122
        );
123
124
        // Register error handler with real container-configured dependencies.
125
        /** @var ErrorHandler $actualErrorHandler */
126
        $actualErrorHandler = $container->get(ErrorHandler::class);
127
        $this->registerErrorHandler($actualErrorHandler, $temporaryErrorHandler);
128
129
        if ($container instanceof Container) {
130
            $container = $container->get(ContainerInterface::class);
131
        }
132
133
        // Run bootstrap
134
        if ($this->bootstrapGroup !== null) {
135
            $this->runBootstrap($container, $config->get($this->bootstrapGroup));
136
        }
137
138
        if ($this->debug && $this->eventGroup !== null) {
139
            /** @psalm-suppress MixedMethodCall */
140
            $container->get(ListenerConfigurationChecker::class)->check($config->get($this->eventGroup));
141
        }
142
143
        $worker = RoadRunner\Worker::create();
144
        /** @var ServerRequestFactoryInterface $serverRequestFactory */
145
        $serverRequestFactory = $container->get(ServerRequestFactoryInterface::class);
146
        /** @var StreamFactoryInterface $streamFactory */
147
        $streamFactory = $container->get(StreamFactoryInterface::class);
148
        /** @var UploadedFileFactoryInterface $uploadsFactory */
149
        $uploadsFactory = $container->get(UploadedFileFactoryInterface::class);
150
        $worker = new RoadRunner\Http\PSR7Worker($worker, $serverRequestFactory, $streamFactory, $uploadsFactory);
151
152
        /** @var Application */
153
        $application = $container->get(Application::class);
154
        $application->start();
155
156
        while ($request = $worker->waitRequest()) {
157
            $request = $request->withAttribute('applicationStartTime', microtime(true));
158
            $response = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $response is dead and can be removed.
Loading history...
159
            try {
160
                $response = $application->handle($request);
161
                $worker->respond($response);
162
            } catch (Throwable $t) {
163
                $handler = new ThrowableHandler($t);
164
                /**
165
                 * @var ResponseInterface
166
                 * @psalm-suppress MixedMethodCall
167
                 */
168
                $response = $container->get(ErrorCatcher::class)->process($request, $handler);
169
                $worker->respond($response);
170
            } finally {
171
                $application->afterEmit($response ?? null);
172
                /** @psalm-suppress MixedMethodCall */
173
                $container->get(StateResetter::class)->reset(); // We should reset the state of such services every request.
174
                gc_collect_cycles();
175
            }
176
        }
177
178
        $application->shutdown();
179
    }
180
181
    private function createTemporaryErrorHandler(): ErrorHandler
182
    {
183
        if ($this->temporaryErrorHandler !== null) {
184
            return $this->temporaryErrorHandler;
185
        }
186
187
        $logger = new Logger([new FileTarget("$this->rootPath/runtime/logs/app.log")]);
188
        return new ErrorHandler($logger, new PlainTextRenderer());
189
    }
190
191
    /**
192
     * @throws ErrorException
193
     */
194
    private function registerErrorHandler(ErrorHandler $registered, ErrorHandler $unregistered = null): void
195
    {
196
        $unregistered?->unregister();
197
198
        if ($this->debug) {
199
            $registered->debug();
200
        }
201
202
        $registered->register();
203
    }
204
205
    private function runBootstrap(ContainerInterface $container, array $bootstrapList): void
206
    {
207
        (new BootstrapRunner($container, $bootstrapList))->run();
208
    }
209
}
210