CurlHttpClient::curlValidate()   A
last analyzed

Complexity

Conditions 6
Paths 5

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 6
c 1
b 0
f 0
dl 0
loc 11
rs 9.2222
cc 6
nc 5
nop 2
1
<?php
2
3
namespace TelegramBot\Api\Http;
4
5
use TelegramBot\Api\HttpException;
6
use TelegramBot\Api\InvalidJsonException;
7
8
class CurlHttpClient extends AbstractHttpClient
9
{
10
    /**
11
     * HTTP codes
12
     *
13
     * @var array
14
     */
15
    private static $codes = [
16
        // Informational 1xx
17
        100 => 'Continue',
18
        101 => 'Switching Protocols',
19
        102 => 'Processing',            // RFC2518
20
        // Success 2xx
21
        200 => 'OK',
22
        201 => 'Created',
23
        202 => 'Accepted',
24
        203 => 'Non-Authoritative Information',
25
        204 => 'No Content',
26
        205 => 'Reset Content',
27
        206 => 'Partial Content',
28
        207 => 'Multi-Status',          // RFC4918
29
        208 => 'Already Reported',      // RFC5842
30
        226 => 'IM Used',               // RFC3229
31
        // Redirection 3xx
32
        300 => 'Multiple Choices',
33
        301 => 'Moved Permanently',
34
        302 => 'Found', // 1.1
35
        303 => 'See Other',
36
        304 => 'Not Modified',
37
        305 => 'Use Proxy',
38
        // 306 is deprecated but reserved
39
        307 => 'Temporary Redirect',
40
        308 => 'Permanent Redirect',    // RFC7238
41
        // Client Error 4xx
42
        400 => 'Bad Request',
43
        401 => 'Unauthorized',
44
        402 => 'Payment Required',
45
        403 => 'Forbidden',
46
        404 => 'Not Found',
47
        405 => 'Method Not Allowed',
48
        406 => 'Not Acceptable',
49
        407 => 'Proxy Authentication Required',
50
        408 => 'Request Timeout',
51
        409 => 'Conflict',
52
        410 => 'Gone',
53
        411 => 'Length Required',
54
        412 => 'Precondition Failed',
55
        413 => 'Payload Too Large',
56
        414 => 'URI Too Long',
57
        415 => 'Unsupported Media Type',
58
        416 => 'Range Not Satisfiable',
59
        417 => 'Expectation Failed',
60
        422 => 'Unprocessable Entity',                                        // RFC4918
61
        423 => 'Locked',                                                      // RFC4918
62
        424 => 'Failed Dependency',                                           // RFC4918
63
        425 => 'Reserved for WebDAV advanced collections expired proposal',   // RFC2817
64
        426 => 'Upgrade Required',                                            // RFC2817
65
        428 => 'Precondition Required',                                       // RFC6585
66
        429 => 'Too Many Requests',                                           // RFC6585
67
        431 => 'Request Header Fields Too Large',                             // RFC6585
68
        // Server Error 5xx
69
        500 => 'Internal Server Error',
70
        501 => 'Not Implemented',
71
        502 => 'Bad Gateway',
72
        503 => 'Service Unavailable',
73
        504 => 'Gateway Timeout',
74
        505 => 'HTTP Version Not Supported',
75
        506 => 'Variant Also Negotiates (Experimental)',                      // RFC2295
76
        507 => 'Insufficient Storage',                                        // RFC4918
77
        508 => 'Loop Detected',                                               // RFC5842
78
        510 => 'Not Extended',                                                // RFC2774
79
        511 => 'Network Authentication Required',                             // RFC6585
80
    ];
81
82
    /**
83
     * Default http status code
84
     */
85
    const DEFAULT_STATUS_CODE = 200;
86
87
    /**
88
     * Not Modified http status code
89
     */
90
    const NOT_MODIFIED_STATUS_CODE = 304;
91
92
    /**
93
     * CURL object
94
     *
95
     * @var resource
96
     */
97
    private $curl;
98
99
    /**
100
     * @var array
101
     */
102
    private $options;
103
104
    public function __construct(array $options = [])
105
    {
106
        $this->curl = curl_init();
0 ignored issues
show
Documentation Bug introduced by
It seems like curl_init() can also be of type CurlHandle. However, the property $curl is declared as type resource. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
107
        $this->options = $options;
108
    }
109
110
    /**
111
     * @inheritDoc
112
     */
113
    protected function doRequest($url, array $data = null)
114
    {
115
        $options = $this->options + [
116
            CURLOPT_URL => $url,
117
            CURLOPT_RETURNTRANSFER => true,
118
        ];
119
120
        if ($data) {
121
            $options[CURLOPT_POST] = true;
122
            $options[CURLOPT_POSTFIELDS] = $data;
123
        }
124
125
        return self::jsonValidate($this->execute($options));
126
    }
127
128
    /**
129
     * @inheritDoc
130
     */
131
    protected function doDownload($url)
132
    {
133
        $options = [
134
            CURLOPT_HEADER => false,
135
            CURLOPT_HTTPGET => true,
136
            CURLOPT_RETURNTRANSFER => true,
137
            CURLOPT_URL => $url,
138
        ];
139
140
        return $this->execute($options);
141
    }
142
143
    /**
144
     * @param array $options
145
     * @return string
146
     * @throws HttpException
147
     */
148
    private function execute(array $options)
149
    {
150
        curl_setopt_array($this->curl, $options);
151
152
        /** @var string|false $result */
153
        $result = curl_exec($this->curl);
154
        if ($result === false) {
0 ignored issues
show
introduced by
The condition $result === false is always false.
Loading history...
155
            throw new HttpException(curl_error($this->curl), curl_errno($this->curl));
156
        }
157
158
        self::curlValidate($this->curl, $result);
159
160
        return $result;
161
    }
162
163
    /**
164
     * @param string $jsonString
165
     * @return array
166
     * @throws InvalidJsonException
167
     */
168
    private static function jsonValidate($jsonString)
169
    {
170
        /** @var array $json */
171
        $json = json_decode($jsonString, true);
172
173
        if (json_last_error() !== JSON_ERROR_NONE) {
174
            throw new InvalidJsonException(json_last_error_msg(), json_last_error());
175
        }
176
177
        return $json;
178
    }
179
180
    /**
181
     * @param resource $curl
182
     * @param string|null $response
183
     * @return void
184
     * @throws HttpException
185
     */
186
    private static function curlValidate($curl, $response = null)
187
    {
188
        $json = json_decode((string) $response, true) ?: [];
189
190
        if (($httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE))
191
            && !in_array($httpCode, [self::DEFAULT_STATUS_CODE, self::NOT_MODIFIED_STATUS_CODE])
192
        ) {
193
            $errorDescription = array_key_exists('description', $json) ? $json['description'] : self::$codes[$httpCode];
194
            $errorParameters = array_key_exists('parameters', $json) ? $json['parameters'] : [];
195
196
            throw new HttpException($errorDescription, $httpCode, null, $errorParameters);
197
        }
198
    }
