CurlHelper   A
last analyzed

Complexity

Total Complexity 30

Size/Duplication

Total Lines 134
Duplicated Lines 0 %

Coupling/Cohesion

Components 0
Dependencies 5

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
dl 0
loc 134
ccs 0
cts 97
cp 0
rs 10
c 0
b 0
f 0
wmc 30
lcom 0
cbo 5

3 Methods

Rating   Name   Duplication   Size   Complexity  
C send() 0 35 13
A getUserAgent() 0 9 4
C exec() 0 57 13
1
<?php
2
declare(strict_types=1);
3
/**
4
 * Minotaur
5
 *
6
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
7
 * use this file except in compliance with the License. You may obtain a copy of
8
 * the License at
9
 *
10
 * http://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing, software
13
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15
 * License for the specific language governing permissions and limitations under
16
 * the License.
17
 *
18
 * @copyright 2015-2017 Appertly
19
 * @license   Apache-2.0
20
 */
21
namespace Minotaur\Net;
22
23
/**
24
 * A trait for using cURL
25
 */
26
trait CurlHelper
27
{
28
    /**
29
     * Does a decent job of sending off a PSR-7 request using cURL
30
     *
31
     * @param \Psr\Http\Message\RequestInterface $request The request
32
     * @return string The response
33
     * @throws \Minotaur\Net\Exception\Unreachable if the remote server cannot be reached
34
     * @throws \Minotaur\Net\Exception\Misconfigured if cURL was incorrectly configured
35
     * @throws \Minotaur\Net\Exception\Unexpected if the remote server returned an error
36
     */
37
    protected function send(\Psr\Http\Message\RequestInterface $request): string
38
    {
39
        $ch = curl_init((string)$request->getUri());
40
        if ($request->getMethod() != 'GET') {
41
            curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $request->getMethod());
42
            curl_setopt($ch, CURLOPT_POSTFIELDS, (string)$request->getBody());
43
        }
44
        $uri = $request->getUri();
45
        if ($uri->getScheme() == 'http' && $uri->getPort() !== null && $uri->getPort() !== 80) {
46
            curl_setopt($ch, CURLOPT_PORT, $uri->getPort());
47
        } elseif ($uri->getScheme() == 'https' && $uri->getPort() !== null && $uri->getPort() !== 443) {
48
                curl_setopt($ch, CURLOPT_PORT, $uri->getPort());
49
        }
50
        $version = $request->getProtocolVersion();
51
        if ($version == 1.1) {
52
            curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
53
        } elseif ($version == 2.0) {
54
            curl_setopt(
55
                $ch,
56
                CURLOPT_HTTP_VERSION,
57
                defined('CURL_HTTP_VERSION_2_0') ? constant('CURL_HTTP_VERSION_2_0') : 3
58
            );
59
        } else {
60
            curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
61
        }
62
        $headers = [];
63
        foreach ($request->getHeaders() as $k => $v) {
64
            $headers[] = "$k: " . implode(", ", $v);
65
        }
66
        if (!$request->hasHeader('User-Agent')) {
67
            $headers[] = "User-Agent: " . $this->getUserAgent();
68
        }
69
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
70
        return $this->exec($ch);
71
    }
72
73
    /**
74
     * Gets a User-Agent string.
75
     *
76
     * @return string A reasonable user-agent string
77
     */
78
    protected function getUserAgent(): string
79
    {
80
        if (extension_loaded('curl') && function_exists('curl_version')) {
81
            return 'curl/' . curl_version()['version'];
82
        } else {
83
            $ua = ini_get('user_agent');
84
            return strlen($ua) > 0 ? $ua : 'PHP/' . PHP_VERSION;
85
        }
86
    }
87
88
    /**
89
     * A convenience wrapper around `curl_multi_exec`
90
     *
91
     * Pass a cURL handle, or, more simply, a string containing a URL (and the
92
     * cURL handle will be created for you), and the cURL request will be executed
93
     * and the `string` result will be retuned.
94
     *
95
     * @param resource|string $urlOrHandle - An existing cURL handle or a
96
     *     `string` URL. String URLs will create a default cURL GET handle.
97
     * @return - The `string` result of the cURL request.
0 ignored issues
show
Documentation introduced by
The doc-type - could not be parsed: Unknown type name "-" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
98
     * @throws \Minotaur\Net\Exception\Unreachable if the remote server cannot be reached
99
     * @throws \Minotaur\Net\Exception\Misconfigured if cURL was incorrectly configured
100
     * @throws \Minotaur\Net\Exception\Unexpected if the remote server returned an error
101
     */
102
    protected function exec($urlOrHandle): string
103
    {
104
        if (is_string($urlOrHandle)) {
105
            $ch = curl_init($urlOrHandle);
106
        } elseif (is_resource($urlOrHandle) && get_resource_type($urlOrHandle) == "curl") {
107
            $ch = $urlOrHandle;
108
        } else {
109
            throw new \InvalidArgumentException(__FUNCTION__ . " expects string or cURL handle");
110
        }
111
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
112
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
113
        curl_setopt($ch, CURLOPT_FAILONERROR, false);
114
        curl_setopt($ch, CURLINFO_HEADER_OUT, true);
115
        $mh = curl_multi_init();
116
        curl_multi_add_handle($mh, $ch);
117
        $sleep_ms = 10;
118
        do {
119
            $active = 1;
120
            do {
121
                $status = curl_multi_exec($mh, $active);
122
            } while ($status == CURLM_CALL_MULTI_PERFORM);
123
            if (!$active) {
124
                break;
125
            }
126
            $select = curl_multi_select($mh);
127
            /* If cURL is built without ares support, DNS queries don't have a socket
128
            * to wait on, so curl_multi_await() (and curl_select() in PHP5) will return
129
            * -1, and polling is required.
130
            */
131
            if ($select == -1) {
132
                sleep($sleep_ms);
133
                if ($sleep_ms < 1000) {
134
                    $sleep_ms *= 2;
135
                }
136
            } else {
137
                $sleep_ms = 10;
138
            }
139
        } while ($status === CURLM_OK);
140
        $info = curl_multi_info_read($mh);
141
        $code = $info['result'];
142
        $content = curl_multi_getcontent($ch);
143
        $cinfo = curl_getinfo($ch);
144
        if ($code !== CURLE_OK) {
145
            if (Exception\Unreachable::isUsable($code)) {
146
                throw new Exception\Unreachable($cinfo, curl_error($ch), $code);
147
            } elseif (Exception\Misconfigured::isUsable($code)) {
148
                throw new Exception\Misconfigured($cinfo, curl_error($ch), $code);
149
            } else {
150
                throw new Exception\Unexpected($content, $cinfo, curl_error($ch), $code);
151
            }
152
        } elseif ($cinfo['http_code'] >= 400) {
153
            throw new Exception\Unexpected($content, $cinfo, "The requested URL returned error: " . $cinfo['http_code'], CURLE_HTTP_NOT_FOUND);
154
        }
155
        curl_multi_remove_handle($mh, $ch);
156
        curl_multi_close($mh);
157
        return (string) $content;
158
    }
159
}
160