ResponseBuilder::getMethodFromRequest()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
1
<?php
2
3
namespace Swis\Http\Fixture;
4
5
use Http\Discovery\Psr17FactoryDiscovery;
6
use Psr\Http\Message\RequestInterface;
7
use Psr\Http\Message\ResponseFactoryInterface;
8
use Psr\Http\Message\ResponseInterface;
9
use Psr\Http\Message\StreamFactoryInterface;
10
use Psr\Http\Message\StreamInterface;
11
use Symfony\Component\String\UnicodeString;
12
13
class ResponseBuilder implements ResponseBuilderInterface
14
{
15
    /**
16
     * @var string
17
     */
18
    private const TYPE_BODY = 'mock';
19
20
    /**
21
     * @var string
22
     */
23
    private const TYPE_HEADERS = 'headers';
24
25
    /**
26
     * @var string
27
     */
28
    private const TYPE_STATUS = 'status';
29
30
    /**
31
     * @var string
32
     */
33
    private $fixturesPath;
34
35
    /**
36
     * @var \Psr\Http\Message\ResponseFactoryInterface
37
     */
38
    private $responseFactory;
39
40
    /**
41
     * @var \Psr\Http\Message\StreamFactoryInterface
42
     */
43
    private $streamFactory;
44
45
    /**
46
     * @var array
47
     */
48
    private $domainAliases = [];
49
50
    /**
51
     * @var array
52
     */
53
    private $ignoredQueryParameters = [];
54
55
    /**
56
     * @var bool
57
     */
58
    private $strictMode = false;
59
60
    /**
61
     * @param string                                          $fixturesPath
62
     * @param \Psr\Http\Message\ResponseFactoryInterface|null $responseFactory
63
     * @param \Psr\Http\Message\StreamFactoryInterface|null   $streamFactory
64
     */
65 96
    public function __construct(
66
        string $fixturesPath,
67
        ?ResponseFactoryInterface $responseFactory = null,
68
        ?StreamFactoryInterface $streamFactory = null
69
    ) {
70 96
        $this->fixturesPath = $fixturesPath;
71 96
        $this->responseFactory = $responseFactory ?: Psr17FactoryDiscovery::findResponseFactory();
72 96
        $this->streamFactory = $streamFactory ?: Psr17FactoryDiscovery::findStreamFactory();
73 16
    }
74
75
    /**
76
     * @return array
77
     */
78 96
    public function getDomainAliases(): array
79
    {
80 96
        return $this->domainAliases;
81
    }
82
83
    /**
84
     * @param array $domainAliases
85
     *
86
     * @return $this
87
     */
88 6
    public function setDomainAliases(array $domainAliases): self
89
    {
90 6
        $this->domainAliases = $domainAliases;
91
92 6
        return $this;
93
    }
94
95
    /**
96
     * @return array
97
     */
98 96
    public function getIgnoredQueryParameters(): array
99
    {
100 96
        return $this->ignoredQueryParameters;
101
    }
102
103
    /**
104
     * @param array $ignoredQueryParameters
105
     *
106
     * @return $this
107
     */
108 6
    public function setIgnoredQueryParameters(array $ignoredQueryParameters): self
109
    {
110 6
        $this->ignoredQueryParameters = $ignoredQueryParameters;
111
112 6
        return $this;
113
    }
114
115
    /**
116
     * @return bool
117
     */
118 96
    public function useStrictMode(): bool
119
    {
120 96
        return $this->strictMode;
121
    }
122
123
    /**
124
     * @param bool $strictMode
125
     *
126
     * @return $this
127
     */
128 6
    public function setStrictMode(bool $strictMode): self
129
    {
130 6
        $this->strictMode = $strictMode;
131
132 6
        return $this;
133
    }
134
135
    /**
136
     * @param \Psr\Http\Message\RequestInterface $request
137
     *
138
     * @throws \RuntimeException
139
     * @throws \Swis\Http\Fixture\MockNotFoundException
140
     *
141
     * @return ResponseInterface
142
     */
143 96
    public function build(RequestInterface $request): ResponseInterface
144
    {
145 96
        $response = $this->responseFactory
146 96
            ->createResponse($this->getMockStatusForRequest($request))
147 96
            ->withBody($this->getMockBodyForRequest($request));
148
149 84
        foreach ($this->getMockHeadersForRequest($request) as $name => $value) {
150 36
            $response = $response->withHeader($name, $value);
151
        }
152
153 84
        return $response;
154
    }
155
156
    /**
157
     * @param \Psr\Http\Message\RequestInterface $request
158
     *
159
     * @throws \RuntimeException
160
     *
161
     * @return int
162
     */
163 96
    protected function getMockStatusForRequest(RequestInterface $request): int
164
    {
165
        try {
166 96
            $file = $this->getMockFilePathForRequest($request, self::TYPE_STATUS);
167
168 36
            return (int) file_get_contents($file);
169 66
        } catch (MockNotFoundException $e) {
170 66
            return 200;
171
        }
172
    }
173
174
    /**
175
     * @param \Psr\Http\Message\RequestInterface $request
176
     *
177
     * @throws \RuntimeException
178
     *
179
     * @return array
180
     */
181 84
    protected function getMockHeadersForRequest(RequestInterface $request): array
182
    {
183
        try {
184 84
            $file = $this->getMockFilePathForRequest($request, self::TYPE_HEADERS);
185
186 36
            return json_decode(file_get_contents($file), true, 512, JSON_THROW_ON_ERROR);
187 48
        } catch (MockNotFoundException $e) {
188 48
            return [];
189
        }
190
    }
191
192
    /**
193
     * @param \Psr\Http\Message\RequestInterface $request
194
     *
195
     * @throws \RuntimeException
196
     * @throws \Swis\Http\Fixture\MockNotFoundException
197
     *
198
     * @return \Psr\Http\Message\StreamInterface
199
     */
200 96
    protected function getMockBodyForRequest(RequestInterface $request): StreamInterface
201
    {
202 96
        $file = $this->getMockFilePathForRequest($request, self::TYPE_BODY);
203
204 84
        return $this->streamFactory->createStreamFromFile($file);
205
    }
206
207
    /**
208
     * @param \Psr\Http\Message\RequestInterface $request
209
     * @param string                             $type
210
     *
211
     * @throws \Swis\Http\Fixture\MockNotFoundException
212
     * @throws \RuntimeException
213
     *
214
     * @return string
215
     */
216 96
    protected function getMockFilePathForRequest(RequestInterface $request, string $type): string
217
    {
218 96
        $possiblePaths = $this->getPossibleMockFilePathsForRequest($request, $type);
219
220 96
        $file = null;
221 96
        foreach ($possiblePaths as $path) {
222 96
            if (file_exists($path)) {
223 90
                $file = $path;
224 90
                break;
225
            }
226
        }
227
228 96
        if (null === $file) {
229 66
            throw new MockNotFoundException('No fixture file found. Check possiblePaths for files that can be used.', $possiblePaths);
230
        }
231
232 90
        if (strpos(realpath($file), realpath($this->getFixturesPath())) !== 0) {
233 6
            throw new \RuntimeException(sprintf('Path to file "%s" is out of bounds.', $file));
234
        }
235
236 84
        return $file;
237
    }
238
239
    /**
240
     * @param \Psr\Http\Message\RequestInterface $request
241
     * @param string                             $type
242
     *
243
     * @return array
244
     */
245 96
    protected function getPossibleMockFilePathsForRequest(RequestInterface $request, string $type): array
246
    {
247 96
        $fixturesPath = $this->getFixturesPath();
248 96
        $host = $this->getHostFromRequest($request);
249 96
        $path = $this->getPathFromRequest($request);
250 96
        $method = $this->getMethodFromRequest($request);
251 96
        $query = $this->getQueryFromRequest($request);
252
253 96
        $basePathToFile = implode('/', [$fixturesPath, $host, $path]);
254
255 96
        $possibleFiles = [];
256
257 96
        if ('' !== $query) {
258 36
            $possibleFiles[] = implode('.', [$basePathToFile, $query, $method, $type]);
259 36
            $possibleFiles[] = implode('.', [$basePathToFile, $query, $type]);
260
        }
261
262 96
        $possibleFiles[] = implode('.', [$basePathToFile, $method, $type]);
263 96
        $possibleFiles[] = implode('.', [$basePathToFile, $type]);
264
265 96
        if ($this->useStrictMode()) {
266 6
            $possibleFiles = array_slice($possibleFiles, 0, 1);
267
        }
268
269 96
        return $possibleFiles;
270
    }
271
272
    /**
273
     * @param \Psr\Http\Message\RequestInterface $request
274
     *
275
     * @return string
276
     */
277 96
    protected function getHostFromRequest(RequestInterface $request): string
278
    {
279 96
        $host = trim($request->getUri()->getHost(), '/');
280
281 96
        $domainAliases = $this->getDomainAliases();
282 96
        if (array_key_exists($host, $domainAliases)) {
283 6
            return $domainAliases[$host];
284
        }
285
286 90
        return $host;
287
    }
288
289
    /**
290
     * @param \Psr\Http\Message\RequestInterface $request
291
     *
292
     * @return string
293
     */
294 96
    protected function getPathFromRequest(RequestInterface $request): string
295
    {
296 96
        return trim($request->getUri()->getPath(), '/');
297
    }
298
299
    /**
300
     * @param \Psr\Http\Message\RequestInterface $request
301
     *
302
     * @return string
303
     */
304 96
    protected function getMethodFromRequest(RequestInterface $request): string
305
    {
306 96
        return strtolower($request->getMethod());
307
    }
308
309
    /**
310
     * @param \Psr\Http\Message\RequestInterface $request
311
     * @param string                             $replacement
312
     *
313
     * @return string
314
     */
315 96
    protected function getQueryFromRequest(RequestInterface $request, string $replacement = '-'): string
316
    {
317 96
        $query = urldecode($request->getUri()->getQuery());
318 96
        $parts = array_filter(
319 96
            explode('&', $query),
320 96
            function (string $part) {
321 96
                return !$this->isQueryPartIgnored($part);
322 96
            }
323 80
        );
324 96
        sort($parts);
325 96
        $query = implode('&', $parts);
326
327 96
        return (new UnicodeString(str_replace(['\\', '/', '?', ':', '*', '"', '>', '<', '|'], $replacement, $query)))
328 96
            ->folded()
329 96
            ->ascii()
330 96
            ->replaceMatches('/[-_\s]+/', $replacement)
331 96
            ->trim($replacement)
332 96
            ->toString();
333
    }
334
335
    /**
336
     * @param string $part
337
     *
338
     * @return bool
339
     */
340 96
    protected function isQueryPartIgnored(string $part): bool
341
    {
342 96
        foreach ($this->getIgnoredQueryParameters() as $parameter) {
343 6
            if ($part === $parameter || strncmp($part, $parameter.'=', strlen($parameter) + 1) === 0) {
344 6
                return true;
345
            }
346
        }
347
348 90
        return false;
349
    }
350
351
    /**
352
     * @return string
353
     */
354 96
    protected function getFixturesPath(): string
355
    {
356 96
        return rtrim($this->fixturesPath, '/');
357
    }
358
}
359