Passed
Pull Request — master (#305)
by Tobias
02:01
created

Browser::getLastResponse()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Buzz;
6
7
use Buzz\Client\BuzzClientInterface;
8
use Buzz\Client\FileGetContents;
9
use Buzz\Exception\ClientException;
10
use Buzz\Exception\InvalidArgumentException;
11
use Buzz\Exception\LogicException;
12
use Buzz\Middleware\MiddlewareInterface;
13
use Http\Message\RequestFactory;
14
use Interop\Http\Factory\RequestFactoryInterface;
15
use Nyholm\Psr7\Factory\MessageFactory;
16
use Psr\Http\Client\ClientInterface;
17
use Psr\Http\Message\RequestInterface;
18
use Psr\Http\Message\ResponseInterface;
19
20
class Browser implements BuzzClientInterface
21
{
22
    /** @var ClientInterface */
23
    private $client;
24
25
    /** @var RequestFactoryInterface|RequestFactory */
26
    private $factory;
27
28
    /**
29
     * @var MiddlewareInterface[]
30
     */
31
    private $middlewares = [];
32
33
    /** @var RequestInterface */
34
    private $lastRequest;
35
36
    /** @var ResponseInterface */
37
    private $lastResponse;
38
39
    public function __construct(BuzzClientInterface $client = null)
40
    {
41
        $this->client = $client ?: new FileGetContents();
42
        $this->factory = new MessageFactory();
43
    }
44
45
    public function get(string $url, array $headers = []): ResponseInterface
46
    {
47
        return $this->request('GET', $url, $headers);
48
    }
49
50
    public function post(string $url, array $headers = [], string $body = ''): ResponseInterface
51
    {
52
        return $this->request('POST', $url, $headers, $body);
53
    }
54
55
    public function head(string $url, array  $headers = []): ResponseInterface
56
    {
57
        return $this->request('HEAD', $url, $headers);
58
    }
59
60
    public function patch(string $url, array  $headers = [], string $body = ''): ResponseInterface
61
    {
62
        return $this->request('PATCH', $url, $headers, $body);
63
    }
64
65
    public function put(string $url, array  $headers = [], string $body = ''): ResponseInterface
66
    {
67
        return $this->request('PUT', $url, $headers, $body);
68
    }
69
70
    public function delete(string $url, array  $headers = [], string $body = ''): ResponseInterface
71
    {
72
        return $this->request('DELETE', $url, $headers, $body);
73
    }
74
75
    /**
76
     * Sends a request.
77
     *
78
     * @param string $method  The request method to use
79
     * @param string $url     The URL to call
80
     * @param array  $headers An array of request headers
81
     * @param string $body    The request content
82
     *
83
     * @return ResponseInterface The response object
84
     */
85
    public function request(string $method, string $url, array $headers = [], string $body = ''): ResponseInterface
86
    {
87
        $request = $this->createRequest($method, $url, $headers, $body);
88
89
        return $this->sendRequest($request);
90
    }
91
92
    /**
93
     * Submit a form.
94
     *
95
     * @throws ClientException
96
     * @throws LogicException
97
     * @throws InvalidArgumentException
98
     */
99
    public function submitForm(string $url, array $fields, string $method = 'POST', array $headers = []): ResponseInterface
100
    {
101
        $body = [];
102
        $files = '';
103
        $boundary = uniqid('', true);
104
        foreach ($fields as $name => $field) {
105
            if (!isset($field['path'])) {
106
                $body[$name] = $field;
107
            } else {
108
                // This is a file
109
                $fileContent = file_get_contents($field['path']);
110
                $files .= $this->prepareMultipart($name, $fileContent, $boundary, $field);
111
            }
112
        }
113
114
        if (empty($files)) {
0 ignored issues
show
introduced by
The condition empty($files) is always true.
Loading history...
115
            $headers['Content-Type'] = 'application/x-www-form-urlencoded';
116
            $body = http_build_query($body);
117
        } else {
118
            $headers['Content-Type'] = 'multipart/form-data; boundary="'.$boundary.'"';
119
120
            foreach ($body as $name => $value) {
121
                $files .= $this->prepareMultipart($name, $value, $boundary);
122
            }
123
            $body = "$files--{$boundary}--\r\n";
124
        }
125
126
        $request = $this->createRequest($method, $url, $headers, $body);
127
128
        return $this->sendRequest($request);
129
    }
130
131
    /**
132
     * Send a PSR7 request.
133
     *
134
     * @throws ClientException
135
     * @throws LogicException
136
     * @throws InvalidArgumentException
137
     */
138
    public function sendRequest(RequestInterface $request, array $options = []): ResponseInterface
139
    {
140
        $chain = $this->createMiddlewareChain($this->middlewares, function (RequestInterface $request, callable $responseChain) use ($options) {
141
            $response = $this->client->sendRequest($request, $options);
0 ignored issues
show
Unused Code introduced by
The call to Psr\Http\Client\ClientInterface::sendRequest() has too many arguments starting with $options. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

141
            /** @scrutinizer ignore-call */ 
142
            $response = $this->client->sendRequest($request, $options);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
142
            $responseChain($request, $response);
143
        }, function (RequestInterface $request, ResponseInterface $response) {
144
            $this->lastRequest = $request;
145
            $this->lastResponse = $response;
146
        });
147
148
        // Call the chain
149
        $chain($request);
150
151
        return $this->lastResponse;
152
    }
153
154
    /**
155
     * @param MiddlewareInterface[] $middlewares
156
     * @param callable              $requestChainLast
157
     * @param callable              $responseChainLast
158
     *
159
     * @return callable
160
     */
161
    private function createMiddlewareChain(array $middlewares, callable $requestChainLast, callable $responseChainLast): callable
162
    {
163
        $responseChainNext = $responseChainLast;
164
165
        // Build response chain
166
        /** @var MiddlewareInterface $middleware */
167
        foreach ($middlewares as $middleware) {
168
            $lastCallable = function (RequestInterface $request, ResponseInterface $response) use ($middleware, $responseChainNext) {
169
                return $middleware->handleResponse($request, $response, $responseChainNext);
170
            };
171
172
            $responseChainNext = $lastCallable;
173
        }
174
175
        $requestChainLast = function (RequestInterface $request) use ($requestChainLast, $responseChainNext) {
176
            // Send the actual request and get the response
177
            $requestChainLast($request, $responseChainNext);
178
        };
179
180
        $middlewares = array_reverse($middlewares);
181
182
        // Build request chain
183
        $requestChainNext = $requestChainLast;
184
        /** @var MiddlewareInterface $middleware */
185
        foreach ($middlewares as $middleware) {
186
            $lastCallable = function (RequestInterface $request) use ($middleware, $requestChainNext) {
187
                return $middleware->handleRequest($request, $requestChainNext);
188
            };
189
190
            $requestChainNext = $lastCallable;
191
        }
192
193
        return $requestChainNext;
194
    }
195
196
    public function getLastRequest(): ?RequestInterface
197
    {
198
        return $this->lastRequest;
199
    }
200
201
    public function getLastResponse(): ?ResponseInterface
202
    {
203
        return $this->lastResponse;
204
    }
205
206
    public function getClient(): BuzzClientInterface
207
    {
208
        return $this->client;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->client returns the type Psr\Http\Client\ClientInterface which includes types incompatible with the type-hinted return Buzz\Client\BuzzClientInterface.
Loading history...
209
    }
210
211
    /**
212
     * Add a new middleware to the stack.
213
     *
214
     * @param MiddlewareInterface $middleware
215
     */
216
    public function addMiddleware(MiddlewareInterface $middleware): void
217
    {
218
        $this->middlewares[] = $middleware;
219
    }
220
221
    private function prepareMultipart(string $name, string $content, string $boundary, array $data = []): string
222
    {
223
        $output = '';
224
        $fileHeaders = [];
225
226
        // Set a default content-disposition header
227
        $fileHeaders['Content-Disposition'] = sprintf('form-data; name="%s"', $name);
228
        if (isset($data['filename'])) {
229
            $fileHeaders['Content-Disposition'] .= sprintf('; filename="%s"', $data['filename']);
230
        }
231
232
        // Set a default content-length header
233
        if ($length = strlen($content)) {
234
            $fileHeaders['Content-Length'] = (string) $length;
235
        }
236
237
        if (isset($data['contentType'])) {
238
            $fileHeaders['Content-Type'] = $data['contentType'];
239
        }
240
241
        // Add start
242
        $output .= "--$boundary\r\n";
243
        foreach ($fileHeaders as $key => $value) {
244
            $output .= sprintf("%s: %s\r\n", $key, $value);
245
        }
246
        $output .= "\r\n";
247
        $output .= $content;
248
        $output .= "\r\n";
249
250
        return $output;
251
    }
252
253
    protected function createRequest(string $method, string $url, array $headers, $body): RequestInterface
254
    {
255
        $request = $this->factory->createRequest($method, $url);
256
        $request->getBody()->write($body);
257
        foreach ($headers as $name => $value) {
258
            $request = $request->withAddedHeader($name, $value);
259
        }
260
261
        return $request;
262
    }
263
}
264