Passed
Branch master (abb9ab)
by
unknown
02:22
created

src/Request.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
 * Class Request
4
 *
5
 * @filesource   Request.php
6
 * @created      13.02.2016
7
 * @package      chillerlan\TinyCurl
8
 * @author       Smiley <[email protected]>
9
 * @copyright    2016 Smiley
10
 * @license      MIT
11
 */
12
13
namespace chillerlan\TinyCurl;
14
15
use chillerlan\Traits\ContainerInterface;
16
17
/**
18
 *
19
 */
20
class Request{
21
22
	/**
23
	 * The cURL connection
24
	 *
25
	 * @var resource
26
	 */
27
	protected $curl;
28
29
	/**
30
	 * @var \chillerlan\TinyCurl\RequestOptions
31
	 */
32
	protected $options;
33
34
	/**
35
	 * Request constructor.
36
	 *
37
	 * @param \chillerlan\Traits\ContainerInterface $options
0 ignored issues
show
Should the type for parameter $options not be null|ContainerInterface?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
38
	 */
39
	public function __construct(ContainerInterface $options = null){
40
		$this->setOptions($options ?: new RequestOptions);
41
	}
42
43
	/**
44
	 * @param \chillerlan\Traits\ContainerInterface $options
45
	 *
46
	 * @return \chillerlan\TinyCurl\Request
47
	 */
48
	public function setOptions(ContainerInterface $options):Request {
49
		$this->options = $options;
0 ignored issues
show
Documentation Bug introduced by
$options is of type object<chillerlan\Traits\ContainerInterface>, but the property $options was declared to be of type object<chillerlan\TinyCurl\RequestOptions>. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof 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 given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
50
51
		return $this;
52
	}
53
54
	/**
55
	 * @return \chillerlan\Traits\ContainerInterface
56
	 */
57
	public function getOptions():ContainerInterface{
58
		return $this->options;
59
	}
60
61
	/**
62
	 * @param string $url
63
	 *
64
	 * @return \chillerlan\TinyCurl\ResponseInterface
65
	 */
66
	protected function getResponse(string $url):ResponseInterface {
67
		curl_setopt($this->curl, CURLOPT_URL, $url);
68
69
		return new Response($this->curl);
70
	}
71
72
	/**
73
	 * @return void
74
	 */
75
	protected function initCurl(){
76
		$this->curl = curl_init();
77
78
		curl_setopt_array($this->curl, [
79
			CURLOPT_HEADER         => false,
80
			CURLOPT_RETURNTRANSFER => true,
81
			CURLOPT_USERAGENT      => $this->options->user_agent,
82
			CURLOPT_PROTOCOLS      => CURLPROTO_HTTP|CURLPROTO_HTTPS,
83
			CURLOPT_SSL_VERIFYPEER => true,
84
			CURLOPT_SSL_VERIFYHOST => 2, // Support for value 1 removed in cURL 7.28.1
85
			CURLOPT_CAINFO         => is_file($this->options->ca_info) ? $this->options->ca_info : null,
86
			CURLOPT_TIMEOUT        => $this->options->timeout,
87
		]);
88
89
		curl_setopt_array($this->curl, $this->options->curl_options);
90
	}
91
92
	/**
93
	 * @param \chillerlan\TinyCurl\URL $url
94
	 * @param array|null               $curl_options
95
	 *
96
	 * @return \chillerlan\TinyCurl\ResponseInterface
97
	 */
98
	public function fetch(URL $url, array $curl_options = []):ResponseInterface {
99
		$this->initCurl();
100
101
		$method  = strtoupper($url->method);
102
		$headers = $this->normalizeHeaders($url->headers);
103
104
		if(in_array($method, ['PATCH', 'POST', 'PUT', 'DELETE'])){
105
106
			$curl_options += in_array($method, ['PATCH', 'PUT', 'DELETE'])
107
				? [CURLOPT_CUSTOMREQUEST => $method]
108
				: [CURLOPT_POST => true];
109
110
			$body = $url->body;
111
112
			if(!isset($headers['Content-type']) && $method === 'POST' && is_array($body)){
113
				$headers += ['Content-type: application/x-www-form-urlencoded'];
114
				$body = http_build_query($body, '', '&', PHP_QUERY_RFC1738);
115
			}
116
117
			$curl_options += [CURLOPT_POSTFIELDS => $body];
118
		}
119
		else{
120
			$curl_options += [CURLOPT_CUSTOMREQUEST => $method];
121
		}
122
123
		$headers += [
124
			'Host: '.$url->host,
125
			'Connection: close',
126
		];
127
128
		if($this->options->max_redirects > 0){
129
			$curl_options += [
130
				CURLOPT_FOLLOWLOCATION => true,
131
				CURLOPT_MAXREDIRS      => $this->options->max_redirects,
132
			];
133
		}
134
135
		$curl_options += [CURLOPT_HTTPHEADER => $headers];
136
137
		curl_setopt_array($this->curl, $curl_options);
138
139
		return $this->getResponse((string)$url);
140
	}
141
142
	/**
143
	 * @param array $headers
144
	 *
145
	 * @return array
146
	 */
147
	public function normalizeHeaders(array $headers):array {
148
		$normalized_headers = [];
149
150
		foreach($headers as $key => $val){
151
152
			if(is_numeric($key)){
153
				$header = explode(':', $val, 2);
154
155
				if(count($header) === 2){
156
					$key = $header[0];
157
					$val = $header[1];
158
				}
159
				else{
160
					continue;
161
				}
162
			}
163
164
			$key = ucfirst(strtolower($key));
165
166
			$normalized_headers[$key] = trim($key).': '.trim($val);
167
		}
168
169
		return $normalized_headers;
170
	}
171
172
	/**
173
	 * @param string $url
174
	 *
175
	 * @return array<string>
176
	 */
177
	public function extractShortUrl(string $url):array {
178
		$urls = [$url];
179
180
		while($url = $this->extract($url)){
181
			$urls[] = $url;
182
		}
183
184
		return $urls;
185
	}
186
187
	/**
188
	 * @param string $url
189
	 *
190
	 * @return string
191
	 * @link http://www.internoetics.com/2012/11/12/resolve-short-urls-to-their-destination-url-php-api/
192
	 */
193
	protected function extract(string $url):string {
194
		$this->initCurl();
195
196
		curl_setopt_array($this->curl, [
197
			CURLOPT_FOLLOWLOCATION => false
198
		]);
199
200
		$response = $this->getResponse($url);
201
202
		if(!$response instanceof ResponseInterface){
203
			return ''; // @codeCoverageIgnore
204
		}
205
206
		$info    = $response->info;
207
		$headers = $response->headers;
208
209
		// check curl_info()
210
		if(in_array($info->http_code, range(300, 308), true) && isset($info->redirect_url) && !empty($info->redirect_url)){
211
			return $info->redirect_url;
212
		}
213
		// look for a location header
214
		elseif(isset($headers->location) && !empty($headers->location)){
215
			return $headers->location; // @codeCoverageIgnore
216
		}
217
218
		return '';
219
	}
220
221
}
222