Passed
Push — master ( 15bf23...a8e716 )
by Alexander
02:13
created

HttpApplicationRunner   A

Complexity

Total Complexity 9

Size/Duplication

Total Lines 116
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 39
c 2
b 0
f 0
dl 0
loc 116
ccs 46
cts 46
cp 1
rs 10
wmc 9

6 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A emit() 0 3 1
A registerErrorHandler() 0 9 2
A createTemporaryErrorHandler() 0 8 2
A withTemporaryErrorHandler() 0 5 1
A run() 0 44 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Yii\Runner\Http;
6
7
use ErrorException;
8
use Psr\Container\ContainerExceptionInterface;
9
use Psr\Container\NotFoundExceptionInterface;
10
use Psr\Http\Message\ResponseInterface;
11
use Psr\Http\Message\ServerRequestInterface;
12
use Throwable;
13
use Yiisoft\Definitions\Exception\CircularReferenceException;
14
use Yiisoft\Definitions\Exception\InvalidConfigException;
15
use Yiisoft\Definitions\Exception\NotInstantiableException;
16
use Yiisoft\Di\NotFoundException;
17
use Yiisoft\ErrorHandler\ErrorHandler;
18
use Yiisoft\ErrorHandler\Middleware\ErrorCatcher;
19
use Yiisoft\ErrorHandler\Renderer\HtmlRenderer;
20
use Yiisoft\Http\Method;
21
use Yiisoft\Log\Logger;
22
use Yiisoft\Log\Target\File\FileTarget;
23
use Yiisoft\Yii\Http\Application;
24
use Yiisoft\Yii\Http\Handler\ThrowableHandler;
25
use Yiisoft\Yii\Runner\ApplicationRunner;
26
use Yiisoft\Yii\Runner\Http\Exception\HeadersHaveBeenSentException;
27
28
use function microtime;
29
30
/**
31
 * `HttpApplicationRunner` runs the Yii HTTP application.
32
 */
33
final class HttpApplicationRunner extends ApplicationRunner
34
{
35
    private ?ErrorHandler $temporaryErrorHandler = 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 5
    public function __construct(string $rootPath, bool $debug, ?string $environment)
43
    {
44 5
        parent::__construct($rootPath, $debug, $environment);
45 5
        $this->bootstrapGroup = 'bootstrap-web';
46 5
        $this->eventsGroup = 'events-web';
47 5
    }
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
     * {@inheritDoc}
68
     *
69
     * @throws CircularReferenceException|ErrorException|HeadersHaveBeenSentException|InvalidConfigException
70
     * @throws ContainerExceptionInterface|NotFoundException|NotFoundExceptionInterface|NotInstantiableException
71
     */
72 4
    public function run(): void
73
    {
74 4
        $startTime = microtime(true);
75
76
        // Register temporary error handler to catch error while container is building.
77 4
        $temporaryErrorHandler = $this->createTemporaryErrorHandler();
78 4
        $this->registerErrorHandler($temporaryErrorHandler);
79
80 4
        $config = $this->getConfig();
81 4
        $container = $this->getContainer($config, 'web');
82
83
        // Register error handler with real container-configured dependencies.
84
        /** @var ErrorHandler $actualErrorHandler */
85 4
        $actualErrorHandler = $container->get(ErrorHandler::class);
86 4
        $this->registerErrorHandler($actualErrorHandler, $temporaryErrorHandler);
87
88 4
        $this->runBootstrap($config, $container);
89 4
        $this->checkEvents($config, $container);
90
91
        /** @var Application $application */
92 4
        $application = $container->get(Application::class);
93
94
        /**
95
         * @var ServerRequestInterface
96
         * @psalm-suppress MixedMethodCall
97
         */
98 4
        $serverRequest = $container->get(ServerRequestFactory::class)->createFromGlobals();
99 4
        $request = $serverRequest->withAttribute('applicationStartTime', $startTime);
100
101
        try {
102 4
            $application->start();
103 4
            $response = $application->handle($request);
104 3
            $this->emit($request, $response);
105 1
        } catch (Throwable $throwable) {
106 1
            $handler = new ThrowableHandler($throwable);
107
            /**
108
             * @var ResponseInterface
109
             * @psalm-suppress MixedMethodCall
110
             */
111 1
            $response = $container->get(ErrorCatcher::class)->process($request, $handler);
112 1
            $this->emit($request, $response);
113 4
        } finally {
114 4
            $application->afterEmit($response ?? null);
115 4
            $application->shutdown();
116
        }
117 4
    }
118
119 4
    private function createTemporaryErrorHandler(): ErrorHandler
120
    {
121 4
        if ($this->temporaryErrorHandler !== null) {
122 1
            return $this->temporaryErrorHandler;
123
        }
124
125 3
        $logger = new Logger([new FileTarget("$this->rootPath/runtime/logs/app.log")]);
126 3
        return new ErrorHandler($logger, new HtmlRenderer());
127
    }
128
129
    /**
130
     * @throws HeadersHaveBeenSentException
131
     */
132 4
    private function emit(ServerRequestInterface $request, ResponseInterface $response): void
133
    {
134 4
        (new SapiEmitter())->emit($response, $request->getMethod() === Method::HEAD);
135 4
    }
136
137
    /**
138
     * @throws ErrorException
139
     */
140 4
    private function registerErrorHandler(ErrorHandler $registered, ErrorHandler $unregistered = null): void
141
    {
142 4
        $unregistered?->unregister();
143
144 4
        if ($this->debug) {
145 4
            $registered->debug();
146
        }
147
148 4
        $registered->register();
149 4
    }
150
}
151