Passed
Push — master ( 2a2245...4f3a31 )
by Evgeniy
02:13
created

RoadRunnerApplicationRunner::run()   A

Complexity

Conditions 5
Paths 2

Size

Total Lines 48
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 28
CRAP Score 5

Importance

Changes 6
Bugs 0 Features 0
Metric Value
cc 5
eloc 28
c 6
b 0
f 0
nc 2
nop 0
dl 0
loc 48
ccs 28
cts 28
cp 1
crap 5
rs 9.1608
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
 * `RoadRunnerApplicationRunner` runs the Yii HTTP application for RoadRunner.
31
 */
32
final class RoadRunnerApplicationRunner 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 string|null $environment The environment name.
41
     */
42 9
    public function __construct(string $rootPath, bool $debug, ?string $environment)
43
    {
44 9
        parent::__construct($rootPath, $debug, $environment);
45 9
        $this->bootstrapGroup = 'bootstrap-web';
46 9
        $this->eventsGroup = 'events-web';
47 9
    }
48
49
    /**
50
     * Returns a new instance with the specified temporary error handler instance {@see ErrorHandler}.
51
     *
52
     * A temporary error handler is needed to handle the creation of configuration and container instances,
53
     * then the error handler configured in your application configuration will be used.
54
     *
55
     * @param ErrorHandler $temporaryErrorHandler The temporary error handler instance.
56
     *
57
     * @return self
58
     */
59 2
    public function withTemporaryErrorHandler(ErrorHandler $temporaryErrorHandler): self
60
    {
61 2
        $new = clone $this;
62 2
        $new->temporaryErrorHandler = $temporaryErrorHandler;
63 2
        return $new;
64
    }
65
66
    /**
67
     * Returns a new instance with the specified PSR-7 worker instance {@see PSR7WorkerInterface}.
68
     *
69
     * @param PSR7WorkerInterface $worker The PSR-7 worker instance.
70
     *
71
     * @return self
72
     */
73 9
    public function withPsr7Worker(PSR7WorkerInterface $worker): self
74
    {
75 9
        $new = clone $this;
76 9
        $new->psr7Worker = $worker;
77 9
        return $new;
78
    }
79
80
    /**
81
     * {@inheritDoc}
82
     *
83
     * @throws CircularReferenceException|ErrorException|InvalidConfigException|JsonException
84
     * @throws ContainerExceptionInterface|NotFoundException|NotFoundExceptionInterface|NotInstantiableException
85
     */
86 8
    public function run(): void
87
    {
88
        // Register temporary error handler to catch error while container is building.
89 8
        $temporaryErrorHandler = $this->createTemporaryErrorHandler();
90 8
        $this->registerErrorHandler($temporaryErrorHandler);
91
92 8
        $config = $this->getConfig();
93 8
        $container = $this->getContainer($config, 'web');
94
95
        // Register error handler with real container-configured dependencies.
96
        /** @var ErrorHandler $actualErrorHandler */
97 8
        $actualErrorHandler = $container->get(ErrorHandler::class);
98 8
        $this->registerErrorHandler($actualErrorHandler, $temporaryErrorHandler);
99
100 8
        $this->runBootstrap($config, $container);
101 8
        $this->checkEvents($config, $container);
102
103 7
        $worker = new RoadRunnerWorker($container, $this->psr7Worker);
104
105
        /** @var Application $application */
106 7
        $application = $container->get(Application::class);
107 7
        $application->start();
108
109 7
        while (true) {
110 7
            $request = $worker->waitRequest();
111 7
            $response = null;
112
113 7
            if ($request === null) {
114 7
                break;
115
            }
116
117 6
            if ($request instanceof Throwable) {
118 1
                $response = $worker->respondWithError($request);
119 1
                $this->afterRespond($application, $container, $response);
120 1
                continue;
121
            }
122
123
            try {
124 5
                $response = $application->handle($request);
125 4
                $worker->respond($response);
126 1
            } catch (Throwable $t) {
127 1
                $response = $worker->respondWithError($t, $request);
128 5
            } finally {
129 5
                $this->afterRespond($application, $container, $response);
130
            }
131
        }
132
133 7
        $application->shutdown();
134 7
    }
135
136 8
    private function createTemporaryErrorHandler(): ErrorHandler
137
    {
138 8
        if ($this->temporaryErrorHandler !== null) {
139 1
            return $this->temporaryErrorHandler;
140
        }
141
142 7
        $logger = new Logger([new FileTarget("$this->rootPath/runtime/logs/app.log")]);
143 7
        return new ErrorHandler($logger, new HtmlRenderer());
144
    }
145
146
    /**
147
     * @throws ErrorException
148
     */
149 8
    private function registerErrorHandler(ErrorHandler $registered, ErrorHandler $unregistered = null): void
150
    {
151 8
        $unregistered?->unregister();
152
153 8
        if ($this->debug) {
154 8
            $registered->debug();
155
        }
156
157 8
        $registered->register();
158 8
    }
159
160 6
    private function afterRespond(
161
        Application $application,
162
        ContainerInterface $container,
163
        ?ResponseInterface $response,
164
    ): void {
165 6
        $application->afterEmit($response);
166
        /** @psalm-suppress MixedMethodCall */
167 6
        $container->get(StateResetter::class)->reset(); // We should reset the state of such services every request.
168 6
        gc_collect_cycles();
169 6
    }
170
}
171