Test Failed
Pull Request — master (#18)
by Evgeniy
01:57
created

HttpApplicationRunner::withBootstrap()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 3
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 5
ccs 4
cts 4
cp 1
crap 1
rs 10
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\ContainerInterface;
10
use Psr\Container\NotFoundExceptionInterface;
11
use Psr\Http\Message\ResponseInterface;
12
use Psr\Http\Message\ServerRequestInterface;
13
use Throwable;
14
use Yiisoft\Definitions\Exception\CircularReferenceException;
15
use Yiisoft\Definitions\Exception\InvalidConfigException;
16
use Yiisoft\Definitions\Exception\NotInstantiableException;
17
use Yiisoft\Di\Container;
18
use Yiisoft\Di\NotFoundException;
19
use Yiisoft\ErrorHandler\ErrorHandler;
20
use Yiisoft\ErrorHandler\Middleware\ErrorCatcher;
21
use Yiisoft\ErrorHandler\Renderer\HtmlRenderer;
22
use Yiisoft\Http\Method;
23
use Yiisoft\Log\Logger;
24
use Yiisoft\Log\Target\File\FileTarget;
25
use Yiisoft\Yii\Http\Application;
26
use Yiisoft\Yii\Http\Handler\ThrowableHandler;
27
use Yiisoft\Yii\Runner\ApplicationRunner;
0 ignored issues
show
Bug introduced by
The type Yiisoft\Yii\Runner\ApplicationRunner was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
28
use Yiisoft\Yii\Runner\Http\Exception\HeadersHaveBeenSentException;
29
30
use function microtime;
31
32
/**
33
 * `HttpApplicationRunner` runs the Yii HTTP application.
34
 */
35
final class HttpApplicationRunner extends ApplicationRunner
36
{
37
    private ?ErrorHandler $temporaryErrorHandler = null;
38
39
    /**
40
     * @param string $rootPath The absolute path to the project root.
41
     * @param bool $debug Whether the debug mode is enabled.
42
     * @param string|null $environment The environment name.
43
     */
44
    public function __construct(string $rootPath, bool $debug, ?string $environment)
45
    {
46
        parent::__construct($rootPath, $debug, $environment);
47
        $this->bootstrapGroup = 'bootstrap-web';
0 ignored issues
show
Bug Best Practice introduced by
The property bootstrapGroup does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
48
        $this->eventsGroup = 'events-web';
0 ignored issues
show
Bug Best Practice introduced by
The property eventsGroup does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
49
    }
50
51
    /**
52
     * Returns a new instance with the specified temporary error handler instance {@see ErrorHandler}.
53
     *
54
     * A temporary error handler is needed to handle the creation of configuration and container instances,
55
     * then the error handler configured in your application configuration will be used.
56
     *
57 5
     * @param ErrorHandler $temporaryErrorHandler The temporary error handler instance.
58
     *
59 5
     * @return self
60 5
     */
61 5
    public function withTemporaryErrorHandler(ErrorHandler $temporaryErrorHandler): self
62 5
    {
63
        $new = clone $this;
64
        $new->temporaryErrorHandler = $temporaryErrorHandler;
65
        return $new;
66
    }
67
68
    /**
69
     * {@inheritDoc}
70
     *
71 1
     * @throws CircularReferenceException|ErrorException|HeadersHaveBeenSentException|InvalidConfigException
72
     * @throws ContainerExceptionInterface|NotFoundException|NotFoundExceptionInterface|NotInstantiableException
73 1
     */
74 1
    public function run(): void
75 1
    {
76
        $startTime = microtime(true);
77
78
        // Register temporary error handler to catch error while container is building.
79
        $temporaryErrorHandler = $this->createTemporaryErrorHandler();
80
        $this->registerErrorHandler($temporaryErrorHandler);
81
82
        $config = $this->config ?? $this->createConfig();
83 2
        $container = $this->container ?? $this->createContainer($config, 'web');
84
85 2
        // Register error handler with real container-configured dependencies.
86 2
        /** @var ErrorHandler $actualErrorHandler */
87 2
        $actualErrorHandler = $container->get(ErrorHandler::class);
88
        $this->registerErrorHandler($actualErrorHandler, $temporaryErrorHandler);
89
90
        if ($container instanceof Container) {
91
            $container = $container->get(ContainerInterface::class);
92
        }
93
94
        $this->runBootstrap($config, $container);
95
        $this->checkEvents($config, $container);
96
97 1
        /** @var Application $application */
98
        $application = $container->get(Application::class);
99 1
100 1
        /**
101 1
         * @var ServerRequestInterface
102
         * @psalm-suppress MixedMethodCall
103
         */
104
        $serverRequest = $container->get(ServerRequestFactory::class)->createFromGlobals();
105
        $request = $serverRequest->withAttribute('applicationStartTime', $startTime);
106
107
        try {
108
            $application->start();
109 2
            $response = $application->handle($request);
110
            $this->emit($request, $response);
111 2
        } catch (Throwable $throwable) {
112 2
            $handler = new ThrowableHandler($throwable);
113 2
            /**
114
             * @var ResponseInterface
115
             * @psalm-suppress MixedMethodCall
116
             */
117
            $response = $container->get(ErrorCatcher::class)->process($request, $handler);
118
            $this->emit($request, $response);
119
        } finally {
120
            $application->afterEmit($response ?? null);
121
            $application->shutdown();
122
        }
123 2
    }
124
125 2
    private function createTemporaryErrorHandler(): ErrorHandler
126 2
    {
127 2
        if ($this->temporaryErrorHandler !== null) {
128
            return $this->temporaryErrorHandler;
129
        }
130
131
        $logger = new Logger([new FileTarget("$this->rootPath/runtime/logs/app.log")]);
132
        return new ErrorHandler($logger, new HtmlRenderer());
133
    }
134
135
    /**
136
     * @throws HeadersHaveBeenSentException
137 3
     */
138
    private function emit(ServerRequestInterface $request, ResponseInterface $response): void
139 3
    {
140 3
        (new SapiEmitter())->emit($response, $request->getMethod() === Method::HEAD);
141 3
    }
142
143
    /**
144
     * @throws ErrorException
145
     */
146
    private function registerErrorHandler(ErrorHandler $registered, ErrorHandler $unregistered = null): void
147
    {
148
        $unregistered?->unregister();
149
150
        if ($this->debug) {
151
            $registered->debug();
152
        }
153
154 2
        $registered->register();
155
    }
156
}
157