Passed
Pull Request — master (#1)
by Evgeniy
01:54
created

WebApplicationRunner::run()   B

Complexity

Conditions 6
Paths 128

Size

Total Lines 60
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
eloc 30
c 1
b 0
f 0
nc 128
nop 0
dl 0
loc 60
ccs 0
cts 27
cp 0
crap 42
rs 8.6311

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Yii\Runner\Web;
6
7
use ErrorException;
8
use Psr\Container\ContainerInterface;
9
use Psr\Http\Message\RequestInterface;
10
use Psr\Http\Message\ResponseInterface;
11
use Psr\Http\Message\ServerRequestInterface;
12
use Throwable;
13
use Yiisoft\Config\Config;
14
use Yiisoft\Di\Container;
15
use Yiisoft\ErrorHandler\ErrorHandler;
16
use Yiisoft\ErrorHandler\Middleware\ErrorCatcher;
17
use Yiisoft\ErrorHandler\Renderer\HtmlRenderer;
18
use Yiisoft\Definitions\Exception\CircularReferenceException;
19
use Yiisoft\Definitions\Exception\InvalidConfigException;
20
use Yiisoft\Definitions\Exception\NotFoundException;
21
use Yiisoft\Definitions\Exception\NotInstantiableException;
22
use Yiisoft\Http\Method;
23
use Yiisoft\Log\Logger;
24
use Yiisoft\Log\Target\File\FileTarget;
25
use Yiisoft\Yii\Event\ListenerConfigurationChecker;
26
use Yiisoft\Yii\Runner\BootstrapRunner;
27
use Yiisoft\Yii\Runner\ConfigFactory;
28
use Yiisoft\Yii\Runner\RunnerInterface;
29
use Yiisoft\Yii\Runner\ThrowableHandler;
30
use Yiisoft\Yii\Web\Application;
31
use Yiisoft\Yii\Web\Exception\HeadersHaveBeenSentException;
32
use Yiisoft\Yii\Web\SapiEmitter;
33
use Yiisoft\Yii\Web\ServerRequestFactory;
34
35
use function microtime;
36
37
final class WebApplicationRunner 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 ?string $bootstrapGroup = 'bootstrap-web';
45
    private ?string $eventGroup = 'event-web';
46
47
    public function __construct(string $rootPath, bool $debug, ?string $environment)
48
    {
49
        $this->rootPath = $rootPath;
50
        $this->debug = $debug;
51
        $this->environment = $environment;
52
    }
53
54
    public function withBootstrap(string $bootstrapGroup): self
55
    {
56
        $new = clone $this;
57
        $new->bootstrapGroup = $bootstrapGroup;
58
        return $new;
59
    }
60
61
    public function withoutBootstrap(): self
62
    {
63
        $new = clone $this;
64
        $new->bootstrapGroup = null;
65
        return $new;
66
    }
67
68
    public function withEvent(string $eventGroup): self
69
    {
70
        $new = clone $this;
71
        $new->eventGroup = $eventGroup;
72
        return $new;
73
    }
74
75
    public function withoutEvent(): self
76
    {
77
        $new = clone $this;
78
        $new->eventGroup = null;
79
        return $new;
80
    }
81
82
    public function withConfig(Config $config): self
83
    {
84
        $new = clone $this;
85
        $new->config = $config;
86
        return $new;
87
    }
88
89
    public function withContainer(ContainerInterface $container): self
90
    {
91
        $new = clone $this;
92
        $new->container = $container;
93
        return $new;
94
    }
95
96
    /**
97
     * @throws CircularReferenceException|ErrorException|HeadersHaveBeenSentException|InvalidConfigException
98
     * @throws NotFoundException|NotInstantiableException|
99
     */
100
    public function run(): void
101
    {
102
        $startTime = microtime(true);
103
104
        // Register temporary error handler to catch error while container is building.
105
        $errorHandler = $this->createTemporaryErrorHandler();
106
        $this->registerErrorHandler($errorHandler);
107
108
        $config = $this->config ?? ConfigFactory::create($this->rootPath, $this->environment);
109
110
        $container = $this->container ?? new Container(
111
            $config->get('web'),
112
            $config->get('providers-web'),
113
            [],
114
            $this->debug,
115
            $config->get('delegates-web')
116
        );
117
118
        // Register error handler with real container-configured dependencies.
119
        $this->registerErrorHandler($container->get(ErrorHandler::class), $errorHandler);
120
121
        // Run bootstrap
122
        if ($this->bootstrapGroup !== null) {
123
            $this->runBootstrap($container, $config->get($this->bootstrapGroup));
124
        }
125
126
        if ($container instanceof Container) {
127
            $container = $container->get(ContainerInterface::class);
128
        }
129
130
        if ($this->debug && $this->eventGroup !== null) {
131
            /** @psalm-suppress MixedMethodCall */
132
            $container->get(ListenerConfigurationChecker::class)->check($config->get($this->eventGroup));
133
        }
134
135
        /** @var Application */
136
        $application = $container->get(Application::class);
137
138
        /**
139
         * @var ServerRequestInterface
140
         * @psalm-suppress MixedMethodCall
141
         */
142
        $serverRequest = $container->get(ServerRequestFactory::class)->createFromGlobals();
143
        $request = $serverRequest->withAttribute('applicationStartTime', $startTime);
144
145
        try {
146
            $application->start();
147
            $response = $application->handle($request);
148
            $this->emit($request, $response);
149
        } catch (Throwable $throwable) {
150
            $handler = new ThrowableHandler($throwable);
151
            /**
152
             * @var ResponseInterface
153
             * @psalm-suppress MixedMethodCall
154
             */
155
            $response = $container->get(ErrorCatcher::class)->process($request, $handler);
156
            $this->emit($request, $response);
157
        } finally {
158
            $application->afterEmit($response ?? null);
159
            $application->shutdown();
160
        }
161
    }
162
163
    private function createTemporaryErrorHandler(): ErrorHandler
164
    {
165
        $logger = new Logger([new FileTarget("$this->rootPath/runtime/logs/app.log")]);
166
        return new ErrorHandler($logger, new HtmlRenderer());
167
    }
168
169
    /**
170
     * @throws HeadersHaveBeenSentException
171
     */
172
    private function emit(RequestInterface $request, ResponseInterface $response): void
173
    {
174
        (new SapiEmitter())->emit($response, $request->getMethod() === Method::HEAD);
175
    }
176
177
    /**
178
     * @throws ErrorException
179
     */
180
    private function registerErrorHandler(ErrorHandler $registered, ErrorHandler $unregistered = null): void
181
    {
182
        if ($unregistered !== null) {
183
            $unregistered->unregister();
184
        }
185
186
        if ($this->debug) {
187
            $registered->debug();
188
        }
189
190
        $registered->register();
191
    }
192
193
    private function runBootstrap(Container $container, array $bootstrapList): void
194
    {
195
        (new BootstrapRunner($container, $bootstrapList))->run();
196
    }
197
}
198