199
200
    /**
201
     * Enable proxy for curl requests. Empty string will disable proxy.
202
     *
203
     * @param string $proxyString
204
     * @param bool $socks5
205
     * @return void
206
     */
207
    public function setProxy($proxyString = '', $socks5 = false)
208
    {
209
        if (empty($proxyString)) {
210
            unset($this->options[CURLOPT_PROXY], $this->options[CURLOPT_HTTPPROXYTUNNEL], $this->options[CURLOPT_PROXYTYPE]);
211
212
            return;
213
        }
214
215
        $this->options[CURLOPT_PROXY] = $proxyString;
216
        $this->options[CURLOPT_HTTPPROXYTUNNEL] = true;
217
218
        if ($socks5) {
219
            $this->options[CURLOPT_PROXYTYPE] = CURLPROXY_SOCKS5;
220
        }
221
    }
222
223
    /**
224
     * @param string $option
225
     * @param string|int|bool $value
226
     * @return void
227
     */
228
    public function setOption($option, $value)
229
    {
230
        $this->options[$option] = $value;
231
    }
232
233
    /**
234
     * @param string $option
235
     * @return void
236
     */
237
    public function unsetOption($option)
238
    {
239
        unset($this->options[$option]);
240
    }
241
242
    /**
243
     * @return void
244
     */
245
    public function resetOptions()
246
    {
247
        $this->options = [];
248
    }
249
}
250