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

AbstractRequester::send()   D

Complexity

Conditions 15
Paths 200

Size

Total Lines 85

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 85
rs 4.3939
c 0
b 0
f 0
cc 15
nc 200
nop 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
        if (empty($uriSchema->getScheme())) {
204
            $uriSchema = $uriSchema->withScheme($this->psr7Request->getUri()->getScheme());
205
        }
206
207
        if (empty($uriSchema->getHost())) {
208
            $uriSchema = $uriSchema->withHost($this->psr7Request->getUri()->getHost());
209
        }
210
211
        $uri = $this->psr7Request->getUri()
212
            ->withScheme($uriSchema->getScheme())
213
            ->withHost($uriSchema->getHost())
214
            ->withPort($uriSchema->getPort())
215
            ->withPath($uriSchema->getPath() . $this->psr7Request->getUri()->getPath());
216
217
        if (!preg_match("~^{$this->schema->getBasePath()}~",  $uri->getPath())) {
218
            $uri = $uri->withPath($this->schema->getBasePath() . $uri->getPath());
219
        }
220
221
        $this->psr7Request = $this->psr7Request->withUri($uri);
222
223
        // Prepare Body to Match Against Specification
224
        $requestBody = $this->psr7Request->getBody();
225
        if (!empty($requestBody)) {
226
            $requestBody = $requestBody->getContents();
227
228
            $contentType = $this->psr7Request->getHeaderLine("content-type");
229
            if (empty($contentType) || strpos($contentType, "application/json") !== false) {
230
                $requestBody = json_decode($requestBody, true);
231
            } elseif (strpos($contentType, "multipart/") !== false) {
232
                $requestBody = $this->parseMultiPartForm($contentType, $requestBody);
233
            } else {
234
                throw new InvalidRequestException("Cannot handle Content Type '{$contentType}'");
235
            }
236
        }
237
238
        // Check if the body is the expected before request
239
        $bodyRequestDef = $this->schema->getRequestParameters($this->psr7Request->getUri()->getPath(), $this->psr7Request->getMethod());
240
        $bodyRequestDef->match($requestBody);
241
242
        // Handle Request
243
        $response = $this->handleRequest($this->psr7Request);
244
        $responseHeader = $response->getHeaders();
245
        $responseBodyStr = (string) $response->getBody();
246
        $responseBody = json_decode($responseBodyStr, true);
247
        $statusReturned = $response->getStatusCode();
248
249
        // Assert results
250
        if ($this->statusExpected != $statusReturned) {
251
            throw new StatusCodeNotMatchedException(
252
                "Status code not matched: Expected {$this->statusExpected}, got {$statusReturned}",
253
                $responseBody
254
            );
255
        }
256
257
        $bodyResponseDef = $this->schema->getResponseParameters(
258
            $this->psr7Request->getUri()->getPath(),
259
            $this->psr7Request->getMethod(),
260
            $this->statusExpected
261
        );
262
        $bodyResponseDef->match($responseBody);
263
264
        foreach ($this->assertHeader as $key => $value) {
265
            if (!isset($responseHeader[$key]) || strpos($responseHeader[$key][0], $value) === false) {
266
                throw new NotMatchedException(
267
                    "Does not exists header '$key' with value '$value'",
268
                    $responseHeader
269
                );
270
            }
271
        }
272
273
        if (!empty($responseBodyStr)) {
274
            foreach ($this->assertBody as $item) {
275
                if (strpos($responseBodyStr, $item) === false) {
276
                    throw new NotMatchedException("Body does not contain '{$item}'");
277
                }
278
            }
279
        }
280
281
        return $response;
282
    }
283
284
    protected function parseMultiPartForm($contentType, $body)
285
    {
286
        $matchRequest = [];
287
288
        if (empty($contentType) || strpos($contentType, "multipart/") === false) {
289
            return null;
290
        }
291
292
        $matches = [];
293
294
        preg_match('/boundary=(.*)$/', $contentType, $matches);
295
        $boundary = $matches[1];
296
297
        // split content by boundary and get rid of last -- element
298
        $blocks = preg_split("/-+$boundary/", $body);
299
        array_pop($blocks);
300
301
        // loop data blocks
302
        foreach ($blocks as $id => $block) {
303
            if (empty($block))
304
                continue;
305
306
            if (strpos($block, 'application/octet-stream') !== false) {
307
                preg_match("/name=\"([^\"]*)\".*stream[\n|\r]+([^\n\r].*)?$/s", $block, $matches);
308
            } else {
309
                preg_match('/\bname=\"([^\"]*)\"\s*;.*?[\n|\r]+([^\n\r].*)?[\r|\n]$/s', $block, $matches);
310
            }
311
            $matchRequest[$matches[1]] = $matches[2];
312
        }
313
314
        return $matchRequest;
315
    }
316
}
317