Passed
Push — master ( 0e91c9...6f8172 )
by Alexander
02:51
created

RoadRunnerHttpApplicationRunner::afterRespond()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 5
nc 1
nop 3
dl 0
loc 11
ccs 6
cts 6
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Yii\Runner\RoadRunner;
6
7
use ErrorException;
8
use JsonException;
9
use Psr\Container\ContainerExceptionInterface;
10
use Psr\Container\ContainerInterface;
11
use Psr\Container\NotFoundExceptionInterface;
12
use Psr\Http\Message\ResponseInterface;
13
use Spiral\RoadRunner\Http\PSR7WorkerInterface;
14
use Throwable;
15
use Yiisoft\Definitions\Exception\CircularReferenceException;
16
use Yiisoft\Definitions\Exception\InvalidConfigException;
17
use Yiisoft\Definitions\Exception\NotInstantiableException;
18
use Yiisoft\Di\NotFoundException;
19
use Yiisoft\Di\StateResetter;
20
use Yiisoft\ErrorHandler\ErrorHandler;
21
use Yiisoft\ErrorHandler\Renderer\HtmlRenderer;
22
use Yiisoft\Log\Logger;
23
use Yiisoft\Log\Target\File\FileTarget;
24
use Yiisoft\Yii\Http\Application;
25
use Yiisoft\Yii\Runner\ApplicationRunner;
26
27
use function gc_collect_cycles;
28
29
/**
30
 * `RoadRunnerHttpApplicationRunner` runs the Yii HTTP application using RoadRunner.
31
 */
