Completed
Push — travis ( f0ed02...4279e5 )
by Eric
192:27 queued 127:20
created

src/CurlHttpAdapter.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
/*
4
 * This file is part of the Ivory Http Adapter package.
5
 *
6
 * (c) Eric GELOEN <[email protected]>
7
 *
8
 * For the full copyright and license information, please read the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Ivory\HttpAdapter;
13
14
use Ivory\HttpAdapter\Extractor\ProtocolVersionExtractor;
15
use Ivory\HttpAdapter\Extractor\StatusCodeExtractor;
16
use Ivory\HttpAdapter\Message\InternalRequestInterface;
17
use Ivory\HttpAdapter\Message\RequestInterface;
18
use Ivory\HttpAdapter\Normalizer\BodyNormalizer;
19
use Ivory\HttpAdapter\Normalizer\HeadersNormalizer;
20
21
/**
22
 * Curl http adapter.
23
 *
24
 * @author GeLo <[email protected]>
25
 */
26
class CurlHttpAdapter extends AbstractCurlHttpAdapter
27
{
28
    /**
29
     * Creates a curl http adapter.
30
     *
31
     * @param \Ivory\HttpAdapter\ConfigurationInterface|null $configuration The configuration.
32
     */
33
    public function __construct(ConfigurationInterface $configuration = null)
34
    {
35
        parent::__construct($configuration);
36
    }
37
38
    /**
39
     * {@inheritdoc}
40
     */
41
    public function getName()
42
    {
43
        return 'curl';
44
    }
45
46
    /**
47
     * {@inheritdoc}
48
     */
49
    protected function sendInternalRequest(InternalRequestInterface $internalRequest)
50
    {
51
        $curl = $this->createCurl($internalRequest);
52
53
        try {
54
            $response = $this->createResponse($curl, curl_exec($curl), $internalRequest);
55
        } catch (HttpAdapterException $e) {
56
            curl_close($curl);
57
58
            throw $e;
59
        }
60
61
        curl_close($curl);
62
63
        return $response;
64
    }
65
66
    /**
67
     * {@inheritdoc}
68
     */
69
    protected function sendInternalRequests(array $internalRequests, $success, $error)
70
    {
71
        $curlMulti = curl_multi_init();
72
73
        $contexts = array();
74
        foreach ($internalRequests as $internalRequest) {
75
            $contexts[] = array(
76
                'curl'    => $curl = $this->createCurl($internalRequest),
77
                'request' => $internalRequest,
78
            );
79
80
            curl_multi_add_handle($curlMulti, $curl);
81
        }
82
83
        do {
84
            do {
85
                $exec = curl_multi_exec($curlMulti, $running);
86
            } while ($exec === CURLM_CALL_MULTI_PERFORM);
87
88
            while ($done = curl_multi_info_read($curlMulti)) {
89
                $curl = $done['handle'];
90
                $internalRequest = $this->resolveInternalRequest($curl, $contexts);
91
92
                try {
93
                    $response = $this->createResponse($curl, curl_multi_getcontent($curl), $internalRequest);
94
                    $response = $response->withParameter('request', $internalRequest);
95
                    call_user_func($success, $response);
96
                } catch (HttpAdapterException $e) {
97
                    $e->setRequest($internalRequest);
98
                    call_user_func($error, $e);
99
                }
100
101
                curl_multi_remove_handle($curlMulti, $curl);
102
                curl_close($curl);
103
            }
104
        } while ($running);
105
106
        curl_multi_close($curlMulti);
107
    }
108
109
    /**
110
     * Creates a curl resource.
111
     *
112
     * @param \Ivory\HttpAdapter\Message\InternalRequestInterface $internalRequest The internal request.
113
     *
114
     * @return resource The curl resource.
115
     */
116
    private function createCurl(InternalRequestInterface $internalRequest)
117
    {
118
        $curl = curl_init();
119
120
        curl_setopt($curl, CURLOPT_URL, (string) $internalRequest->getUri());
121
        curl_setopt($curl, CURLOPT_FOLLOWLOCATION, false);
122
        curl_setopt($curl, CURLOPT_HTTP_VERSION, $this->prepareProtocolVersion($internalRequest));
123
        curl_setopt($curl, CURLOPT_HEADER, true);
124
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
125
        curl_setopt($curl, CURLOPT_HTTPHEADER, $this->prepareHeaders($internalRequest, false, false));
126
127
        $this->configureTimeout($curl, 'CURLOPT_TIMEOUT');
128
        $this->configureTimeout($curl, 'CURLOPT_CONNECTTIMEOUT');
129
130
        $files = $internalRequest->getFiles();
131
132
        if (!empty($files) && $this->isSafeUpload()) {
133
            curl_setopt($curl, CURLOPT_SAFE_UPLOAD, true);
134
        }
135
136
        switch ($internalRequest->getMethod()) {
137
            case RequestInterface::METHOD_HEAD:
138
                curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $internalRequest->getMethod());
139
                curl_setopt($curl, CURLOPT_NOBODY, true);
140
                break;
141
142
            case RequestInterface::METHOD_TRACE:
143
                curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $internalRequest->getMethod());
144
                break;
145
146 View Code Duplication
            case RequestInterface::METHOD_POST:
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
147
                curl_setopt($curl, CURLOPT_POST, true);
148
                curl_setopt($curl, CURLOPT_POSTFIELDS, $this->prepareContent($internalRequest));
149
                break;
150
151
            case RequestInterface::METHOD_PUT:
152
            case RequestInterface::METHOD_PATCH:
153
            case RequestInterface::METHOD_DELETE:
154 View Code Duplication
            case RequestInterface::METHOD_OPTIONS:
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
155
                curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $internalRequest->getMethod());
