Passed
Push — master ( 089fbf...f223f6 )
by Sebastian
04:05
created

RequestHelper   A

Complexity

Total Complexity 33

Size/Duplication

Total Lines 417
Duplicated Lines 0 %

Importance

Changes 5
Bugs 0 Features 0
Metric Value
wmc 33
eloc 113
c 5
b 0
f 0
dl 0
loc 417
rs 9.76

21 Methods

Rating   Name   Duplication   Size   Complexity  
A getBody() 0 3 1
A createCURL() 0 45 5
A addContent() 0 5 1
A getResponse() 0 3 1
A getTimeout() 0 3 1
A setHeader() 0 5 1
A enableLogging() 0 5 1
A renderHeaders() 0 11 2
A __construct() 0 7 1
A getMimeBoundary() 0 3 1
A send() 0 37 4
A getResponseHeader() 0 9 2
A addFile() 0 5 1
A setTimeout() 0 5 1
A getEOL() 0 3 1
A disableSSLChecks() 0 4 1
A addVariable() 0 5 1
A configureLogging() 0 24 3
A getMimeBody() 0 3 1
A getHeader() 0 8 2
A getHeaders() 0 3 1
1
<?php
2
/**
3
 * File containing the {@link RequestHelper} class.
4
 * @package Application Utils
5
 * @subpackage RequestHelper
6
 * @see RequestHelper
7
 */
8
9
namespace AppUtils;
10
11
/**
12
 * Handles sending POST requests with file attachments and regular variables.
13
 * Creates the raw request headers required for the request and sends them
14
 * using file_get_contents with the according context parameters.
15
 *
16
 * @package Application Utils
17
 * @subpackage RequestHelper
18
 * @author Sebastian Mordziol <[email protected]>
19
 */