32
final class RoadRunnerHttpApplicationRunner extends ApplicationRunner
33
{
34
    private ?ErrorHandler $temporaryErrorHandler = null;
35
    private ?PSR7WorkerInterface $psr7Worker = null;
36
37
    /**
38
     * @param string $rootPath The absolute path to the project root.
39
     * @param bool $debug Whether the debug mode is enabled.
40
     * @param bool $checkEvents Whether to check events' configuration.
41
     * @param string|null $environment The environment name.
42
     * @param string $bootstrapGroup The bootstrap configuration group name.
43
     * @param string $eventsGroup The events' configuration group name.
44
     * @param string $diGroup The container definitions' configuration group name.
45
     * @param string $diProvidersGroup The container providers' configuration group name.
46
     * @param string $diDelegatesGroup The container delegates' configuration group name.
47
     * @param string $diTagsGroup The container tags' configuration group name.
48
     * @param string $paramsGroup The configuration parameters group name.
49
     * @param array $nestedParamsGroups Configuration group names that are included into configuration parameters group.
50
     * This is needed for recursive merging of parameters.
51
     * @param array $nestedEventsGroups Configuration group names that are included into events' configuration group.
52
     * This is needed for reverse and recursive merge of events' configurations.
53
     *
54
     * @psalm-param list<string> $nestedParamsGroups
55
     * @psalm-param list<string> $nestedEventsGroups
56
     */
57 9
    public function __construct(
58
        string $rootPath,
59
        bool $debug = false,
60
        bool $checkEvents = false,
61
        ?string $environment = null,
62
        string $bootstrapGroup = 'bootstrap-web',
63
        string $eventsGroup = 'events-web',
64
        string $diGroup = 'di-web',
65
        string $diProvidersGroup = 'di-providers-web',
66
        string $diDelegatesGroup = 'di-delegates-web',
67
        string $diTagsGroup = 'di-tags-web',
68
        string $paramsGroup = 'params-web',
69
        array $nestedParamsGroups = ['params'],
70
        array $nestedEventsGroups = ['events'],
71
    ) {
72 9
        parent::__construct(
73 9
            $rootPath,
74 9
            $debug,
75 9
            $checkEvents,
76 9
            $environment,
77 9
            $bootstrapGroup,
78 9
            $eventsGroup,
79 9
            $diGroup,
80 9
            $diProvidersGroup,
81 9
            $diDelegatesGroup,
82 9
            $diTagsGroup,
83 9
            $paramsGroup,
84 9
            $nestedParamsGroups,
85 9
            $nestedEventsGroups,
86 9
        );
87
    }
88
89
    /**
90
     * Returns a new instance with the specified temporary error handler instance {@see ErrorHandler}.
91
     *
92
     * A temporary error handler is needed to handle the creation of configuration and container instances,
93
     * then the error handler configured in your application configuration will be used.
94
     *
95
     * @param ErrorHandler $temporaryErrorHandler The temporary error handler instance.
96
     */
97 2
    public function withTemporaryErrorHandler(ErrorHandler $temporaryErrorHandler): self
98
    {
99 2
        $new = clone $this;
100 2
        $new->temporaryErrorHandler = $temporaryErrorHandler;
101 2
        return $new;
102
    }
103
104
    /**
105
     * Returns a new instance with the specified PSR-7 worker instance {@see PSR7WorkerInterface}.
106
     *
107
     * @param PSR7WorkerInterface $worker The PSR-7 worker instance.
108
     */
109 9
    public function withPsr7Worker(PSR7WorkerInterface $worker): self
110
    {
111 9
        $new = clone $this;
112 9
        $new->psr7Worker = $worker;
113 9
        return $new;
114
    }
115
116
    /**
117
     * {@inheritDoc}
118
     *
119
     * @throws CircularReferenceException|ErrorException|InvalidConfigException|JsonException
120
     * @throws ContainerExceptionInterface|NotFoundException|NotFoundExceptionInterface|NotInstantiableException
121
     */
122 8
    public function run(): void
123
    {
124
        // Register temporary error handler to catch error while container is building.
125 8
        $temporaryErrorHandler = $this->createTemporaryErrorHandler();
126 8
        $this->registerErrorHandler($temporaryErrorHandler);
127
128 8
        $container = $this->getContainer();
129
130
        // Register error handler with real container-configured dependencies.
131
        /** @var ErrorHandler $actualErrorHandler */
132 8
        $actualErrorHandler = $container->get(ErrorHandler::class);
133 8
        $this->registerErrorHandler($actualErrorHandler, $temporaryErrorHandler);
134
135 8
        $this->runBootstrap();
136 8
        $this->checkEvents();
137
138 7
        $worker = new RoadRunnerWorker($container, $this->psr7Worker);
139
140
        /** @var Application $application */
141 7
        $application = $container->get(Application::class);
142 7
        $application->start();
143
144 7
        while (true) {
145 7
            $request = $worker->waitRequest();
146 7
            $response = null;
147
148 7
            if ($request === null) {
149 7
                break;
150
            }
151
152 6
            if ($request instanceof Throwable) {
153 1
                $response = $worker->respondWithError($request);
154 1
                $this->afterRespond($application, $container, $response);
155 1
                continue;
156
            }
157
158
            try {
159 5
                $response = $application->handle($request);
160 4
                $worker->respond($response);
161 1
            } catch (Throwable $t) {
162 1
                $response = $worker->respondWithError($t, $request);
163
            } finally {
164 5
                $this->afterRespond($application, $container, $response);
165
            }
166
        }
167
168 7
        $application->shutdown();
169
    }
170
171 8
    private function createTemporaryErrorHandler(): ErrorHandler
172
    {
173 8
        if ($this->temporaryErrorHandler !== null) {
174 1
            return $this->temporaryErrorHandler;
175
        }
176
177 7
        $logger = new Logger([new FileTarget("$this->rootPath/runtime/logs/app.log")]);
178 7
        return new ErrorHandler($logger, new HtmlRenderer());
179
    }
180
181
    /**
182
     * @throws ErrorException
183
     */
184 8
    private function registerErrorHandler(ErrorHandler $registered, ErrorHandler $unregistered = null): void
185
    {
186 8
        $unregistered?->unregister();
187
188 8
        if ($this->debug) {
189 8
            $registered->debug();
190
        }
191
192 8
        $registered->register();
193
    }
194
195 6
    private function afterRespond(
196
        Application $application,
197
        ContainerInterface $container,
198
        ?ResponseInterface $response,
199
    ): void {
200 6
        $application->afterEmit($response);
201
        /** @psalm-suppress MixedMethodCall */
202 6
        $container
203 6
            ->get(StateResetter::class)
204 6
            ->reset(); // We should reset the state of such services every request.
205 6
        gc_collect_cycles();
206
    }
207
}
208