Completed
Pull Request — master (#129)
by Eric
257:01 queued 192:06
created

src/CurlHttpAdapter.php (1 issue)

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:
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:
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'];
0 ignored issues
show
The variable $context seems to be defined by a foreach iteration on line 219. Are you sure the iterator is never empty, otherwise this variable is not defined?

It seems like you are relying on a variable being defined by an iteration:

foreach ($a as $b) {
}

// $b is defined here only if $a has elements, for example if $a is array()
// then $b would not be defined here. To avoid that, we recommend to set a
// default value for $b.


// Better
$b = 0; // or whatever default makes sense in your context
foreach ($a as $b) {
}

// $b is now guaranteed to be defined here.
Loading history...
226
    }
227
}
228