Completed
Push — master ( 2b5725...c174df )
by Tobias
02:15
created

Client   A

Complexity

Total Complexity 22

Size/Duplication

Total Lines 168
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 13

Test Coverage

Coverage 96.15%

Importance

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

7 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 22 6
B sendRequest() 0 21 5
A createRequest() 0 12 1
A createResponse() 0 12 1
A addPsrHeadersToBuzzRequest() 0 9 3
A getBuzzHeaders() 0 12 2
B 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 ($this->client === null) {
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 4
            throw new HttplugException\RequestException($e->getMessage(), $request, $e);
80
        } catch (BuzzException\ClientException $e) {
81
            throw new HttplugException\TransferException($e->getMessage(), 0, $e);
82
        }
83
84 196
        return $this->createResponse($buzzResponse);
85
    }
86
87
    /**
88
     * Converts a PSR request into a BuzzRequest request.
89
     *
90
     * @param RequestInterface $request
91
     *
92
     * @return BuzzRequest
93
     */
94 200
    private function createRequest(RequestInterface $request)
95
    {
96 200
        $buzzRequest = new BuzzRequest();
97 200
        $buzzRequest->setMethod($request->getMethod());
98 200
        $buzzRequest->fromUrl($request->getUri()->__toString());
99 200
        $buzzRequest->setProtocolVersion($request->getProtocolVersion());
100 200
        $buzzRequest->setContent((string) $request->getBody());
101
102 200
        $this->addPsrHeadersToBuzzRequest($request, $buzzRequest);
103
104 200
        return $buzzRequest;
105
    }
106
107
    /**
108
     * Converts a Buzz response into a PSR response.
109
     *
110
     * @param BuzzResponse $response
111
     *
112
     * @return ResponseInterface
113
     */
114 196
    private function createResponse(BuzzResponse $response)
115
    {
116 196
        $body = $response->getContent();
117
118 196
        return $this->responseFactory->createResponse(
119 196
            $response->getStatusCode(),
120 196
            null,
121 196
            $this->getBuzzHeaders($response),
122 196
            $body,
123 196
            number_format($response->getProtocolVersion(), 1)
124 196
        );
125
    }
126
127
    /**
128
     * Apply headers on a Buzz request.
129
     *
130
     * @param RequestInterface $request
131
     * @param BuzzRequest      $buzzRequest
132
     */
133 200
    private function addPsrHeadersToBuzzRequest(RequestInterface $request, BuzzRequest $buzzRequest)
134
    {
135 200
        $headers = $request->getHeaders();
136 200
        foreach ($headers as $name => $values) {
137 200
            foreach ($values as $header) {
138 200
                $buzzRequest->addHeader($name.': '.$header);
139 200
            }
140 200
        }
141 200
    }
142
143
    /**
144
     * Get headers from a Buzz response.
145
     *
146
     * @param BuzzResponse $response
147
     *
148
     * @return array
149
     */
150 196
    private function getBuzzHeaders(BuzzResponse $response)
151
    {
152 196
        $buzzHeaders = $response->getHeaders();
153 196
        unset($buzzHeaders[0]);
154 196
        $headers = [];
155 196
        foreach ($buzzHeaders as $headerLine) {
156 196
            list($name, $value) = explode(':', $headerLine, 2);
157 196
            $headers[$name] = trim($value);
158 196
        }
159
160 196
        return $headers;
161
    }
162
163
    /**
164
     * Assert that the request has a valid body based on the request method.
165
     *
166
     * @param RequestInterface $request
167
     */
168 212
    private function assertRequestHasValidBody(RequestInterface $request)
169
    {
170
        $validMethods = [
171 212
            BuzzRequestInterface::METHOD_POST,
172 212
            BuzzRequestInterface::METHOD_PUT,
173 212
            BuzzRequestInterface::METHOD_DELETE,
174 212
            BuzzRequestInterface::METHOD_PATCH,
175 212
            BuzzRequestInterface::METHOD_OPTIONS,
176 212
        ];
177
178
        // The Buzz Curl client does not send request bodies for request methods such as GET, HEAD and TRACE. Instead of
179
        // silently ignoring the request body in these cases, throw an exception to make users aware.
180 212
        if ($this->client instanceof Curl &&
181 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...
182 20
            !in_array(strtoupper($request->getMethod()), $validMethods, true)
183 212
        ) {
184 12
            throw new HttplugException\RequestException(
185 12
                sprintf('%s does not support %s requests with a body', Curl::class, $request->getMethod()),
186
                $request
187 12
            );
188
        }
189 200
    }
190
}
191