Passed
Push — master ( 219199...627bc7 )
by Sergei
04:09 queued 01:41
created

TestApplicationRunner   A

Complexity

Total Complexity 11

Size/Duplication

Total Lines 184
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 4
Bugs 0 Features 0
Metric Value
eloc 71
c 4
b 0
f 0
dl 0
loc 184
ccs 0
cts 82
cp 0
rs 10
wmc 11

6 Methods

Rating   Name   Duplication   Size   Complexity  
A addProviders() 0 3 1
A withRequest() 0 23 1
A createContainer() 0 30 5
A __construct() 0 30 1
A run() 0 39 2
A preloadContainer() 0 11 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Yii\Testing;
6
7
use Psr\Container\ContainerInterface;
8
use Psr\Http\Message\ResponseInterface;
9
use Psr\Http\Message\ServerRequestInterface;
10
use Throwable;
11
use Yiisoft\Config\ConfigInterface;
12
use Yiisoft\Di\Container;
13
use Yiisoft\Di\ContainerConfig;
14
use Yiisoft\Di\ServiceProviderInterface;
15
use Yiisoft\ErrorHandler\Middleware\ErrorCatcher;
16
use Yiisoft\Yii\Http\Application;
17
use Yiisoft\Yii\Http\Handler\ThrowableHandler;
18
use Yiisoft\Yii\Runner\ApplicationRunner;
19
use Yiisoft\Yii\Runner\Http\ServerRequestFactory;
20
21
final class TestApplicationRunner extends ApplicationRunner
22
{
23
    private array $requestParameters = [];
24
    public ?ContainerInterface $container = null;
25
    /**
26
     * @var ServiceProviderInterface[]
27
     */
28
    private array $providers = [];
29
30
    /**
31
     * @param string $rootPath The absolute path to the project root.
32
     * @param bool $debug Whether the debug mode is enabled.
33
     * @param bool $checkEvents Whether to check events' configuration.
34
     * @param string|null $environment The environment name.
35
     * @param string $bootstrapGroup The bootstrap configuration group name.
36
     * @param string $eventsGroup The events' configuration group name.
37
     * @param string $diGroup The container definitions' configuration group name.
38
     * @param string $diProvidersGroup The container providers' configuration group name.
39
     * @param string $diDelegatesGroup The container delegates' configuration group name.
40
     * @param string $diTagsGroup The container tags' configuration group name.
41
     * @param string $paramsGroup The configuration parameters group name.
42
     * @param array $nestedParamsGroups Configuration group names that included into configuration parameters group.
43
     * This is needed for recursive merging of parameters.
44
     * @param array $nestedEventsGroups Configuration group names that included into events' configuration group. This
45
     * is needed for reverse and recursive merge of events' configurations.
46
     *
47
     * @psalm-param list<string> $nestedParamsGroups
48
     * @psalm-param list<string> $nestedEventsGroups
49
     */
50
    public function __construct(
51
        public ResponseGrabber $responseGrabber,
52
        string $rootPath,
53
        bool $debug = false,
54
        bool $checkEvents = false,
55
        ?string $environment = null,
56
        string $bootstrapGroup = 'bootstrap-web',
57
        string $eventsGroup = 'events-web',
58
        string $diGroup = 'di-web',
59
        string $diProvidersGroup = 'di-providers-web',
60
        string $diDelegatesGroup = 'di-delegates-web',
61
        string $diTagsGroup = 'di-tags-web',
62
        string $paramsGroup = 'params-web',
63
        array $nestedParamsGroups = ['params'],
64
        array $nestedEventsGroups = ['events'],
65
    ) {
66
        parent::__construct(
67
            $rootPath,
68
            $debug,
69
            $checkEvents,
70
            $environment,
71
            $bootstrapGroup,
72
            $eventsGroup,
73
            $diGroup,
74
            $diProvidersGroup,
75
            $diDelegatesGroup,
76
            $diTagsGroup,
77
            $paramsGroup,
78
            $nestedParamsGroups,
79
            $nestedEventsGroups,
80
        );
81
    }
82
83
    /**
84
     * {@inheritDoc}
85
     */
86
    public function run(): void
87
    {
88
        $this->preloadContainer();
89
90
        /** @var ContainerInterface $container */
91
        $container = $this->container;
92
93
        /** @var Application $application */
94
        $application = $container->get(Application::class);
95
96
        /**
97
         * @var ServerRequestInterface
98
         * @psalm-suppress MixedMethodCall
99
         */
100
        $serverRequest = $container
101
            ->get(ServerRequestFactory::class)
102
            ->createFromParameters(
103
                ...$this->requestParameters,
104
            );
105
106
        /**
107
         * @var ResponseInterface|null $response
108
         */
109
        $response = null;
110
        try {
111
            $application->start();
112
            $response = $application->handle($serverRequest);
113
        } catch (Throwable $throwable) {
114
            $handler = new ThrowableHandler($throwable);
115
            /**
116
             * @psalm-suppress MixedMethodCall
117
             */
118
            $response = $container
119
                ->get(ErrorCatcher::class)
120
                ->process($serverRequest, $handler);
121
        } finally {
122
            $application->afterEmit($response ?? null);
123
            $application->shutdown();
124
            $this->responseGrabber->setResponse($response);
125
        }
126
    }
127
128
    public function withRequest(
129
        string $method,
130
        string $url,
131
        array $queryParams = [],
132
        array $postParams = [],
133
        mixed $body = null,
134
        array $headers = [],
135
        array $cookies = [],
136
        array $files = [],
137
    ): void {
138
        $this->requestParameters = [
139
            'server' => [
140
                'SCRIPT_NAME' => '/index.php',
141
                'REQUEST_METHOD' => $method,
142
                'SERVER_PROTOCOL' => '1.1',
143
                'REQUEST_URI' => $url,
144
            ],
145
            'headers' => $headers,
146
            'cookies' => $cookies,
147
            'get' => $queryParams,
148
            'post' => $postParams,
149
            'files' => $files,
150
            'body' => $body,
151
        ];
152
    }
153
154
    public function preloadContainer(): void
155
    {
156
        /**
157
         * @psalm-suppress UnresolvableInclude
158
         */
159
        require_once $this->rootPath . '/autoload.php';
160
161
        $this->container = $this->createContainer();
162
163
        $this->runBootstrap();
164
        $this->checkEvents();
165
    }
166
167
    /**
168
     * @param ServiceProviderInterface[] $providers
169
     */
170
    public function addProviders(array $providers): void
171
    {
172
        $this->providers = array_merge($this->providers, $providers);
173
    }
174
175
    private function createContainer(): Container
176
    {
177
        $containerConfig = ContainerConfig::create()->withValidate($this->debug);
178
179
        $config = $this->getConfig();
180
181
        if (null !== $definitions = $this->getConfiguration($this->diGroup)) {
182
            $containerConfig = $containerConfig->withDefinitions($definitions);
183
        }
184
185
        if (null !== $providers = $this->getConfiguration($this->diProvidersGroup)) {
186
            $providers = array_merge($providers, $this->providers);
187
        } else {
188
            $providers = $this->providers;
189
        }
190
        $containerConfig = $containerConfig->withProviders($providers);
191
192
        if (null !== $delegates = $this->getConfiguration($this->diDelegatesGroup)) {
193
            $containerConfig = $containerConfig->withDelegates($delegates);
194
        }
195
196
        if (null !== $tags = $this->getConfiguration($this->diTagsGroup)) {
197
            $containerConfig = $containerConfig->withTags($tags);
198
        }
199
200
        $containerConfig = $containerConfig->withDefinitions(
201
            array_merge($containerConfig->getDefinitions(), [ConfigInterface::class => $config])
202
        );
203
204
        return new Container($containerConfig);
205
    }
206
}
207