Passed
Pull Request — master (#324)
by Tobias
03:33
created

Browser::getLastRequest()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
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 Http\Message\ResponseFactory;
15
use Interop\Http\Factory\RequestFactoryInterface;
16
use Interop\Http\Factory\ResponseFactoryInterface;
17
use Nyholm\Psr7\Factory\MessageFactory;
18
use Psr\Http\Message\RequestInterface;
19
use Psr\Http\Message\ResponseInterface;
20
21
class Browser implements BuzzClientInterface
22
{
23
    /** @var BuzzClientInterface */
24
    private $client;
25
26
    /** @var RequestFactoryInterface|RequestFactory */
27
    private $requestFactory;
28
29
    /**
30
     * @var MiddlewareInterface[]
31
     */
32
    private $middlewares = [];
33
34
    /** @var RequestInterface */
35
    private $lastRequest;
36
37
    /** @var ResponseInterface */
38
    private $lastResponse;
39
40
    /**
41
     * @param BuzzClientInterface|null                      $client
42
     * @param RequestFactoryInterface|RequestFactory|null   $requestFactory
43
     * @param ResponseFactoryInterface|ResponseFactory|null $responseFactory To change the default response factory for FileGetContents
44
     */
45 72
    public function __construct(
46
        BuzzClientInterface $client = null,
47
        $requestFactory = null,
48
        $responseFactory = null
49
    ) {
50 72
        $this->client = $client ?: new FileGetContents([], $responseFactory ?: new MessageFactory());
51
52 72
        if (null === $requestFactory) {
53 72
            $requestFactory = new MessageFactory();
54
        } elseif (!$requestFactory instanceof RequestFactoryInterface && !$requestFactory instanceof RequestFactory) {
0 ignored issues
show
introduced by
$requestFactory is always a sub-type of Http\Message\RequestFactory.
Loading history...
55
            throw new InvalidArgumentException('$requestFactory not a valid RequestFactory');
56
        }
57 72
        $this->requestFactory = $requestFactory;
58 72
    }
59
60 1
    public function get(string $url, array $headers = []): ResponseInterface
61
    {
62 1
        return $this->request('GET', $url, $headers);
63
    }
64
65 1
    public function post(string $url, array $headers = [], string $body = ''): ResponseInterface
66
    {
67 1
        return $this->request('POST', $url, $headers, $body);
68
    }
69
70 1
    public function head(string $url, array  $headers = []): ResponseInterface
71
    {
72 1
        return $this->request('HEAD', $url, $headers);
73
    }
74
75 1
    public function patch(string $url, array  $headers = [], string $body = ''): ResponseInterface
76
    {
77 1
        return $this->request('PATCH', $url, $headers, $body);
78
    }
79
80 1
    public function put(string $url, array  $headers = [], string $body = ''): ResponseInterface
81
    {
82 1
        return $this->request('PUT', $url, $headers, $body);
83
    }
84
85 1
    public function delete(string $url, array  $headers = [], string $body = ''): ResponseInterface
86
    {
87 1
        return $this->request('DELETE', $url, $headers, $body);
88
    }
89
90
    /**
91
     * Sends a request.
92
     *
93
     * @param string $method  The request method to use
94
     * @param string $url     The URL to call
95
     * @param array  $headers An array of request headers
96
     * @param string $body    The request content
97
     *
98
     * @return ResponseInterface The response object
99
     */
100 6
    public function request(string $method, string $url, array $headers = [], string $body = ''): ResponseInterface
101
    {
102 6
        $request = $this->createRequest($method, $url, $headers, $body);
103
104 6
        return $this->sendRequest($request);
105
    }
106
107
    /**
108
     * Submit a form.
109
     *
110
     * @throws ClientException
111
     * @throws LogicException
112
     * @throws InvalidArgumentException
113
     */
114 9
    public function submitForm(string $url, array $fields, string $method = 'POST', array $headers = []): ResponseInterface
115
    {
116 9
        $body = [];
117 9
        $files = '';
118 9
        $boundary = uniqid('', true);
119 9
        foreach ($fields as $name => $field) {
120 9
            if (!isset($field['path'])) {
121 6
                $body[$name] = $field;
122
            } else {
123
                // This is a file
124 8
                $fileContent = file_get_contents($field['path']);
125 9
                $files .= $this->prepareMultipart($name, $fileContent, $boundary, $field);
126
            }
127
        }
128
129 9
        if (empty($files)) {
130 1
            $headers['Content-Type'] = 'application/x-www-form-urlencoded';
131 1
            $body = http_build_query($body);
132
        } else {
133 8
            $headers['Content-Type'] = 'multipart/form-data; boundary="'.$boundary.'"';
134
135 8
            foreach ($body as $name => $value) {
136 5
                $files .= $this->prepareMultipart($name, $value, $boundary);
137
            }
138 8
            $body = "$files--{$boundary}--\r\n";
139
        }
140
141 9
        $request = $this->createRequest($method, $url, $headers, $body);
142
143 9
        return $this->sendRequest($request);
144
    }
145
146
    /**
147
     * Send a PSR7 request.
148
     *
149
     * @throws ClientException
150
     * @throws LogicException
151
     * @throws InvalidArgumentException
152
     */
153
    public function sendRequest(RequestInterface $request, array $options = []): ResponseInterface
154
    {
155 68
        $chain = $this->createMiddlewareChain($this->middlewares, function (RequestInterface $request, callable $responseChain) use ($options) {
156 68
            $response = $this->client->sendRequest($request, $options);
157 67
            $responseChain($request, $response);
158
        }, function (RequestInterface $request, ResponseInterface $response) {
159 67
            $this->lastRequest = $request;
160 67
            $this->lastResponse = $response;
161 68
        });
162
163
        // Call the chain
164 68
        $chain($request);
165
166 67
        return $this->lastResponse;
167
    }
168
169
    /**
170
     * @param MiddlewareInterface[] $middlewares
171
     * @param callable              $requestChainLast
172
     * @param callable              $responseChainLast
173
     *
174
     * @return callable
175
     */
176 68
    private function createMiddlewareChain(array $middlewares, callable $requestChainLast, callable $responseChainLast): callable
177
    {
178 68
        $responseChainNext = $responseChainLast;
179
180
        // Build response chain
181
        /** @var MiddlewareInterface $middleware */
182 68
        foreach ($middlewares as $middleware) {
183 3
            $lastCallable = function (RequestInterface $request, ResponseInterface $response) use ($middleware, $responseChainNext) {
184 3
                return $middleware->handleResponse($request, $response, $responseChainNext);
185 3
            };
186
187 3
            $responseChainNext = $lastCallable;
188
        }
189
190 68
        $requestChainLast = function (RequestInterface $request) use ($requestChainLast, $responseChainNext) {
191
            // Send the actual request and get the response
192 68
            $requestChainLast($request, $responseChainNext);
193 68
        };
194
195 68
        $middlewares = array_reverse($middlewares);
196
197
        // Build request chain
198 68
        $requestChainNext = $requestChainLast;
199
        /** @var MiddlewareInterface $middleware */
200 68
        foreach ($middlewares as $middleware) {
201 3
            $lastCallable = function (RequestInterface $request) use ($middleware, $requestChainNext) {
202 3
                return $middleware->handleRequest($request, $requestChainNext);
203 3
            };
204
205 3
            $requestChainNext = $lastCallable;
206
        }
207
208 68
        return $requestChainNext;
209
    }
210
211 1
    public function getLastRequest(): ?RequestInterface
212
    {
213 1
        return $this->lastRequest;
214
    }
215
216 1
    public function getLastResponse(): ?ResponseInterface
217
    {
218 1
        return $this->lastResponse;
219
    }
220
221 1
    public function getClient(): BuzzClientInterface
222
    {
223 1
        return $this->client;
224
    }
225
226
    /**
227
     * Add a new middleware to the stack.
228
     *
229
     * @param MiddlewareInterface $middleware
230
     */
231 3
    public function addMiddleware(MiddlewareInterface $middleware): void
232
    {
233 3
        $this->middlewares[] = $middleware;
234 3
    }
235
236 8
    private function prepareMultipart(string $name, string $content, string $boundary, array $data = []): string
237
    {
238 8
        $output = '';
239 8
        $fileHeaders = [];
240
241
        // Set a default content-disposition header
242 8
        $fileHeaders['Content-Disposition'] = sprintf('form-data; name="%s"', $name);
243 8
        if (isset($data['filename'])) {
244 7
            $fileHeaders['Content-Disposition'] .= sprintf('; filename="%s"', $data['filename']);
245
        }
246
247
        // Set a default content-length header
248 8
        if ($length = strlen($content)) {
249 8
            $fileHeaders['Content-Length'] = (string) $length;
250
        }
251
252 8
        if (isset($data['contentType'])) {
253 7
            $fileHeaders['Content-Type'] = $data['contentType'];
254
        }
255
256
        // Add start
257 8
        $output .= "--$boundary\r\n";
258 8
        foreach ($fileHeaders as $key => $value) {
259 8
            $output .= sprintf("%s: %s\r\n", $key, $value);
260
        }
261 8
        $output .= "\r\n";
262 8
        $output .= $content;
263 8
        $output .= "\r\n";
264
265 8
        return $output;
266
    }
267
268 12
    protected function createRequest(string $method, string $url, array $headers, $body): RequestInterface
269
    {
270 12
        $request = $this->requestFactory->createRequest($method, $url);
271 12
        $request->getBody()->write($body);
272 12
        foreach ($headers as $name => $value) {
273 12
            $request = $request->withAddedHeader($name, $value);
274
        }
275
276 12
        return $request;
277
    }
278
}
279