156
                curl_setopt($curl, CURLOPT_POSTFIELDS, $this->prepareContent($internalRequest));
157
                break;
158
        }
159
160
        return $curl;
161
    }
162
163
    /**
164
     * Configures a timeout.
165
     *
166
     * @param resource $curl The curl resource.
167
     * @param string   $type The timeout type.
168
     */
169
    private function configureTimeout($curl, $type)
170
    {
171
        if (defined($type.'_MS')) {
172
            curl_setopt($curl, constant($type.'_MS'), $this->getConfiguration()->getTimeout() * 1000);
173
        } else { // @codeCoverageIgnoreStart
174
            curl_setopt($curl, constant($type), $this->getConfiguration()->getTimeout());
175
        } // @codeCoverageIgnoreEnd
176
    }
177
178
    /**
179
     * Creates a response.
180
     *
181
     * @param resource                                            $curl            The curl resource.
182
     * @param string|boolean|null                                 $data            The data.
183
     * @param \Ivory\HttpAdapter\Message\InternalRequestInterface $internalRequest The internal request.
184
     *
185
     * @throws \Ivory\HttpAdapter\HttpAdapterException If an error occurred.
186
     *
187
     * @return \Ivory\HttpAdapter\Message\ResponseInterface The response.
188
     */
189
    private function createResponse($curl, $data, InternalRequestInterface $internalRequest)
190
    {
191
        if (empty($data)) {
192
            throw HttpAdapterException::cannotFetchUri(
193
                (string) $internalRequest->getUri(),
194
                $this->getName(),
195
                curl_error($curl)
196
            );
197
        }
198
199
        $headers = substr($data, 0, $headersSize = curl_getinfo($curl, CURLINFO_HEADER_SIZE));
200
201
        return $this->getConfiguration()->getMessageFactory()->createResponse(
202
            StatusCodeExtractor::extract($headers),
203
            ProtocolVersionExtractor::extract($headers),
204
            HeadersNormalizer::normalize($headers),
205
            BodyNormalizer::normalize(substr($data, $headersSize), $internalRequest->getMethod())
206
        );
207
    }
208
209
    /**
210
     * Resolves the internal request.
211
     *
212
     * @param resource $curl     The curl resource.
213
     * @param array    $contexts The contexts.
214
     *
215
     * @return \Ivory\HttpAdapter\Message\InternalRequestInterface The internal request.
216
     */
217
    private function resolveInternalRequest($curl, array $contexts)
218
    {
219
        foreach ($contexts as $context) {
220
            if ($context['curl'] === $curl) {
221
                break;
222
            }
223
        }
224
225
        return $context['request'];
226
    }
227
}
228