Client   A
last analyzed

Complexity

Total Complexity 22

Size/Duplication

Total Lines 169
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 13

Test Coverage

Coverage 96.15%

Importance

Changes 0
Metric Value
wmc 22
lcom 1
cbo 13
dl 0
loc 169
ccs 75
cts 78
cp 0.9615
rs 10
c 0
b 0
f 0

7 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 22 6
A sendRequest() 0 22 5
A createRequest() 0 12 1
A createResponse() 0 12 1
A addPsrHeadersToBuzzRequest() 0 9 3
A getBuzzHeaders() 0 12 2
A assertRequestHasValidBody() 0 22 4
1
<?php
2
3
namespace Http\Adapter\Buzz;
4
5
use Buzz\Browser;
6
use Buzz\Client\ClientInterface;
7
use Buzz\Client\Curl;
8
use Buzz\Client\FileGetContents;
9
use Buzz\Exception as BuzzException;
10
use Buzz\Message\Request as BuzzRequest;
11
use Buzz\Message\RequestInterface as BuzzRequestInterface;
12
use Buzz\Message\Response as BuzzResponse;
13
use Http\Client\HttpClient;
14
use Http\Discovery\MessageFactoryDiscovery;
15
use Http\Message\ResponseFactory;
16
use Psr\Http\Message\RequestInterface;
17
use Psr\Http\Message\ResponseInterface;
18
use Http\Client\Exception as HttplugException;
19
20
/**
21
 * @author Tobias Nyholm <[email protected]>
22
 */
