Passed
Pull Request — master (#3038)
by Alan
04:13
created

Client::extractHeaders()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 8
c 1
b 0
f 0
nc 4
nop 1
dl 0
loc 16
rs 10
1
<?php
2
3
/*
4
 * This file is part of the API Platform project.
5
 *
6
 * (c) Kévin Dunglas <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
declare(strict_types=1);
13
14
namespace ApiPlatform\Core\Bridge\Symfony\Bundle\Test;
15
16
use Symfony\Bundle\FrameworkBundle\KernelBrowser;
17
use Symfony\Component\DependencyInjection\ContainerInterface;
18
use Symfony\Component\HttpClient\HttpClientTrait;
19
use Symfony\Component\HttpKernel\KernelInterface;
20
use Symfony\Component\HttpKernel\Profiler\Profile;
21
use Symfony\Contracts\HttpClient\HttpClientInterface;
22
use Symfony\Contracts\HttpClient\ResponseInterface;
23
use Symfony\Contracts\HttpClient\ResponseStreamInterface;
24
25
/**
26
 * Convenient test client that makes requests to a Kernel object.
27
 *
28
 * @experimental
29
 *
30
 * @author Kévin Dunglas <[email protected]>
31
 */
32
final class Client implements HttpClientInterface
33
{
34
    use HttpClientTrait;
35
36
    /**
37
     * @see HttpClientInterface::OPTIONS_DEFAULTS
38
     */
39
    public const API_OPTIONS_DEFAULTS = [
40
        'auth_basic' => null,
41
        'auth_bearer' => null,
42
        'query' => [],
43
        'headers' => ['accept' => ['application/ld+json']],
44
        'body' => '',
45
        'json' => null,
46
        'base_uri' => 'http://example.com',
47
    ];
48
49
    private $kernelBrowser;
50
51
    private $defaultOptions = self::API_OPTIONS_DEFAULTS;
52
53
    /**
54
     * @var Response
55
     */
56
    private $response;
57
58
    /**
59
     * @param array $defaultOptions Default options for the requests
60
     *
61
     * @see HttpClientInterface::OPTIONS_DEFAULTS for available options
62
     */
63
    public function __construct(KernelBrowser $kernelBrowser, array $defaultOptions = [])
64
    {
65
        $this->kernelBrowser = $kernelBrowser;
66
        $kernelBrowser->followRedirects(false);
67
        if ($defaultOptions) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $defaultOptions of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
68
            $this->setDefaultOptions($defaultOptions);
69
        }
70
    }
71
72
    /**
73
     * Sets the default options for the requests.
74
     *
75
     * @see HttpClientInterface::OPTIONS_DEFAULTS for available options
76
     */
77
    public function setDefaultOptions(array $defaultOptions): void
78
    {
79
        [, $this->defaultOptions] = self::prepareRequest(null, null, $defaultOptions, self::API_OPTIONS_DEFAULTS);
80
    }
81
82
    /**
83
     * {@inheritdoc}
84
     *
85
     * @return Response
86
     */
87
    public function request(string $method, string $url, array $options = []): ResponseInterface
88
    {
89
        $basic = $options['auth_basic'] ?? null;
90
        [$url, $options] = self::prepareRequest($method, $url, $options, $this->defaultOptions);
91
        $resolvedUrl = implode('', $url);
92
93
        /** @var array<string, string[]> $headers */
94
        $headers = self::extractHeaders($options);
95
96
        $server = [];
97
        // Convert headers to a $_SERVER-like array
98
        foreach ($headers as $key => $value) {
99
            if ('content-type' === $key) {
100
                $server['CONTENT_TYPE'] = $value[0] ?? '';
101
102
                continue;
103
            }
104
105
            // BrowserKit doesn't support setting several headers with the same name
106
            $server['HTTP_'.strtoupper(str_replace('-', '_', $key))] = $value[0] ?? '';
107
        }
108
109
        if ($basic) {
110
            $credentials = \is_array($basic) ? $basic : explode(':', $basic, 2);
111
            $server['PHP_AUTH_USER'] = $credentials[0];
112
            $server['PHP_AUTH_PW'] = $credentials[1] ?? '';
113
        }
114
115
        $info = [
116
            'response_headers' => [],
117
            'redirect_count' => 0,
118
            'redirect_url' => null,
119
            'start_time' => 0.0,
120
            'http_method' => $method,
121
            'http_code' => 0,
122
            'error' => null,
123
            'user_data' => $options['user_data'] ?? null,
124
            'url' => $resolvedUrl,
125
            'primary_port' => 'http:' === $url['scheme'] ? 80 : 443,
126
        ];
127
        $this->kernelBrowser->request($method, $resolvedUrl, [], [], $server, $options['body'] ?? null);
128
129
        return $this->response = new Response($this->kernelBrowser->getResponse(), $this->kernelBrowser->getInternalResponse(), $info);
130
    }
131
132
    /**
133
     * {@inheritdoc}
134
     */
135
    public function stream($responses, float $timeout = null): ResponseStreamInterface
136
    {
137
        throw new \LogicException('Not implemented yet');
138
    }
139
140
    /**
141
     * Gets the latest response.
142
     *
143
     * @internal
144
     */
145
    public function getResponse(): ?Response
146
    {
147
        return $this->response;
148
    }
149
150
    /**
151
     * Gets the underlying test client.
152
     *
153
     * @internal
154
     */
155
    public function getKernelBrowser(): KernelBrowser
156
    {
157
        return $this->kernelBrowser;
158
    }
159
160
    // The following methods are proxy methods for KernelBrowser's ones
161
162
    /**
163
     * Returns the container.
164
     *
165
     * @return ContainerInterface|null Returns null when the Kernel has been shutdown or not started yet
166
     */
167
    public function getContainer(): ?ContainerInterface
168
    {
169
        return $this->kernelBrowser->getContainer();
170
    }
171
172
    /**
173
     * Returns the kernel.
174
     */
175
    public function getKernel(): KernelInterface
176
    {
177
        return $this->kernelBrowser->getKernel();
178
    }
179
180
    /**
181
     * Gets the profile associated with the current Response.
182
     *
183
     * @return Profile|false A Profile instance
184
     */
185
    public function getProfile()
186
    {
187
        return $this->kernelBrowser->getProfile();
188
    }
189
190
    /**
191
     * Enables the profiler for the very next request.
192
     *
193
     * If the profiler is not enabled, the call to this method does nothing.
194
     */
195
    public function enableProfiler(): void
196
    {
197
        $this->kernelBrowser->enableProfiler();
198
    }
199
200
    /**
201
     * Disables kernel reboot between requests.
202
     *
203
     * By default, the Client reboots the Kernel for each request. This method
204
     * allows to keep the same kernel across requests.
205
     */
206
    public function disableReboot(): void
207
    {
208
        $this->kernelBrowser->disableReboot();
209
    }
210
211
    /**
212
     * Enables kernel reboot between requests.
213
     */
214
    public function enableReboot(): void
215
    {
216
        $this->kernelBrowser->enableReboot();
217
    }
218
219
    /**
220
     * Extracts headers depending on the symfony/http-client version being used.
221
     */
222
    private static function extractHeaders(array $options): array
223
    {
224
        if (!isset($options['normalized_headers'])) {
225
            return $options['headers'];
226
        }
227
228
        $headers = [];
229
230
        foreach ($options['normalized_headers'] as $key => $values) {
231
            foreach ($values as $value) {
232
                [, $value] = explode(': ', $value, 2);
233
                $headers[$key][] = $value;
234
            }
235
        }
236
237
        return $headers;
238
    }
239
}
240