Passed
Pull Request — master (#50)
by Joao
01:50
created

AbstractRequester::parseMultiPartForm()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 32

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 32
rs 8.7857
c 0
b 0
f 0
cc 6
nc 5
nop 2
1
<?php
2
3
namespace ByJG\ApiTools;
4
5
use ByJG\ApiTools\Base\Schema;
6
use ByJG\ApiTools\Exception\InvalidRequestException;
7
use ByJG\ApiTools\Exception\NotMatchedException;
8
use ByJG\ApiTools\Exception\StatusCodeNotMatchedException;
9
use ByJG\Util\Psr7\MessageException;
10
use ByJG\Util\Psr7\Request;
11
use ByJG\Util\Psr7\Response;
12
use ByJG\Util\Uri;
13
use MintWare\Streams\MemoryStream;
14
use Psr\Http\Message\RequestInterface;
15
use Psr\Http\Message\ResponseInterface;
16
17
/**
18
 * Abstract baseclass for request handlers.
19
 *
20
 * The baseclass provides processing and verification of request and response.
21
 * It only delegates the actual message exchange to the derived class. For the
22
 * messages, it uses the PSR-7 implementation from Guzzle.
23
 *
24
 * This is an implementation of the Template Method Patttern
25
 * (https://en.wikipedia.org/wiki/Template_method_pattern).
26
 */
27
abstract class AbstractRequester
28
{
29
    /**
30
     * @var Schema
31
     */
32
    protected $schema = null;
33
34
    protected $statusExpected = 200;
35
    protected $assertHeader = [];
36
    protected $assertBody = [];
37
38
    /**
39
     * @var RequestInterface
40
     */
41
    protected $psr7Request;
42
43
    /**
44
     * AbstractRequester constructor.
45
     * @throws MessageException
46
     */
47
    public function __construct()
48
    {
49
        $this->withPsr7Request(Request::getInstance(new Uri("/"))->withMethod("get"));
50
    }
51
52
    /**
53
     * abstract function to be implemented by derived classes
54
     *
55
     * This function must be implemented by derived classes. It should process
56
     * the given request and return an according response.
57
     *
58
     * @param RequestInterface $request
59
     * @return ResponseInterface
60
     */
61
    abstract protected function handleRequest(RequestInterface $request);
62
63
    /**
64
     * @param Schema $schema
65
     * @return $this
66
     */
67
    public function withSchema($schema)
68
    {
69
        $this->schema = $schema;
70
71
        return $this;
72
    }
73
74
    /**
75
     * @return bool
76
     */
77
    public function hasSchema()
78
    {
79
        return !empty($this->schema);
80
    }
81
82
    /**
83
     * @param string $method
84
     * @return $this
85
     * @throws MessageException
86
     */
87
    public function withMethod($method)
88
    {
89
        $this->psr7Request = $this->psr7Request->withMethod($method);
90
91
        return $this;
92
    }
93
94
    /**
95
     * @param string $path
96
     * @return $this
97
     */
98
    public function withPath($path)
99
    {
100
        $uri = $this->psr7Request->getUri()->withPath($path);
101
        $this->psr7Request = $this->psr7Request->withUri($uri);
102
103
        return $this;
104
    }
105
106
    /**
107
     * @param array $requestHeader
108
     * @return $this
109
     */
110
    public function withRequestHeader($requestHeader)
111
    {
112
        foreach ((array)$requestHeader as $name => $value) {
113
            $this->psr7Request = $this->psr7Request->withHeader($name, $value);
114
        }
115
116
        return $this;
117
    }
118
119
    /**
120
     * @param array $query
121
     * @return $this
122
     */
123
    public function withQuery($query = null)
124
    {
125
        $uri = $this->psr7Request->getUri();
126
127
        if (is_null($query)) {
128
            $uri = $uri->withQuery(null);
129
            $this->psr7Request = $this->psr7Request->withUri($uri);
130
            return $this;
131
        }
132
133
        $currentQuery = [];
134
        parse_str($uri->getQuery(), $currentQuery);
135
136
        $uri = $uri->withQuery(http_build_query(array_merge($currentQuery, $query)));
137
        $this->psr7Request = $this->psr7Request->withUri($uri);
138
139
        return $this;
140
    }
141
142
    /**
143
     * @param null $requestBody
144
     * @return $this
145
     */
146
    public function withRequestBody($requestBody)
147
    {
148
        $contentType = $this->psr7Request->getHeaderLine("Content-Type");
149
        if (is_array($requestBody) && (empty($contentType) || strpos($contentType, "application/json") !== false)) {
150
            $requestBody = json_encode($requestBody);
151
        }
152
        $this->psr7Request = $this->psr7Request->withBody(new MemoryStream($requestBody));
0 ignored issues
show
Bug introduced by
It seems like $requestBody can also be of type array; however, MintWare\Streams\MemoryStream::__construct() does only seem to accept string|null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
153
154
        return $this;
155
    }
156
157
    public function withPsr7Request(RequestInterface $requesInterface)
158
    {
159
        $this->psr7Request = clone $requesInterface;
160
        $this->psr7Request = $this->psr7Request->withHeader("Accept", "application/json");
161
162
        return $this;
163
    }
164
165
    public function assertResponseCode($code)
166
    {
167
        $this->statusExpected = $code;
168
169
        return $this;
170
    }
171
172
    public function assertHeaderContains($header, $contains)
173
    {
174
        $this->assertHeader[$header] = $contains;
175
176
        return $this;
177
    }
178
179
    public function assertBodyContains($contains)
180
    {
181
        $this->assertBody[] = $contains;
182
183
        return $this;
184
    }
185
186
    /**
187
     * @return Response|ResponseInterface
188
     * @throws Exception\DefinitionNotFoundException
189
     * @throws Exception\GenericSwaggerException
190
     * @throws Exception\HttpMethodNotFoundException
191
     * @throws Exception\InvalidDefinitionException
192
     * @throws Exception\PathNotFoundException
193
     * @throws NotMatchedException
194
     * @throws StatusCodeNotMatchedException
195
     * @throws MessageException
196
     * @throws InvalidRequestException
197
     */
198
    public function send()
199
    {
200
        // Process URI based on the OpenAPI schema
201
        $uriSchema = new Uri($this->schema->getServerUrl());
202
203
        $uri = $this->psr7Request->getUri()
204
            ->withScheme(empty($uriSchema->getScheme()) ? $this->psr7Request->getUri()->getScheme() : $uriSchema->getScheme())
205
            ->withHost(empty($uriSchema->getHost()) ? $this->psr7Request->getUri()->getHost() : $uriSchema->getHost())
206
            ->withPort($uriSchema->getPort())
207
            ->withPath($uriSchema->getPath() . $this->psr7Request->getUri()->getPath());
208
209
        if (!preg_match("~^{$this->schema->getBasePath()}~",  $uri->getPath())) {
210
            $uri = $uri->withPath($this->schema->getBasePath() . $uri->getPath());
211
        }
212
213
        $this->psr7Request = $this->psr7Request->withUri($uri);
214
215
        // Prepare Body to Match Against Specification
216
        $requestBody = $this->psr7Request->getBody();
217
        if (!empty($requestBody)) {
218
            $requestBody = $requestBody->getContents();
219
220
            $contentType = $this->psr7Request->getHeaderLine("content-type");
221
            if (empty($contentType) || strpos($contentType, "application/json") !== false) {
222
                $requestBody = json_decode($requestBody, true);
223
            } elseif (strpos($contentType, "multipart/") !== false) {
224
                $requestBody = $this->parseMultiPartForm($contentType, $requestBody);
225
            } else {
226
                throw new InvalidRequestException("Cannot handle Content Type '{$contentType}'");
227
            }
228
        }
229
230
        // Check if the body is the expected before request
231
        $bodyRequestDef = $this->schema->getRequestParameters($this->psr7Request->getUri()->getPath(), $this->psr7Request->getMethod());
232
        $bodyRequestDef->match($requestBody);
233
234
        // Handle Request
235
        $response = $this->handleRequest($this->psr7Request);
236
        $responseHeader = $response->getHeaders();
237
        $responseBodyStr = (string) $response->getBody();
238
        $responseBody = json_decode($responseBodyStr, true);
239
        $statusReturned = $response->getStatusCode();
240
241
        // Assert results
242
        if ($this->statusExpected != $statusReturned) {
243
            throw new StatusCodeNotMatchedException(
244
                "Status code not matched: Expected {$this->statusExpected}, got {$statusReturned}",
245
                $responseBody
246
            );
247
        }
248
249
        $bodyResponseDef = $this->schema->getResponseParameters(
250
            $this->psr7Request->getUri()->getPath(),
251
            $this->psr7Request->getMethod(),
252
            $this->statusExpected
253
        );
254
        $bodyResponseDef->match($responseBody);
255
256
        foreach ($this->assertHeader as $key => $value) {
257
            if (!isset($responseHeader[$key]) || strpos($responseHeader[$key][0], $value) === false) {
258
                throw new NotMatchedException(
259
                    "Does not exists header '$key' with value '$value'",
260
                    $responseHeader
261
                );
262
            }
263
        }
264
265
        if (!empty($responseBodyStr)) {
266
            foreach ($this->assertBody as $item) {
267
                if (strpos($responseBodyStr, $item) === false) {
268
                    throw new NotMatchedException("Body does not contain '{$item}'");
269
                }
270
            }
271
        }
272
273
        return $response;
274
    }
275
276
    protected function parseMultiPartForm($contentType, $body)
277
    {
278
        $matchRequest = [];
279
280
        if (empty($contentType) || strpos($contentType, "multipart/") === false) {
281
            return null;
282
        }
283
284
        $matches = [];
285
286
        preg_match('/boundary=(.*)$/', $contentType, $matches);
287
        $boundary = $matches[1];
288
289
        // split content by boundary and get rid of last -- element
290
        $blocks = preg_split("/-+$boundary/", $body);
291
        array_pop($blocks);
292
293
        // loop data blocks
294
        foreach ($blocks as $id => $block) {
295
            if (empty($block))
296
                continue;
297
298
            if (strpos($block, 'application/octet-stream') !== false) {
299
                preg_match("/name=\"([^\"]*)\".*stream[\n|\r]+([^\n\r].*)?$/s", $block, $matches);
300
            } else {
301
                preg_match('/\bname=\"([^\"]*)\"\s*;.*?[\n|\r]+([^\n\r].*)?[\r|\n]$/s', $block, $matches);
302
            }
303
            $matchRequest[$matches[1]] = $matches[2];
304
        }
305
306
        return $matchRequest;
307
    }
308
}
309