20
class RequestHelper
21
{
22
    const FILETYPE_TEXT = 'text/plain';
23
    const FILETYPE_XML = 'text/xml';
24
    const FILETYPE_HTML = 'text/html';
25
    const ENCODING_UTF8 = 'UTF-8';
26
27
    const TRANSFER_ENCODING_BASE64 = 'BASE64';
28
    const TRANSFER_ENCODING_8BIT = '8BIT';
29
    const TRANSFER_ENCODING_BINARY = 'BINARY';
30
    
31
    const ERROR_REQUEST_FAILED = 17902;
32
    const ERROR_CURL_INIT_FAILED = 17903;
33
    const ERROR_CANNOT_OPEN_LOGFILE = 17904;
34
35
   /**
36
    * @var string
37
    */
38
    protected $eol = "\r\n";
39
40
   /**
41
    * @var string
42
    */
43
    protected $mimeBoundary;
44
45
   /**
46
    * @var string
47
    */
48
    protected $destination;
49
50
   /**
51
    * @var array<string,string>
52
    */
53
    protected $headers = array();
54
    
55
   /**
56
    * Whether to verify SSL certificates.
57
    * @var bool
58
    */
59
    protected $verifySSL = true;
60
    
61
   /**
62
    * @var RequestHelper_Boundaries
63
    */
64
    protected $boundaries;
65
    
66
   /**
67
    * @var RequestHelper_Response|NULL
68
    */
69
    protected $response;
70
71
   /**
72
    * Timeout duration, in seconds.
73
    * @var integer
74
    */
75
    protected $timeout = 30;
76
    
77
   /**
78
    * @var string
79
    */
80
    protected $logfile = '';
81
82
   /**
83
    * @var resource|NULL
84
    */
85
    protected $logfilePointer = null;
86
    
87
   /**
88
    * Creates a new request helper to send POST data to the specified destination URL.
89
    * @param string $destinationURL
90
    */
91
    public function __construct(string $destinationURL)
92
    {
93
        $this->destination = $destinationURL;
94
        $this->mimeBoundary = str_repeat('-', 20).md5('request-helper-boundary');
95
        $this->boundaries = new RequestHelper_Boundaries($this);
96
        
97
        requireCURL();
98
    }
99
    
100
    public function getMimeBoundary() : string
101
    {
102
        return $this->mimeBoundary;
103
    }
104
    
105
    public function getMimeBody() : string
106
    {
107
        return $this->boundaries->render();
108
    }
109
    
110
    public function getEOL() : string
111
    {
112
        return $this->eol;
113
    }
114
    
115
   /**
116
    * Sets the timeout for the request, in seconds. If the request
117
    * takes longer, it will be cancelled and an exception triggered.
118
    * 
119
    * @param int $seconds
120
    * @return RequestHelper
121
    */
122
    public function setTimeout(int $seconds) : RequestHelper
123
    {
124
        $this->timeout = $seconds;
125
        
126
        return $this;
127
    }
128
    
129
    public function getTimeout() : int
130
    {
131
        return $this->timeout;
132
    }
133
    
134
   /**
135
    * Enables verbose logging of the CURL request, which
136
    * is then redirected to the target file.
137
    * 
138
    * @param string $targetFile
139
    * @return RequestHelper
140
    */
141
    public function enableLogging(string $targetFile) : RequestHelper
142
    {
143
        $this->logfile = $targetFile;
144
        
145
        return $this;
146
    }
147
148
   /**
149
    * Adds a file to be sent with the request.
150
    *
151
    * @param string $varName The variable name to send the file in
152
    * @param string $fileName The name of the file as it should be received at the destination
153
    * @param string $content The raw content of the file
154
    * @param string $contentType The content type, use the constants to specify this
155
    * @param string $encoding The encoding of the file, use the constants to specify this
156
    */
157
    public function addFile(string $varName, string $fileName, string $content, string $contentType = '', string $encoding = '') : RequestHelper
158
    {
159
        $this->boundaries->addFile($varName, $fileName, $content, $contentType, $encoding);
160
        
161
        return $this;
162
    }
163
    
164
   /**
165
    * Adds arbitrary content.
166
    * 
167
    * @param string $varName The variable name to send the content in.
168
    * @param string $content
169
    * @param string $contentType
170
    */
171
    public function addContent(string $varName, string $content, string $contentType) : RequestHelper
172
    {
173
        $this->boundaries->addContent($varName, $content, $contentType);
174
        
175
        return $this;
176
    }
177
178
   /**
179
    * Adds a variable to be sent with the request. If it
180
    * already exists, its value is overwritten.
181
    *
182
    * @param string $name
183
    * @param string $value
184
    */
185
    public function addVariable(string $name, string $value) : RequestHelper
186
    {
187
        $this->boundaries->addVariable($name, $value);
188
        
189
        return $this;
190
    }
191
    
192
   /**
193
    * Sets an HTTP header to include in the request.
194
    * 
195
    * @param string $name
196
    * @param string $value
197
    * @return RequestHelper
198
    */
199
    public function setHeader(string $name, string $value) : RequestHelper
200
    {
201
        $this->headers[$name] = $value;
202
        
203
        return $this;
204
    }
205
    
206
   /**
207
    * Disables SSL certificate checking.
208
    * 
209
    * @return RequestHelper
210
    */
211
    public function disableSSLChecks() : RequestHelper
212
    {
213
        $this->verifySSL = false;
214
        return $this;
215
    }
216
   
217
   /**
218
    * @var integer
219
    */
220
    protected $contentLength = 0;
221
222
   /**
223
    * Sends the POST request to the destination, and returns
224
    * the response text.
225
    *
226
    * The response object is stored internally, so after calling
227
    * this method it may be retrieved at any moment using the
228
    * {@link getResponse()} method.
229
    *
230
    * @return string
231
    * @see RequestHelper::getResponse()
232
    * @throws RequestHelper_Exception
233
    * 
234
    * @see RequestHelper::ERROR_REQUEST_FAILED
235
    */
236
    public function send() : string
237
    {
238
        $info = parseURL($this->destination);
239
        
240
        $ch = $this->createCURL($info);
241
242
        $output = curl_exec($ch);
243
244
        if(isset($this->logfilePointer))
245
        {
246
            fclose($this->logfilePointer);
247
        }
248
        
249
        $info = curl_getinfo($ch);
250
        
251
        $this->response = new RequestHelper_Response($this, $info);
252
        
253
        // CURL will complain about an empty response when the 
254
        // server sends a 100-continue code. That should not be
255
        // regarded as an error.
256
        if($output === false && $this->response->getCode() !== 100)
257
        {
258
            $curlCode = curl_errno($ch);
259
            
260
            $this->response->setError(
261
                $curlCode,
262
                curl_error($ch).' | Explanation: '.curl_strerror($curlCode)
263
            );
264
        }
265
        else
266
        {
267
            $this->response->setBody((string)$output);
268
        }
269
        
270
        curl_close($ch);
271
        
272
        return $this->response->getResponseBody();
273
    }
274
    
275
    public function getBody() : string
276
    {
277
        return $this->data;
0 ignored issues
show
Bug Best Practice introduced by
The property data does not exist on AppUtils\RequestHelper. Did you maybe forget to declare it?
Loading history...
278
    }
279
    
280
   /**
281
    * Creates a new CURL resource configured according to the
282
    * request's settings.
283
    * 
284
    * @param URLInfo $url
285
    * @throws RequestHelper_Exception
286
    * @return resource
287
    */
288
    protected function createCURL(URLInfo $url)
289
    {
290
        $ch = curl_init();
291
        
292
        if(!is_resource($ch))
293
        {
294
            throw new RequestHelper_Exception(
295
                'Could not initialize a new cURL instance.',
296
                'Calling curl_init returned false. Additional information is not available.',
297
                self::ERROR_CURL_INIT_FAILED
298
            );
299
        }
300
301
        $this->setHeader('Content-Length', (string)$this->boundaries->getContentLength());
302
        $this->setHeader('Content-Type', 'multipart/form-data; boundary=' . $this->mimeBoundary);
303
304
        curl_setopt($ch, CURLOPT_POST, true);
305
        curl_setopt($ch, CURLOPT_URL, $url->getNormalizedWithoutAuth());
306
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
307
        curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout);
308
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
309
        curl_setopt($ch, CURLOPT_HTTPHEADER, $this->renderHeaders());
310
        curl_setopt($ch, CURLOPT_POSTFIELDS, $this->boundaries->render());
311
        curl_setopt($ch, CURLOPT_FAILONERROR, true);
312
        
313
        $loggingEnabled = $this->configureLogging($ch);
314
        
315
        if(!$loggingEnabled) 
316
        {
317
            curl_setopt($ch, CURLINFO_HEADER_OUT, true);
318
        }
319
        
320
        if($this->verifySSL)
321
        {
322
            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
323
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
324
        }
325
        
326
        if($url->hasUsername())
327
        {
328
            curl_setopt($ch, CURLOPT_USERNAME, $url->getUsername());
329
            curl_setopt($ch, CURLOPT_PASSWORD, $url->getPassword());
330
        }
331
        
332
        return $ch;
333
    }
