Passed
Branch master (114a3b)
by
unknown
04:22
created

Request   A

Complexity

Total Complexity 27

Size/Duplication

Total Lines 202
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 27
lcom 1
cbo 3
dl 0
loc 202
rs 10
c 2
b 0
f 0

9 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 3 2
A setOptions() 0 5 1
A getOptions() 0 3 1
A getResponse() 0 5 1
A initCurl() 0 16 2
C fetch() 0 43 7
B normalizeHeaders() 0 24 4
A extractShortUrl() 0 9 2
C extract() 0 27 7
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
/**
16
 *
17
 */
18
class Request{
19
20
	/**
21
	 * The cURL connection
22
	 *
23
	 * @var resource
24
	 */
25
	protected $curl;
26
27
	/**
28
	 * @var \chillerlan\TinyCurl\RequestOptions
29
	 */
30
	protected $options;
31
32
	/**
33
	 * Request constructor.
34
	 *
35
	 * @param \chillerlan\TinyCurl\RequestOptions $options
0 ignored issues
show
Documentation introduced by
Should the type for parameter $options not be null|RequestOptions?

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...
36
	 */
37
	public function __construct(RequestOptions $options = null){
38
		$this->setOptions($options ?: new RequestOptions);
39
	}
40
41
	/**
42
	 * @param \chillerlan\TinyCurl\RequestOptions $options
43
	 *
44
	 * @return \chillerlan\TinyCurl\Request
45
	 */
46
	public function setOptions(RequestOptions $options):Request {
47
		$this->options = $options;
48
49
		return $this;
50
	}
51
52
	/**
53
	 * @return \chillerlan\TinyCurl\RequestOptions
54
	 */
55
	public function getOptions():RequestOptions{
56
		return $this->options;
57
	}
58
59
	/**
60
	 * @param string $url
61
	 *
62
	 * @return \chillerlan\TinyCurl\ResponseInterface
63
	 */
64
	protected function getResponse(string $url):ResponseInterface {
65
		curl_setopt($this->curl, CURLOPT_URL, $url);
66
67
		return new Response($this->curl);
68
	}
69
70
	/**
71
	 * @return void
72
	 */
73
	protected function initCurl(){
74
		$this->curl = curl_init();
75
76
		curl_setopt_array($this->curl, [
77
			CURLOPT_HEADER         => false,
78
			CURLOPT_RETURNTRANSFER => true,
79
			CURLOPT_USERAGENT      => $this->options->user_agent,
80
			CURLOPT_PROTOCOLS      => CURLPROTO_HTTP|CURLPROTO_HTTPS,
81
			CURLOPT_SSL_VERIFYPEER => true,
82
			CURLOPT_SSL_VERIFYHOST => 2, // Support for value 1 removed in cURL 7.28.1
83
			CURLOPT_CAINFO         => is_file($this->options->ca_info) ? $this->options->ca_info : null,
84
			CURLOPT_TIMEOUT        => $this->options->timeout,
85
		]);
86
87
		curl_setopt_array($this->curl, $this->options->curl_options);
88
	}
89
90
	/**
91
	 * @param \chillerlan\TinyCurl\URL $url
92
	 * @param array|null               $curl_options
93
	 *
94
	 * @return \chillerlan\TinyCurl\ResponseInterface
95
	 */
96
	public function fetch(URL $url, array $curl_options = []):ResponseInterface {
97
		$this->initCurl();
98
99
		$method  = strtoupper($url->method);
100
		$headers = $this->normalizeHeaders($url->headers);
101
102
		if(in_array($method, ['PATCH', 'POST', 'PUT', 'DELETE'])){
103
104
			$curl_options += in_array($method, ['PATCH', 'PUT', 'DELETE'])
105
				? [CURLOPT_CUSTOMREQUEST => $method]
106
				: [CURLOPT_POST => true];
107
108
			$body = $url->body;
109
110
			if(!isset($headers['Content-type']) && $method === 'POST' && is_array($body)){
111
				$headers += ['Content-type: application/x-www-form-urlencoded'];
112
				$body = http_build_query($body, '', '&', PHP_QUERY_RFC1738);
113
			}
114
115
			$curl_options += [CURLOPT_POSTFIELDS => $body];
116
		}
117
		else{
118
			$curl_options += [CURLOPT_CUSTOMREQUEST => $method];
119
		}
120
121
		$headers += [
122
			'Host: '.$url->host,
123
			'Connection: close',
124
		];
125
126
		if($this->options->max_redirects > 0){
127
			$curl_options += [
128
				CURLOPT_FOLLOWLOCATION => true,
129
				CURLOPT_MAXREDIRS      => $this->options->max_redirects,
130
			];
131
		}
132
133
		$curl_options += [CURLOPT_HTTPHEADER => $headers];
134
135
		curl_setopt_array($this->curl, $curl_options);
136
137
		return $this->getResponse((string)$url);
138
	}
139
140
	/**
141
	 * @param array $headers
142
	 *
143
	 * @return array
144
	 */
145
	public function normalizeHeaders(array $headers):array {
146
		$normalized_headers = [];
147
148
		foreach($headers as $key => $val){
149
150
			if(is_numeric($key)){
151
				$header = explode(':', $val, 2);
152
153
				if(count($header) === 2){
154
					$key = $header[0];
155
					$val = $header[1];
156
				}
157
				else{
158
					continue;
159
				}
160
			}
161
162
			$key = ucfirst(strtolower($key));
163
164
			$normalized_headers[$key] = trim($key).': '.trim($val);
165
		}
166
167
		return $normalized_headers;
168
	}
169
170
	/**
171
	 * @param string $url
172
	 *
173
	 * @return array<string>
174
	 */
175
	public function extractShortUrl(string $url):array {
176
		$urls = [$url];
177
178
		while($url = $this->extract($url)){
179
			$urls[] = $url;
180
		}
181
182
		return $urls;
183
	}
184
185
	/**
186
	 * @param string $url
187
	 *
188
	 * @return string
189
	 * @link http://www.internoetics.com/2012/11/12/resolve-short-urls-to-their-destination-url-php-api/
190
	 */
191
	protected function extract(string $url):string {
192
		$this->initCurl();
193
194
		curl_setopt_array($this->curl, [
195
			CURLOPT_FOLLOWLOCATION => false
196
		]);
197
198
		$response = $this->getResponse($url);
199
200
		if(!$response instanceof ResponseInterface){
201
			return ''; // @codeCoverageIgnore
202
		}
203
204
		$info    = $response->info;
0 ignored issues
show
Bug introduced by
Accessing info on the interface chillerlan\TinyCurl\ResponseInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
205
		$headers = $response->headers;
0 ignored issues
show
Bug introduced by
Accessing headers on the interface chillerlan\TinyCurl\ResponseInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
206
207
		// check curl_info()
208
		if(in_array($info->http_code, range(300, 308), true) && isset($info->redirect_url) && !empty($info->redirect_url)){
209
			return $info->redirect_url;
210
		}
211
		// look for a location header
212
		elseif(isset($headers->location) && !empty($headers->location)){
213
			return $headers->location; // @codeCoverageIgnore
214
		}
215
216
		return '';
217
	}
218
219
}
220