Completed
Push — master ( eb2839...d8a28e )
by Julián
17:45
created

Client::setCloseOnError()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 1
1
<?php
2
3
/*
4
 * A PSR7 aware cURL client (https://github.com/juliangut/spiral).
5
 *
6
 * @license BSD-3-Clause
7
 * @link https://github.com/juliangut/spiral
8
 * @author Julián Gutiérrez <[email protected]>
9
 */
10
11
namespace Jgut\Spiral;
12
13
use Jgut\Spiral\Exception\TransportException;
14
use Jgut\Spiral\Transport\Curl;
15
use Jgut\Spiral\Transport\TransportInterface;
16
use Psr\Http\Message\RequestInterface;
17
use Psr\Http\Message\ResponseInterface;
18
use Zend\Diactoros\Stream;
19
20
/**
21
 * PSR7 aware client.
22
 */
23
class Client
24
{
25
    /**
26
     * cURL transport handler.
27
     *
28
     * @var TransportInterface
29
     */
30
    private $transport;
31
32
    /**
33
     * Auto close on error.
34
     *
35
     * @var bool
36
     */
37
    private $closeOnError = true;
38
39
    /**
40
     * @param TransportInterface|null $transport
41
     */
42
    public function __construct(TransportInterface $transport = null)
43
    {
44
        $this->transport = $transport;
45
    }
46
47
    /**
48
     * Set transport handler.
49
     *
50
     * @param TransportInterface $transport
51
     */
52
    public function setTransport(TransportInterface $transport)
53
    {
54
        $this->transport = $transport;
55
    }
56
57
    /**
58
     * Retrieve transport handler.
59
     *
60
     * @return TransportInterface
61
     */
62
    public function getTransport()
63
    {
64
        if (!$this->transport instanceof TransportInterface) {
65
            $this->transport = Curl::createFromDefaults();
66
        }
67
68
        return $this->transport;
69
    }
70
71
    /**
72
     * Set automatic connection close on error.
73
     *
74
     * @param bool $closeOnError
75
     */
76
    public function setCloseOnError($closeOnError)
77
    {
78
        $this->closeOnError = $closeOnError === true;
79
    }
80
81
    /**
82
     * Check if automatic connection close on error is active.
83
     *
84
     * @return bool
85
     */
86
    public function isCloseOnError()
87
    {
88
        return $this->closeOnError;
89
    }
90
91
    /**
92
     * Run PSR7 request.
93
     *
94
     * @param RequestInterface  $request
95
     * @param ResponseInterface $response
96
     * @param array             $vars
97
     * @param array             $flags
98
     *
99
     * @throws TransportException
100
     *
101
     * @return ResponseInterface
102
     */
103
    public function request(
104
        RequestInterface $request,
105
        ResponseInterface $response,
106
        array $vars = [],
107
        array $flags = []
108
    ) {
109
        $transport = $this->getTransport();
110
111
        try {
112
            $transferResponse = $transport->request(
113
                $request->getMethod(),
114
                (string) $request->getUri(),
115
                $request->getHeaders(),
116
                $vars,
117
                $flags
118
            );
119
        } catch (TransportException $exception) {
120
            if ($this->closeOnError) {
121
                $transport->close();
122
            }
123
124
            // Bubble exception
125
            throw $exception;
126
        }
127
128
        $transferInfo = $transport->responseInfo();
129
        $transport->close();
130
131
        $responseHeaders = '';
132
        $responseContent = $transferResponse;
133
134
        if (isset($transferInfo['header_size']) && $transferInfo['header_size']) {
135
            $headersSize = $transferInfo['header_size'];
136
137
            $responseHeaders = rtrim(substr($transferResponse, 0, $headersSize));
138
            $responseContent = (strlen($transferResponse) === $headersSize)
139
                ? ''
140
                : substr($transferResponse, $headersSize);
141
        }
142
143
        // Split headers blocks
144
        $responseHeaders = preg_split('/(\\r?\\n){2}/', $responseHeaders);
145
146
        $responseHeaders = $this->getTransferHeaders(
147
            preg_split('/\\r?\\n/', array_pop($responseHeaders)),
148
            $responseContent,
149
            $transferInfo
150
        );
151
152
        return $this->populateResponse($response, $responseHeaders, $responseContent);
153
    }
154
155
    /**
156
     * Get response headers based on transfer information.
157
     *
158
     * @param array  $transferHeaders
159
     * @param string $transferContent
160
     * @param array  $transferInfo
161
     *
162
     * @return array
163
     */
164
    protected function getTransferHeaders(array $transferHeaders, $transferContent, array $transferInfo)
165
    {
166
        $responseHeaders = [
167
            'Status'         => $transferInfo['http_code'],
168
            'Content-Type'   => $transferInfo['content_type'],
169
            'Content-Length' => strlen($transferContent),
170
        ];
171
172
        foreach ($transferHeaders as $header) {
173
            if (preg_match('/^HTTP\/(1\.\d) +([1-5]\d{2}) +.+$/', $header, $matches)) {
174
                $responseHeaders['Protocol-Version'] = $matches[1];
175
                $responseHeaders['Status'] = $matches[2];
176
            } elseif (strpos($header, ':') !== false) {
177
                list($name, $value) = explode(':', $header, 2);
178
                $responseHeaders[$name] = trim($value);
179
            }
180
        }
181
182
        return $responseHeaders;
183
    }
184
185
    /**
186
     * Set response headers and content.
187
     *
188
     * @param ResponseInterface $response
189
     * @param array             $headers
190
     * @param string            $content
191
     *
192
     * @return ResponseInterface
193
     */
194
    protected function populateResponse(ResponseInterface $response, array $headers, $content)
195
    {
196
        if (array_key_exists('Protocol-Version', $headers)) {
197
            $response = $response->withProtocolVersion($headers['Protocol-Version']);
198
            unset($headers['Protocol-Version']);
199
        }
200
201
        if (array_key_exists('Status', $headers)) {
202
            $response = $response->withStatus((int) $headers['Status']);
203
            unset($headers['Status']);
204
        }
205
206
        foreach ($headers as $name => $value) {
207
            $response = $response->withHeader($name, (string) $value);
208
        }
209
210
        $body = new Stream('php://temp', 'wb+');
211
        $body->write($content);
212
213
        return $response->withBody($body);
214
    }
215
}
216