Completed
Push — master ( 8d3c73...8ec121 )
by Gino
03:12
created

RequestContext::assertValidUrl()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 2
nc 2
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace GinoPane\NanoRest\Request;
4
5
use GinoPane\NanoRest\Exceptions\RequestContextException;
6
use GinoPane\NanoRest\Supplemental\HeadersProperty;
7
8
/**
9
 * Class RequestContext
10
 *
11
 * @author Sergey <Gino Pane> Karavay
12
 */
13
class RequestContext
14
{
15
    /**
16
     * Default values for timeouts
17
     */
18
    const TIMEOUT_DEFAULT               = 10;
19
    const CONNECTION_TIMEOUT_DEFAULT    = 5;
20
21
    /**
22
     * Default values for charsets
23
     */
24
    const CHARSET_UTF8      = 'UTF-8';
25
    const CHARSET_ISO88591  = 'ISO-8859-1';
26
27
    /**
28
     * Sample HTTP Methods
29
     */
30
    const METHOD_OPTIONS = 'OPTIONS';
31
    const METHOD_GET     = 'GET';
32
    const METHOD_HEAD    = 'HEAD';
33
    const METHOD_POST    = 'POST';
34
    const METHOD_PUT     = 'PUT';
35
    const METHOD_DELETE  = 'DELETE';
36
    const METHOD_TRACE   = 'TRACE';
37
    const METHOD_CONNECT = 'CONNECT';
38
    const METHOD_PATCH   = 'PATCH';
39
40
    /**
41
     * Sample content types
42
     */
43
    const CONTENT_TYPE_FORM         = 'multipart/form-data';
44
    const CONTENT_TYPE_FORM_URLENCODED  = 'application/x-www-form-urlencoded';
45
    const CONTENT_TYPE_TEXT_PLAIN   = 'text/plain';
46
    const CONTENT_TYPE_JSON         = 'application/json';
47
    const CONTENT_TYPE_JAVASCRIPT   = 'application/javascript';
48
    const CONTENT_TYPE_APP_XML      = 'application/xml';
49
    const CONTENT_TYPE_TEXT_XML     = 'text/xml';
50
    const CONTENT_TYPE_TEXT_HTML    = 'text/html';
51
52
    /**
53
     * The list of supported HTTP methods
54
     *
55
     * @var array
56
     */
57
    private static $availableMethods = array(
58
         self::METHOD_OPTIONS,
59
         self::METHOD_GET,
60
         self::METHOD_HEAD,
61
         self::METHOD_POST,
62
         self::METHOD_PUT,
63
         self::METHOD_DELETE,
64
         self::METHOD_TRACE,
65
         self::METHOD_CONNECT,
66
         self::METHOD_PATCH
67
    );
68
69
    /**
70
     * Default content type for requests
71
     */
72
    private $contentType = self::CONTENT_TYPE_TEXT_PLAIN;
73
74
    /**
75
     * Default charset for requests
76
     *
77
     * @var string
78
     */
79
    private $charset = self::CHARSET_UTF8;
80
81
    /**
82
     * Preferred HTTP method
83
     *
84
     * @var string
85
     */
86
    private $method = self::METHOD_GET;
87
88
    /**
89
     * Generic data to be sent
90
     *
91
     * @var mixed
92
     */
93
    private $data = null;
94
95
    /**
96
     * Parameters that should be appended to request URL
97
     *
98
     * @var array
99
     */
100
    private $requestParameters = [];
101
102
    /**
103
     * Options for transport
104
     *
105
     * @var array
106
     */
107
    private $curlOptions = [];
108
109
    /**
110
     * URL string for request
111
     *
112
     * @var string
113
     */
114
    private $url = '';
115
116
    /**
117
     * Address of proxy server
118
     *
119
     * @var string
120
     */
121
    private $proxy = '';
122
123
    /**
124
     * Connection timeout
125
     *
126
     * @var int
127
     */
128
    private $connectionTimeout = self::CONNECTION_TIMEOUT_DEFAULT;
129
130
    /**
131
     * General timeout value to be used with the request
132
     *
133
     * @var
134
     */
135
    private $timeout = self::TIMEOUT_DEFAULT;
136
137
    use HeadersProperty;
138
    use HttpBuildQueryBehavior;
139
140
    /**
141
     * RequestContext constructor
142
     *
143
     * @param string $url
144
     */
145
    public function __construct(string $url = '')
146
    {
147
        if ($url) {
148
            $this->setUrl($url);
149
        }
150
    }
151
152
    /**
153
     * Fluent setter for consistency with other methods
154
     *
155
     * @param array $headers
156
     *
157
     * @return RequestContext
158
     */
159
    public function setHeaders(array $headers = []): RequestContext
160
    {
161
        $this->headers()->setHeaders($headers);
162
163
        return $this;
164
    }
165
166
    /**
167
     * Get headers prepared for request with Content-type assigned if it was not already set
168
     *
169
     * @return array
170
     */
171
    public function getRequestHeaders(): array
172
    {
173
        $headers = clone $this->headers();
174
175
        if (!$headers->headerExists('Content-type')) {
176
            if ($contentType = $this->getContentType()) {
177
                if (($charset = $this->getCharset()) && (stripos($contentType, 'charset=') === false)) {
178
                    $contentType .= "; charset={$charset}";
179
                }
180
181
                $headers->setHeader('Content-type', $contentType);
182
            }
183
        }
184
185
        return $headers->getHeadersForRequest();
186
    }
187
188
    /**
189
     * Set data for request
190
     *
191
     * @param mixed $data
192
     *
193
     * @return RequestContext
194
     */
195
    public function setData($data): RequestContext
196
    {
197
        $this->data = $data;
198
199
        return $this;
200
    }
201
202
    /**
203
     * Get previously set data
204
     *
205
     * @return mixed
206
     */
207
    public function getData()
208
    {
209
        return $this->data;
210
    }
211
212
    /**
213
     * Get previously set data encoded for request
214
     *
215
     * @return string
216
     */
217
    public function getRequestData(): string
218
    {
219
        $requestData = $this->getData();
220
221
        $requestData = is_array($requestData) ? $this->httpBuildQuery($requestData) : (string)$requestData;
222
223
        return $requestData;
224
    }
225
226
    /**
227
     * Get HTTP method
228
     *
229
     * @return string
230
     */
231
    public function getMethod(): string
232
    {
233
        return $this->method;
234
    }
235
236
    /**
237
     * Override default HTTP method
238
     *
239
     * @param string $method
240
     *
241
     * @throws RequestContextException
242
     *
243
     * @return RequestContext
244
     */
245
    public function setMethod(string $method): RequestContext
246
    {
247
        $method = strtoupper($method);
248
249
        if (!in_array($method, self::$availableMethods)) {
250
            throw new RequestContextException('Supplied HTTP method is not supported');
251
        }
252
253
        $this->method = $method;
254
255
        return $this;
256
    }
257
258
    /**
259
     * Get URL string
260
     *
261
     * @return string
262
     */
263
    public function getUrl(): string
264
    {
265
        return $this->url;
266
    }
267
268
    /**
269
     * Set URL string
270
     *
271
     * @param string $url
272
     *
273
     * @return RequestContext
274
     */
275
    public function setUrl(string $url): RequestContext
276
    {
277
        $this->assertValidUrl($url);
278
279
        $this->url = $url;
280
281
        return $this;
282
    }
283
284
    /**
285
     * Get URL string with request parameters applied
286
     *
287
     * @return string
288
     */
289
    public function getRequestUrl(): string
290
    {
291
        $url = $this->getUrl();
292
293
        if ($this->getRequestParameters()) {
294
            $url = $this->attachQueryToUrl($url, $this->httpBuildQuery($this->getRequestParameters()));
295
        }
296
297
        return $url;
298
    }
299
300
    /**
301
     * Attach request query to URL
302
     *
303
     * @param $url
304
     * @param $query
305
     *
306
     * @return string
307
     */
308
    public function attachQueryToUrl($url, $query): string
309
    {
310
        return $url . (strpos($url, '?') === false ? '?' : '') . $query;
311
    }
312
313
    /**
314
     * Get request params
315
     *
316
     * @return array
317
     */
318
    public function getRequestParameters(): array
319
    {
320
        return $this->requestParameters;
321
    }
322
323
    /**
324
     * Set an array of request params
325
     *
326
     * @param array $requestParameters
327
     *
328
     * @return RequestContext
329
     */
330
    public function setRequestParameters(array $requestParameters = []): RequestContext
331
    {
332
        $this->requestParameters = $requestParameters;
333
334
        return $this;
335
    }
336
337
    /**
338
     * Get CURL options
339
     *
340
     * @return array
341
     */
342
    public function getCurlOptions(): array
343
    {
344
        return $this->curlOptions;
345
    }
346
347
    /**
348
     * Set a single CURL option for context
349
     *
350
     * @param int $optionName
351
     * @param mixed $optionValue
352
     *
353
     * @throws RequestContextException
354
     *
355
     * @return RequestContext
356
     */
357
    public function setCurlOption(int $optionName, $optionValue): RequestContext
358
    {
359
        if (@curl_setopt(curl_init(), $optionName, $optionValue)) {
360
            $this->curlOptions[$optionName] = $optionValue;
361
        } else {
362
            throw new RequestContextException(
363
                "Curl option is invalid: '$optionName' => " . var_export($optionValue, true)
364
            );
365
        }
366
367
        return $this;
368
    }
369
370
    /**
371
     * Set an array of CURL options for context. Please note, that old options would be removed or overwritten
372
     *
373
     * @param array $curlOptions
374
     *
375
     * @return RequestContext
376
     */
377
    public function setCurlOptions(array $curlOptions = []): RequestContext
378
    {
379
        $this->curlOptions = [];
380
381
        foreach ($curlOptions as $name => $value) {
382
            $this->setCurlOption($name, $value);
383
        }
384
385
        return $this;
386
    }
387
388
    /**
389
     * @return mixed
390
     */
391
    public function getContentType()
392
    {
393
        return $this->contentType;
394
    }
395
396
    /**
397
     * @param mixed $contentType
398
     *
399
     * @return RequestContext
400
     */
401
    public function setContentType($contentType): RequestContext
402
    {
403
        $this->contentType = $contentType;
404
405
        return $this;
406
    }
407
408
    /**
409
     * Get charset for current request
410
     *
411
     * @return string
412
     */
413
    public function getCharset(): string
414
    {
415
        return $this->charset;
416
    }
417
418
    /**
419
     * Set charset for current request
420
     *
421
     * @param string $charset
422
     *
423
     * @return RequestContext
424
     */
425
    public function setCharset(string $charset): RequestContext
426
    {
427
        $this->charset = $charset;
428
429
        return $this;
430
    }
431
432
    /**
433
     * @return string
434
     */
435
    public function getProxy(): string
436
    {
437
        return $this->proxy;
438
    }
439
440
    /**
441
     * @param string $proxy
442
     *
443
     * @throws RequestContextException
444
     *
445
     * @return RequestContext
446
     */
447
    public function setProxy(string $proxy): RequestContext
448
    {
449
        $this->assertValidUrl($proxy);
450
451
        $this->proxy = $proxy;
452
453
        return $this;
454
    }
455
456
    /**
457
     * @return int|float
458
     */
459
    public function getConnectionTimeout()
460
    {
461
        return $this->connectionTimeout;
462
    }
463
464
    /**
465
     * @param int|float $connectionTimeout
466
     *
467
     * @return RequestContext
468
     */
469
    public function setConnectionTimeout($connectionTimeout): RequestContext
470
    {
471
        $this->connectionTimeout = $connectionTimeout;
472
473
        return $this;
474
    }
475
476
    /**
477
     * @return int|float
478
     */
479
    public function getTimeout()
480
    {
481
        return $this->timeout;
482
    }
483
484
    /**
485
     * @param int|float $timeout
486
     *
487
     * @return RequestContext
488
     */
489
    public function setTimeout($timeout): RequestContext
490
    {
491
        $this->timeout = $timeout;
492
493
        return $this;
494
    }
495
496
    /**
497
     * Get string representation of RequestContext object
498
     *
499
     * @return string
500
     */
501
    public function __toString(): string
502
    {
503
        $headers = $this->getRequestHeaders()
504
            ? print_r($this->getRequestHeaders(), true)
505
            : "No headers were set";
506
507
        $data = $this->getData() ? print_r($this->getData(), true) : "No data was set";
508
509
        $requestParameters = $this->getRequestParameters()
510
            ? print_r($this->getRequestParameters(), true)
511
            : "No request parameters were set";
512
513
        return <<<DEBUG
514
===================
515
Method: {$this->getMethod()}
516
Request URL: {$this->getRequestUrl()}
517
===================
518
Headers:
519
520
{$headers}
521
===================
522
Data:
523
524
{$data}
525
===================
526
Request Parameters:
527
528
{$requestParameters}
529
===================
530
DEBUG;
531
    }
532
533
    /**
534
     * Throw exception on invalid URL
535
     *
536
     * @param string $url
537
     *
538
     * @throws RequestContextException
539
     */
540
    private function assertValidUrl(string $url): void
541
    {
542
        if (!(filter_var($url, FILTER_VALIDATE_URL) || filter_var($url, FILTER_VALIDATE_IP))) {
543
            throw new RequestContextException("Failed to set invalid URL: $url");
544
        }
545
    }
546
}
547