23
class Client implements HttpClient
24
{
25
    /**
26
     * @var ClientInterface
27
     */
28
    private $client;
29
30
    /**
31
     * @var ResponseFactory
32
     */
33
    private $responseFactory;
34
35
    /**
36
     * @param ClientInterface|Browser|null $client
37
     * @param ResponseFactory|null         $responseFactory
38
     */
39 214
    public function __construct($client = null, ResponseFactory $responseFactory = null)
40
    {
41 214
        $this->client = $client;
0 ignored issues
show
Documentation Bug introduced by
It seems like $client can also be of type object<Buzz\Browser>. However, the property $client is declared as type object<Buzz\Client\ClientInterface>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
42
43 214
        if (null === $this->client) {
44 53
            $this->client = new FileGetContents();
45 54
            $this->client->setMaxRedirects(0);
46 53
        }
47
48 214
        if ((!$this->client instanceof ClientInterface) && (!$this->client instanceof Browser)) {
49 2
            throw new \InvalidArgumentException(
50 2
                sprintf(
51 2
                    'The client passed to the Buzz adapter must either implement %s or be an instance of %s. You passed %s.',
52 2
                    ClientInterface::class,
53 2
                    Browser::class,
54 2
                    is_object($client) ? get_class($client) : gettype($client)
55 2
                )
56 2
            );
57
        }
58
59 212
        $this->responseFactory = $responseFactory ?: MessageFactoryDiscovery::find();
60 212
    }
61
62
    /**
63
     * {@inheritdoc}
64
     */
65 212
    public function sendRequest(RequestInterface $request)
66
    {
67 212
        $this->assertRequestHasValidBody($request);
68
69 200
        $buzzRequest = $this->createRequest($request);
70
71
        try {
72 200
            $buzzResponse = new BuzzResponse();
73 200
            $this->client->send($buzzRequest, $buzzResponse);
74 200
        } catch (BuzzException\RequestException $e) {
75 4
            if (28 === $e->getCode() || strstr($e->getMessage(), 'failed to open stream: Operation timed out')) {
76
                // Timeout
77
                throw new HttplugException\NetworkException($e->getMessage(), $request, $e);
78
            }
79
80 4
            throw new HttplugException\RequestException($e->getMessage(), $request, $e);
81
        } catch (BuzzException\ClientException $e) {
82
            throw new HttplugException\TransferException($e->getMessage(), 0, $e);
83
        }
84
85 196
        return $this->createResponse($buzzResponse);
86
    }
87
88
    /**
89
     * Converts a PSR request into a BuzzRequest request.
90
     *
91
     * @param RequestInterface $request
92
     *
93
     * @return BuzzRequest
94
     */
95 200
    private function createRequest(RequestInterface $request)
96
    {
97 200
        $buzzRequest = new BuzzRequest();
98 200
        $buzzRequest->setMethod($request->getMethod());
99 200
        $buzzRequest->fromUrl($request->getUri()->__toString());
100 200
        $buzzRequest->setProtocolVersion($request->getProtocolVersion());
101 200
        $buzzRequest->setContent((string) $request->getBody());
102
103 200
        $this->addPsrHeadersToBuzzRequest($request, $buzzRequest);
104
105 200
        return $buzzRequest;
106
    }
107
108
    /**
109
     * Converts a Buzz response into a PSR response.
110
     *
111
     * @param BuzzResponse $response
112
     *
113
     * @return ResponseInterface
114
     */
115 196
    private function createResponse(BuzzResponse $response)
116
    {
117 196
        $body = $response->getContent();
118
119 196
        return $this->responseFactory->createResponse(
120 196
            $response->getStatusCode(),
121 196
            null,
122 196
            $this->getBuzzHeaders($response),
123 196
            $body,
124 196
            number_format($response->getProtocolVersion(), 1)
125 196
        );
126
    }
127
128
    /**
129
     * Apply headers on a Buzz request.
130
     *
131
     * @param RequestInterface $request
132
     * @param BuzzRequest      $buzzRequest
133
     */
134 200
    private function addPsrHeadersToBuzzRequest(RequestInterface $request, BuzzRequest $buzzRequest)
135
    {
136 200
        $headers = $request->getHeaders();
137 200
        foreach ($headers as $name => $values) {
138 200
            foreach ($values as $header) {
139 200
                $buzzRequest->addHeader($name.': '.$header);
140 200
            }
141 200
        }
142 200
    }
143
144
    /**
145
     * Get headers from a Buzz response.
146
     *
147
     * @param BuzzResponse $response
148
     *
149
     * @return array
150
     */
151 196
    private function getBuzzHeaders(BuzzResponse $response)
152
    {
153 196
        $buzzHeaders = $response->getHeaders();
154 196
        unset($buzzHeaders[0]);
155 196
        $headers = [];
156 196
        foreach ($buzzHeaders as $headerLine) {
157 196
            list($name, $value) = explode(':', $headerLine, 2);
158 196
            $headers[$name] = trim($value);
159 196
        }
160
161 196
        return $headers;
162
    }
163
164
    /**
165
     * Assert that the request has a valid body based on the request method.
166
     *
167
     * @param RequestInterface $request
168
     */
169 212
    private function assertRequestHasValidBody(RequestInterface $request)
170
    {
171
        $validMethods = [
172 212
            BuzzRequestInterface::METHOD_POST,
173 212
            BuzzRequestInterface::METHOD_PUT,
174 212
            BuzzRequestInterface::METHOD_DELETE,
175 212
            BuzzRequestInterface::METHOD_PATCH,
176 212
            BuzzRequestInterface::METHOD_OPTIONS,
177 212
        ];
178
179
        // The Buzz Curl client does not send request bodies for request methods such as GET, HEAD and TRACE. Instead of
180
        // silently ignoring the request body in these cases, throw an exception to make users aware.
181 212
        if ($this->client instanceof Curl &&
182 212
            $request->getBody()->getSize() &&
0 ignored issues
show
Bug Best Practice introduced by
The expression $request->getBody()->getSize() of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
183 20
            !in_array(strtoupper($request->getMethod()), $validMethods, true)
184 212
        ) {
185 12
            throw new HttplugException\RequestException(
186 12
                sprintf('%s does not support %s requests with a body', Curl::class, $request->getMethod()),
187
                $request
188 12
            );
189
        }
190 200
    }
191
}
192