Completed
Pull Request — master (#129)
by Eric
257:01 queued 192:06
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 1585
    public function __construct(ConfigurationInterface $configuration = null)
34
    {
35 1585
        parent::__construct($configuration);
36 1584
    }
37
38
    /**
39
     * {@inheritdoc}
40
     */
41 91
    public function getName()
42
    {
43 91
        return 'curl';
44
    }
45
46
    /**
47
     * {@inheritdoc}
48
     */
49 1476
    protected function sendInternalRequest(InternalRequestInterface $internalRequest)
50
    {
51 1476
        $curl = $this->createCurl($internalRequest);
52
53
        try {
54 1476
            $response = $this->createResponse($curl, curl_exec($curl), $internalRequest);
55 1397
        } catch (HttpAdapterException $e) {
56 54
            curl_close($curl);
57
58 54
            throw $e;
59
        }
60
61 1422
        curl_close($curl);
62
63 1422
        return $response;
64
    }
65
66
    /**
67
     * {@inheritdoc}
68
     */
69 36
    protected function sendInternalRequests(array $internalRequests, $success, $error)
70
    {
71 36
        $curlMulti = curl_multi_init();
72
73 36
        $contexts = array();
74 36
        foreach ($internalRequests as $internalRequest) {
75 36
            $contexts[] = array(
76 36
                'curl'    => $curl = $this->createCurl($internalRequest),
77 36
                'request' => $internalRequest,
78
            );
79
80 36
            curl_multi_add_handle($curlMulti, $curl);
81 34
        }
82
83
        do {
84
            do {
85 36
                $exec = curl_multi_exec($curlMulti, $running);
86 36
            } while ($exec === CURLM_CALL_MULTI_PERFORM);
87
88 36
            while ($done = curl_multi_info_read($curlMulti)) {
89 36
                $curl = $done['handle'];
90 36
                $internalRequest = $this->resolveInternalRequest($curl, $contexts);
91
92
                try {
93 36
                    $response = $this->createResponse($curl, curl_multi_getcontent($curl), $internalRequest);
94 36
                    $response = $response->withParameter('request', $internalRequest);
95 36
                    call_user_func($success, $response);
96 35
                } catch (HttpAdapterException $e) {
97 18
                    $e->setRequest($internalRequest);
98 18
                    call_user_func($error, $e);
99
                }
100
101 36
                curl_multi_remove_handle($curlMulti, $curl);
102 36
                curl_close($curl);
103 34
            }
104 36
        } while ($running);
105
106 36
        curl_multi_close($curlMulti);
107 36
    }
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 1512
    private function createCurl(InternalRequestInterface $internalRequest)
117
    {
118 1512
        $curl = curl_init();
119
120 1512
        curl_setopt($curl, CURLOPT_URL, (string) $internalRequest->getUri());
121 1512
        curl_setopt($curl, CURLOPT_FOLLOWLOCATION, false);
122 1512
        curl_setopt($curl, CURLOPT_HTTP_VERSION, $this->prepareProtocolVersion($internalRequest));
123 1512
        curl_setopt($curl, CURLOPT_HEADER, true);
124 1512
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
125 1512
        curl_setopt($curl, CURLOPT_HTTPHEADER, $this->prepareHeaders($internalRequest, false, false));
126
127 1512
        $this->configureTimeout($curl, 'CURLOPT_TIMEOUT');
128 1512
        $this->configureTimeout($curl, 'CURLOPT_CONNECTTIMEOUT');
129
130 1512
        $files = $internalRequest->getFiles();
131
132 1512
        if (!empty($files) && $this->isSafeUpload()) {
133 160
            curl_setopt($curl, CURLOPT_SAFE_UPLOAD, true);
134 150
        }
135
136 1512
        switch ($internalRequest->getMethod()) {
137 1512
            case RequestInterface::METHOD_HEAD:
138 144
                curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $internalRequest->getMethod());
139 144
                curl_setopt($curl, CURLOPT_NOBODY, true);
140 144
                break;
141
142 1404
            case RequestInterface::METHOD_TRACE:
143 144
                curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $internalRequest->getMethod());
144 144
                break;
145
146 1296 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 252
                curl_setopt($curl, CURLOPT_POST, true);
148 252
                curl_setopt($curl, CURLOPT_POSTFIELDS, $this->prepareContent($internalRequest));
149 252
                break;
150
151 1080
            case RequestInterface::METHOD_PUT:
152 1069
            case RequestInterface::METHOD_PATCH:
153 1058
            case RequestInterface::METHOD_DELETE:
154 1047 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 828
                curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $internalRequest->getMethod());
156 828
                curl_setopt($curl, CURLOPT_POSTFIELDS, $this->prepareContent($internalRequest));
157 828
                break;
158 1428
        }
159
160 1512
        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 1512
    private function configureTimeout($curl, $type)
170
    {
171 1512
        if (defined($type.'_MS')) {
172 1512
            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 1512
    }
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 1512
    private function createResponse($curl, $data, InternalRequestInterface $internalRequest)
190
    {
191 1512
        if (empty($data)) {
192 72
            throw HttpAdapterException::cannotFetchUri(
193 72
                (string) $internalRequest->getUri(),
194 72
                $this->getName(),
195 68
                curl_error($curl)
196 68
            );
197
        }
198
199 1458
        $headers = substr($data, 0, $headersSize = curl_getinfo($curl, CURLINFO_HEADER_SIZE));
200
201 1458
        return $this->getConfiguration()->getMessageFactory()->createResponse(
202 1458
            StatusCodeExtractor::extract($headers),
203 1458
            ProtocolVersionExtractor::extract($headers),
204 1458
            HeadersNormalizer::normalize($headers),
205 1458
            BodyNormalizer::normalize(substr($data, $headersSize), $internalRequest->getMethod())
206 1377
        );
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 36
    private function resolveInternalRequest($curl, array $contexts)
218
    {
219 36
        foreach ($contexts as $context) {
220 36
            if ($context['curl'] === $curl) {
221 36
                break;
222
            }
223 34
        }
224
225 36
        return $context['request'];
226
    }
227
}
228