Passed
Push — master ( 23c831...15f2f3 )
by Jasper
12:00
created

ResponseBuilder::getPathFromRequest()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

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