334
    
335
   /**
336
    * @param resource $ch
337
    * @return bool Whether logging is enabled.
338
    */
339
    protected function configureLogging($ch) : bool
340
    {
341
        if(empty($this->logfile))
342
        {
343
            return false;
344
        }
345
        
346
        $res = fopen($this->logfile, 'w+');
347
        
348
        if($res === false)
349
        {
350
            throw new RequestHelper_Exception(
351
                'Cannot open logfile for writing.',
352
                sprintf('Tried accessing the file at [%s].', $this->logfile),
353
                self::ERROR_CANNOT_OPEN_LOGFILE
354
            );
355
        }
356
        
357
        $this->logfilePointer = $res;
358
        
359
        curl_setopt($ch, CURLOPT_VERBOSE, true);
360
        curl_setopt($ch, CURLOPT_STDERR, $res);
361
        
362
        return true;
363
    }
364
365
   /**
366
    * Compiles the associative headers array into
367
    * the format understood by CURL, namely an indexed
368
    * array with one header string per entry.
369
    * 
370
    * @return array
371
    */
372
    protected function renderHeaders() : array
373
    {
374
        $result = array();
375
        
376
        $this->setHeader('Expect', '');
377
        
378
        foreach($this->headers as $name => $value) {
379
            $result[] = $name.': '.$value;
380
        }
381
        
382
        return $result;
383
    }
384
    
385
   /**
386
    * Retrieves the raw response header, in the form of an indexed
387
    * array containing all response header lines.
388
    * 
389
    * @return array
390
    */
391
    public function getResponseHeader() : array
392
    {
393
        $response = $this->getResponse();
394
        
395
        if($response !== null) {
396
            return $response->getHeaders();
397
        }
398
399
        return array();
400
    }
401
402
   /**
403
    * After calling the {@link send()} method, this may be used to
404
    * retrieve the response text from the POST request.
405
    *
406
    * @return RequestHelper_Response|NULL
407
    */
408
    public function getResponse() : ?RequestHelper_Response
409
    {
410
        return $this->response;
411
    }
412
    
413
   /**
414
    * Retrieves all headers set until now.
415
    * 
416
    * @return array<string,string>
417
    */
418
    public function getHeaders() : array
419
    {
420
        return $this->headers;
421
    }
422
    
423
   /**
424
    * Retrieves the value of a header by its name.
425
    * 
426
    * @param string $name
427
    * @return string The header value, or an empty string if not set.
428
    */
429
    public function getHeader(string $name) : string
430
    {
431
        if(isset($this->headers[$name]))
432
        {
433
            return $this->headers[$name];
434
        }
435
        
436
        return '';
437
    }
438
}
439