Completed
Push — master ( 00d4b3...7831a2 )
by Tobias
02:40
created

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