Passed
Push — master ( 6822a1...a4928c )
by Alexander
22:14 queued 09:07
created

createTemporaryErrorHandler()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 4
ccs 3
cts 3
cp 1
crap 1
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace App\Runner;
6
7
use App\Handler\ThrowableHandler;
8
use ErrorException;
9
use Psr\Container\ContainerInterface;
10
use Psr\Http\Message\RequestInterface;
11
use Psr\Http\Message\ResponseInterface;
12
use Psr\Http\Message\ServerRequestInterface;
13
use Throwable;
14
use Yiisoft\Config\Config;
15
use Yiisoft\Definitions\Exception\CircularReferenceException;
16
use Yiisoft\Definitions\Exception\InvalidConfigException;
17
use Yiisoft\Definitions\Exception\NotFoundException;
18
use Yiisoft\Definitions\Exception\NotInstantiableException;
19
use Yiisoft\Di\Container;
20
use Yiisoft\ErrorHandler\ErrorHandler;
21
use Yiisoft\ErrorHandler\Middleware\ErrorCatcher;
22
use Yiisoft\ErrorHandler\Renderer\HtmlRenderer;
23
use Yiisoft\Http\Method;
24
use Yiisoft\Log\Logger;
25
use Yiisoft\Log\Target\File\FileTarget;
26
use Yiisoft\Yii\Event\ListenerConfigurationChecker;
27
use Yiisoft\Yii\Web\Application;
28
use Yiisoft\Yii\Web\Exception\HeadersHaveBeenSentException;
29
use Yiisoft\Yii\Web\SapiEmitter;
30
use Yiisoft\Yii\Web\ServerRequestFactory;
31
32
use function dirname;
33
use function microtime;
34
35
final class WebApplicationRunner
36
{
37
    private bool $debug = false;
38
39 10
    public function debug(bool $enable = true): void
40
    {
41 10
        $this->debug = $enable;
42 10
    }
43
44
    /**
45
     * @throws CircularReferenceException|ErrorException|HeadersHaveBeenSentException|InvalidConfigException
46
     * @throws NotFoundException|NotInstantiableException|
47
     */
48 10
    public function run(): void
49
    {
50 10
        $startTime = microtime(true);
51
52
        // Register temporary error handler to catch error while container is building.
53 10
        $errorHandler = $this->createTemporaryErrorHandler();
54 10
        $this->registerErrorHandler($errorHandler);
55
56 10
        $config = new Config(
57 10
            dirname(__DIR__, 2),
58 10
            '/config/packages', // Configs path.
59 10
            null,
60
            [
61 10
                'params',
62
                'events',
63
                'events-web',
64
                'events-console',
65
            ],
66
        );
67
68 10
        $container = new Container(
69 10
            $config->get('web'),
70 10
            $config->get('providers-web'),
71 10
            [],
72 10
            $this->debug,
73 10
            $config->get('delegates-web')
74
        );
75
76
        // Register error handler with real container-configured dependencies.
77 10
        $this->registerErrorHandler($container->get(ErrorHandler::class), $errorHandler);
78
79
        // Run bootstrap
80 10
        $this->runBootstrap($container, $config->get('bootstrap-web'));
81
82 10
        $container = $container->get(ContainerInterface::class);
83
84 10
        if ($this->debug) {
85
            /** @psalm-suppress MixedMethodCall */
86 10
            $container->get(ListenerConfigurationChecker::class)->check($config->get('events-web'));
87
        }
88
89
        /** @var Application */
90 10
        $application = $container->get(Application::class);
91
92
        /**
93
         * @var ServerRequestInterface
94
         * @psalm-suppress MixedMethodCall
95
         */
96 10
        $serverRequest = $container->get(ServerRequestFactory::class)->createFromGlobals();
97 10
        $request = $serverRequest->withAttribute('applicationStartTime', $startTime);
98
99
        try {
100 10
            $application->start();
101 10
            $response = $application->handle($request);
102 10
            $this->emit($request, $response);
103
        } catch (Throwable $throwable) {
104
            $handler = new ThrowableHandler($throwable);
105
            /**
106
             * @var ResponseInterface
107
             * @psalm-suppress MixedMethodCall
108
             */
109
            $response = $container->get(ErrorCatcher::class)->process($request, $handler);
110
            $this->emit($request, $response);
111 10
        } finally {
112 10
            $application->afterEmit($response ?? null);
113 10
            $application->shutdown();
114
        }
115 10
    }
116
117 10
    private function createTemporaryErrorHandler(): ErrorHandler
118
    {
119 10
        $logger = new Logger([new FileTarget(dirname(__DIR__) . '/runtime/logs/app.log')]);
120 10
        return new ErrorHandler($logger, new HtmlRenderer());
121
    }
122
123
    /**
124
     * @throws HeadersHaveBeenSentException
125
     */
126 10
    private function emit(RequestInterface $request, ResponseInterface $response): void
127
    {
128 10
        (new SapiEmitter())->emit($response, $request->getMethod() === Method::HEAD);
129 10
    }
130
131
    /**
132
     * @throws ErrorException
133
     */
134 10
    private function registerErrorHandler(ErrorHandler $registered, ErrorHandler $unregistered = null): void
135
    {
136 10
        if ($unregistered !== null) {
137 10
            $unregistered->unregister();
138
        }
139
140 10
        if ($this->debug) {
141 10
            $registered->debug();
142
        }
143
144 10
        $registered->register();
145 10
    }
146
147 10
    private function runBootstrap(Container $container, array $bootstrapList): void
148
    {
149 10
        (new BootstrapRunner($container, $bootstrapList))->run();
150 10
    }
151
}
152