Passed
Push — master ( f9279d...87874e )
by Joao
01:35 queued 11s
created

AbstractRequester::withPsr7Request()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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