Passed
Branch master (e903e1)
by
unknown
02:26
created

Request   A

Complexity

Total Complexity 26

Size/Duplication

Total Lines 200
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 26
lcom 1
cbo 3
dl 0
loc 200
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
B extract() 0 24 6
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                    $curl_options
93
	 *
94
	 * @return \chillerlan\TinyCurl\ResponseInterface
95
	 * @throws \chillerlan\TinyCurl\RequestException
96
	 */
97
	public function fetch(URL $url, array $curl_options = []):ResponseInterface {
98
		$this->initCurl();
99
100
		$headers = $this->normalizeHeaders($url->headers);
101
102
		if(in_array($url->method, ['POST', 'PUT'])){
103
104
			$curl_options += $url->method === 'PUT'
105
				? [CURLOPT_CUSTOMREQUEST => 'PUT']
106
				: [CURLOPT_POST => true];
107
108
			if(!isset($headers['Content-type']) && $url->method === 'POST' && is_array($url->body)){
109
				$headers += ['Content-type: application/x-www-form-urlencoded'];
110
				$body     = http_build_query($url->body, '', '&', PHP_QUERY_RFC1738);
111
			}
112
			else{
113
				$body = $url->body;
114
			}
115
116
			$curl_options += [CURLOPT_POSTFIELDS => $body];
117
		}
118
		else{
119
			$curl_options += [CURLOPT_CUSTOMREQUEST => $url->method];
120
		}
121
122
		$headers += [
123
			'Host: '.$url->host,
124
			'Connection: close',
125
		];
126
127
		if($this->options->max_redirects > 0){
128
			$curl_options += [
129
				CURLOPT_FOLLOWLOCATION => true,
130
				CURLOPT_MAXREDIRS      => $this->options->max_redirects,
131
			];
132
		}
133
134
		$curl_options += [CURLOPT_HTTPHEADER => $headers];
135
136
		curl_setopt_array($this->curl, $curl_options);
137
138
		return $this->getResponse((string)$url);
139
	}
140
141
	/**
142
	 * @param array $headers
143
	 *
144
	 * @return array
145
	 */
146
	public function normalizeHeaders(array $headers):array {
147
		$normalized_headers = [];
148
149
		foreach($headers as $key => $val){
150
151
			if(is_numeric($key)){
152
				$header = explode(':', $val, 2);
153
154
				if(count($header) === 2){
155
					$key = $header[0];
156
					$val = $header[1];
157
				}
158
				else{
159
					continue;
160
				}
161
			}
162
163
			$key = ucfirst(strtolower($key));
164
165
			$normalized_headers[$key] = trim($key).': '.trim($val);
166
		}
167
168
		return $normalized_headers;
169
	}
170
171
	/**
172
	 * @param string $url
173
	 *
174
	 * @return array<string>
175
	 */
176
	public function extractShortUrl(string $url):array {
177
		$urls = [$url];
178
179
		while($url = $this->extract($url)){
180
			$urls[] = $url;
181
		}
182
183
		return $urls;
184
	}
185
186
	/**
187
	 * @param string $url
188
	 *
189
	 * @return string
190
	 * @link http://www.internoetics.com/2012/11/12/resolve-short-urls-to-their-destination-url-php-api/
191
	 */
192
	protected function extract(string $url):string {
193
		$this->initCurl();
194
195
		curl_setopt_array($this->curl, [
196
			CURLOPT_FOLLOWLOCATION => false
197
		]);
198
199
		$response = $this->getResponse($url);
200
201
		$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...
202
		$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...
203
204
		switch(true){
205
			// check curl_info()
206
			case in_array($info->http_code, range(300, 308), true) && isset($info->redirect_url) && !empty($info->redirect_url):
207
				return $info->redirect_url;
208
			// look for a location header
209
			case isset($headers->location) && !empty($headers->location):
210
				return $headers->location; // @codeCoverageIgnore
211
			default:
212
				return '';
213
		}
214
215
	}
216
217
}
218