Passed
Push — master ( dc4356...41e10c )
by Alexander
03:41
created

Request::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
c 2
b 0
f 0
nc 1
nop 7
dl 0
loc 12
rs 10
1
<?php
2
3
/**
4
 * Lenevor Framework
5
 *
6
 * LICENSE
7
 *
8
 * This source file is subject to the new BSD license that is bundled
9
 * with this package in the file license.md.
10
 * It is also available through the world-wide-web at this URL:
11
 * https://lenevor.com/license
12
 * If you did not receive a copy of the license and are unable to
13
 * obtain it through the world-wide-web, please send an email
14
 * to [email protected] so we can send you a copy immediately.
15
 *
16
 * @package     Lenevor
17
 * @subpackage  Base
18
 * @link        https://lenevor.com
19
 * @copyright   Copyright (c) 2019 - 2023 Alexander Campo <[email protected]>
20
 * @license     https://opensource.org/licenses/BSD-3-Clause New BSD license or see https://lenevor.com/license or see /license.md
21
 */
22
23
namespace Syscodes\Components\Http;
24
25
use Closure;
26
use LogicException;
27
use RuntimeException;
28
use Syscodes\Components\Support\Arr;
29
use Syscodes\Components\Support\Str;
30
use Syscodes\Components\Http\Loaders\Parameters;
31
use Syscodes\Components\Http\Helpers\RequestUtils;
32
use Syscodes\Components\Http\Resources\HttpRequest;
33
use Syscodes\Components\Http\Resources\HttpResources;
34
use Syscodes\Components\Http\Session\SessionDecorator;
35
use Syscodes\Components\Http\Concerns\CanBePrecognitive;
36
use Syscodes\Components\Http\Concerns\InteractsWithInput;
37
use Syscodes\Components\Http\Concerns\InteractsWithFlashData;
0 ignored issues
show
Bug introduced by
The type Syscodes\Components\Http...\InteractsWithFlashData was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
38
use Syscodes\Components\Http\Concerns\InteractsWithContentTypes;
39
use Syscodes\Components\Http\Exceptions\SessionNotFoundException;
40
41
/**
42
 * Request represents an HTTP request.
43
 */
44
class Request
45
{
46
	use HttpRequest,
47
	    HttpResources,
48
	    CanBePrecognitive,	    
49
	    InteractsWithInput,
50
	    InteractsWithFlashData,
51
	    InteractsWithContentTypes;
52
53
	/**
54
	 * Get the acceptable of content types.
55
	 * 
56
	 * @var string[] $acceptableContenTypes
57
	 */
58
	protected $acceptableContentTypes;
59
60
	/**
61
	 * The decoded JSON content for the request.
62
	 * 
63
	 * @var \Syscodes\Components\Http\Loaders\Parameters|null $json
64
	 */
65
	protected $json;
66
67
	/**
68
	 * The path info of URL.
69
	 * 
70
	 * @var string $pathInfo
71
	 */
72
	protected $pathInfo;
73
74
	/**
75
	 * Get request URI.
76
	 * 
77
	 * @var string $requestToUri
78
	 */
79
	protected $requestToUri;
80
81
	/**
82
	 * Get the route resolver callback.
83
	 * 
84
	 * @var \Closure $routeResolver
85
	 */
86
	protected $routeResolver;
87
88
	/**
89
	 * The Session implementation.
90
	 * 
91
	 * @var \Syscodes\Components\Contracts\Session\Session $session
92
	 */
93
	protected $session;
94
95
	/**
96
	 * Create a new Syscodes HTTP request from server variables.
97
	 * 
98
	 * @return static
99
	 */
100
	public static function capture(): static
101
	{
102
		static::enabledHttpMethodParameterOverride();
103
		
104
		return static::createFromRequest(static::createFromRequestGlobals());
105
	}
106
107
	/**
108
	 * Returns the desired segment, or $default if it does not exist.
109
	 *
110
	 * @param  int  $index  The segment number (1-based index)
111
	 * @param  mixed  $default  Default value to return
112
	 *
113
	 * @return string
114
	 */
115
	public function segment($index, $default = null)
116
	{
117
		return $this->uri->getSegment($index, $default);
118
	}
119
120
	/**
121
	 * Returns all segments in an array. For total of segments
122
	 * used the function PHP count().
123
	 *
124
	 * @return array|null
125
	 */
126
	public function segments()
127
	{
128
		return $this->uri->getSegments();
129
	}
130
131
	/**
132
	 * Returns the total number of segment.
133
	 *
134
	 * @return int|null  
135
	 */
136
	public function totalSegments()
137
	{
138
		return $this->uri->getTotalSegments();
139
	}
140
141
	/**
142
	 * Returns the full request string.
143
	 * 
144
	 * @param  string  $key
145
	 * @param  mixed  $default
146
	 *
147
	 * @return mixed 
148
	 */
149
	public function get(string $key, $default = null) 
150
	{
151
		if ($this !== $result = $this->attributes->get($key, $this)) {
152
			return $result;
153
		}
154
155
		if ($this->query->has($key)) {
156
			return $this->query->all()[$key];
157
		}
158
		
159
		if ($this->request->has($key)) {
160
			return $this->request->all()[$key];
161
		}
162
		
163
		return $default;
164
	}
165
166
	/**
167
	 * Gets the Session.
168
	 * 
169
	 * @return \Syscodes\Components\Http\Session\SessionInterface
170
	 * 
171
	 * @throws \Syscodes\Components\Http\Exceptions\SessionNotFoundException
172
	 */
173
	public function getSession()
174
	{
175
		$this->hasSession()
176
		            ? new SessionDecorator($this->session())
177
					: throw new SessionNotFoundException;
178
	}
179
180
	/**
181
	 * Whether the request contains a Session object.
182
	 * 
183
	 * @return bool
184
	 */
185
	public function hasSession(): bool
186
	{
187
		return ! is_null($this->session);
188
	}
189
190
	/**
191
	 * Get the session associated with the request.
192
	 * 
193
	 * @return \Syscodes\Components\Contracts\Session\Session
194
	 * 
195
	 * @throws RuntimeException
196
	 */
197
	public function session()
198
	{
199
		if ( ! $this->hasSession()) {
200
			throw new RuntimeException('Session store not set on request');
201
		}
202
		
203
		return $this->session;
204
	}
205
	
206
	/**
207
	 * Set the session instance on the request.
208
	 * 
209
	 * @param  \Syscodes\Components\Contracts\Session\Session  $session
210
	 * 
211
	 * @return void
212
	 */
213
	public function setSession($session): void
214
	{
215
		$this->session = $session;
216
	}
217
218
	/**
219
	 * Get the JSON payload for the request.
220
	 * 
221
	 * @param  string|null  $key  
222
	 * @param  mixed  $default  
223
	 * 
224
	 * @return \Syscodes\Components\Http\Loaders\Parameters|mixed
225
	 */
226
	public function json($key = null, $default = null)
227
	{
228
		if ( ! isset($this->json)) {
229
			$this->json = new Parameters((array) json_decode($this->getContent(), true));
230
		}
231
232
		if (is_null($key)) {
233
			return $this->json;
234
		}
235
236
		return Arr::get($this->json->all(), $key, $default);
0 ignored issues
show
Bug introduced by
The method all() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

236
		return Arr::get($this->json->/** @scrutinizer ignore-call */ all(), $key, $default);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
237
	}
238
239
	/**
240
	 * Set the JSON payload for the request.
241
	 * 
242
	 * @param  \Syscodes\Components\Http\Loaders\Parameters  $json
243
	 * 
244
	 * @return static
245
	 */
246
	public function setJson($json): static
247
	{
248
		$this->json = $json;
249
250
		return $this;
251
	}
252
	
253
	/**
254
	 * Gets a list of content types acceptable by the client browser in preferable order.
255
	 * 
256
	 * @return string[]
257
	 */
258
	public function getAcceptableContentTypes(): array
259
	{
260
		if (null !== $this->acceptableContentTypes) {
261
			return $this->acceptableContentTypes;
262
		}
263
		
264
		return $this->acceptableContentTypes = array_map('strval', [$this->headers->get('Accept')]);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->headers->get('Accept') targeting Syscodes\Components\Http\Loaders\Headers::get() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
265
	}
266
267
	/**
268
	 * Returns whether this is an AJAX request or not.
269
	 * Alias of isXmlHttpRequest().
270
	 *
271
	 * @return bool
272
	 */
273
	public function ajax(): bool
274
	{
275
		return $this->isXmlHttpRequest();
276
	}
277
278
	/**
279
	 * Returns whether this is an AJAX request or not.
280
	 *
281
	 * @return bool
282
	 */
283
	public function isXmlHttpRequest(): bool
284
	{
285
		return ! empty($this->server->get('HTTP_X_REQUESTED_WITH')) && 
286
				strtolower($this->server->get('HTTP_X_REQUESTED_WITH')) === 'xmlhttprequest';
287
	}
288
	
289
	/**
290
	 * Determine if the request is the result of a PJAX call.
291
	 * 
292
	 * @return bool
293
	 */
294
	public function pjax(): bool
295
	{
296
		return $this->headers->get('X-PJAX') == true;
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->headers->get('X-PJAX') targeting Syscodes\Components\Http\Loaders\Headers::get() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
297
	}
298
	
299
	/**
300
	 * Determine if the request is the result of a prefetch call.
301
	 * 
302
	 * @return bool
303
	 */
304
	public function prefetch(): bool
305
	{
306
		return strcasecmp($this->server->get('HTTP_X_MOZ') ?? '', 'prefetch') === 0 ||
307
		       strcasecmp($this->headers->get('Purpose') ?? '', 'prefetch') === 0;
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->headers->get('Purpose') targeting Syscodes\Components\Http\Loaders\Headers::get() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
308
	}
309
310
	/**
311
	 * Checks if the method request is of specified type.
312
	 * 
313
	 * @param  string  $method
314
	 * 
315
	 * @return bool
316
	 */
317
	public function isMethod(string $method): bool
318
	{
319
		return $this->getMethod() === strtoupper($method);
320
	}
321
322
	/**
323
     * Alias of the request method.
324
     * 
325
     * @return string
326
     */
327
    public function method(): string
328
    {
329
        return $this->getMethod();
330
    }
331
332
	/**
333
	 * Returns the input method used (GET, POST, DELETE, etc.).
334
	 *
335
	 * @return string
336
	 * 
337
	 * @throws \LogicException  
338
	 */
339
	public function getmethod(): string
340
	{
341
		if (null !== $this->method) {
342
			return $this->method;
343
		}
344
		
345
		$this->method = strtoupper($this->server->get('REQUEST_METHOD', 'GET'));
346
		
347
		if ('POST' !== $this->method) {
348
			return $this->method;
349
		}
350
		
351
		$method = $this->headers->get('X-HTTP-METHOD-OVERRIDE');
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $method is correct as $this->headers->get('X-HTTP-METHOD-OVERRIDE') targeting Syscodes\Components\Http\Loaders\Headers::get() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
352
		
353
		if ( ! $method && self::$httpMethodParameterOverride) {
0 ignored issues
show
introduced by
$method is of type null, thus it always evaluated to false.
Loading history...
354
			$method = $this->request->get('_method', $this->query->get('_method', 'POST'));
355
		}
356
		
357
		if ( ! is_string($method)) {
358
			return $this->method;
359
		}
360
		
361
		$method = strtoupper($method);
362
		
363
		if (in_array($method, ['GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'CONNECT', 'OPTIONS', 'PATCH', 'PURGE', 'TRACE'], true)) {
364
			return $this->method = $method;
365
		}
366
		
367
		if ( ! preg_match('/^[A-Z]++$/D', $method)) {
368
			throw new LogicException(sprintf('Invalid method override "%s".', $method));
369
		}
370
		
371
		return $this->method = $method;
372
	}
373
374
	/**
375
	 * Sets the request method.
376
	 *
377
	 * @param  string  $method  
378
	 *
379
	 * @return void
380
	 */
381
	public function setMethod(string $method): void
382
	{
383
		$this->method = null;
384
385
		$this->server->set('REQUEST_METHOD', $method);
386
	}
387
388
	/**
389
	 * Get the input source for the request.
390
	 * 
391
	 * @return \Syscodes\Components\Http\Loaders\Parameters
392
	 */
393
	public function getInputSource()
394
	{
395
		if ($this->isJson()) {
396
			return $this->json();
397
		}
398
399
		return in_array($this->getMethod(), ['GET', 'HEAD']) ? $this->query : $this->request;
400
	}
401
	
402
	/**
403
	 * Determine if the current request URI matches a pattern.
404
	 * 
405
	 * @param  mixed  ...$patterns
406
	 * 
407
	 * @return bool
408
	 */
409
	public function is(...$patterns): bool
410
	{
411
		$path = $this->decodedPath();
412
		
413
		foreach ($patterns as $pattern) {
414
			if (Str::is($pattern, $path)) {
415
				return true;
416
			}
417
		}
418
419
		return false;
420
	}
421
422
	/**
423
	 * Determine if the route name matches a given pattern.
424
	 * 
425
	 * @param  mixed  ...$patterns
426
	 * 
427
	 * @return bool
428
	 */
429
	public function routeIs(...$patterns): bool
430
	{
431
		return $this->route() && $this->route()->is(...$patterns);
0 ignored issues
show
Bug introduced by
The method is() does not exist on Syscodes\Components\Routing\Route. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

431
		return $this->route() && $this->route()->/** @scrutinizer ignore-call */ is(...$patterns);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
432
	}
433
434
	/**
435
	 * Get the route handling the request.
436
	 * 
437
	 * @param  string|null  $param  
438
	 * @param  mixed  $default  
439
	 * 
440
	 * @return \Syscodes\Components\Routing\Route|object|string|null
441
	 */
442
	public function route($param = null, $default = null)
443
	{
444
		$route = call_user_func($this->getRouteResolver());
445
446
		if (is_null($route) || is_null($param)) {
447
			return $route;
448
		}
449
450
		return $route->parameter($param, $default);
451
	}
452
453
	/**
454
	 * Get the current decoded path info for the request.
455
	 * 
456
	 * @return string
457
	 */
458
	public function decodedPath(): string
459
	{
460
		return rawurldecode($this->path());
461
	}
462
463
	/**
464
	 * Get the current path info for the request.
465
	 * 
466
	 * @return string
467
	 */
468
	public function path(): string
469
	{
470
		$path = trim($this->getPathInfo(), '/');
471
472
		return $path == '' ? '/' : $path;
473
	}
474
475
	/**
476
	 * Get the full URL for the request.
477
	 * 
478
	 * @return string
479
	 */
480
	public function fullUrl(): string
481
	{
482
		$query = $this->getQueryString();
483
		
484
		$question = $this->getBaseUrl().$this->getPathInfo() === '/' ? '/?' : '?';
485
		
486
		return $query ? $this->url().$question.$query : $this->url();
487
	}
488
	
489
	/**
490
	 * Generates the normalized query string for the Request.
491
	 * 
492
	 * @return string
493
	 */
494
	public function getQueryString(): ?string
495
	{
496
		$queryString = RequestUtils::normalizedQueryString($this->server->get('QUERY_STRING'));
497
		
498
		return '' === $queryString ? null : $queryString;
499
	}
500
501
	/**
502
	 * Retunrs the request body content.
503
	 * 
504
	 * @return string
505
	 */
506
	public function getContent(): string
507
	{
508
		if (null === $this->content || false === $this->content) {
509
			$this->content = file_get_contents('php://input');
510
		}
511
512
		return $this->content;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->content could return the type object|resource which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
513
	}
514
515
	/**
516
	 * Returns the path being requested relative to the executed script. 
517
	 * 
518
	 * @return string
519
	 */
520
	public function getPathInfo(): string
521
	{
522
		if (null === $this->pathInfo) {
523
			$this->pathInfo = $this->parsePathInfo();
524
		}
525
526
		return $this->pathInfo;
527
	}
528
529
	/**
530
	 * Returns the root URL from which this request is executed.
531
	 * 
532
	 * @return string
533
	 */
534
	public function getBaseUrl(): string
535
	{
536
		if (null === $this->baseUrl) {
537
			$this->baseUrl = $this->parseBaseUrl();
538
		}
539
540
		return $this->baseUrl;
541
	}
542
543
	/**
544
	 * Returns the requested URI.
545
	 * 
546
	 * @return string
547
	 */
548
	public function getRequestUri(): string
549
	{
550
		if (null === $this->requestToUri) {
551
			$this->requestToUri = $this->parseRequestUri();
552
		}
553
554
		return $this->requestToUri;
555
	}
556
	
557
	/**
558
	 * Generates a normalized URI (URL) for the Request.
559
	 * 
560
	 * @return string
561
	 */
562
	public function getUri(): string
563
	{
564
		if (null !== $query = $this->getQueryString()) {
565
			$query = '?'.$query;
566
		}
567
	
568
		return $this->getSchemeWithHttpHost().$this->getBaseUrl().$this->getPathInfo().$query;
569
	}
570
571
	/**
572
	 * Get the root URL for the application.
573
	 * 
574
	 * @return string
575
	 */
576
	public function root(): string
577
	{
578
		return rtrim($this->getSchemeWithHttpHost().$this->getBaseUrl(), '/');
579
	}
580
581
	/**
582
	 * Get the URL for the request.
583
	 * 
584
	 * @return string
585
	 */
586
	public function url(): string
587
	{
588
		// Changed $this->path() for $this->getUri()
589
		return rtrim(preg_replace('/\?.*/', '', $this->getUri()), '/');
590
	}
591
592
	/**
593
	 * Returns the referer.
594
	 * 
595
	 * @param  string  $default
596
	 * 
597
	 * @return string
598
	 */
599
	public function referer(string $default = ''): string
600
	{
601
		return $this->server->get('HTTP_REFERER', $default);
602
	}
603
	
604
	/**
605
	 * Attempts to detect if the current connection is secure through 
606
	 * over HTTPS protocol.
607
	 * 
608
	 * @return bool
609
	 */
610
	public function secure(): bool
611
	{
612
		if ( ! empty($this->server->get('HTTPS')) && strtolower($this->server->get('HTTPS')) !== 'off') {
613
			return true;
614
		} elseif (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $this->server->get('HTTP_X_FORWARDED_PROTO') === 'https') {
615
			return true;
616
		} elseif ( ! empty($this->server->get('HTTP_FRONT_END_HTTPS')) && strtolower($this->server->get('HTTP_FRONT_END_HTTPS')) !== 'off') {
617
			return true;
618
		}
619
620
		return false;
621
	}
622
623
	/**
624
	 * Returns the user agent.
625
	 *
626
	 * @param  string|null  $default
627
	 *
628
	 * @return string
629
	 */
630
	public function userAgent(string $default = null): string
631
	{
632
		return $this->server->get('HTTP_USER_AGENT', $default);
633
	}
634
	
635
	/**
636
	 * Get the client IP address.
637
	 * 
638
	 * @return string|null
639
	 */
640
	public function ip(): ?string
641
	{
642
		return $this->clientIp->getClientIp();
643
	}
644
645
	/**
646
	 * Get the route resolver callback.
647
	 * 
648
	 * @return \Closure
649
	 */
650
	public function getRouteResolver(): Closure
651
	{
652
		return $this->routeResolver ?: function () {
653
			//
654
		};
655
	}
656
657
	/**
658
	 * Set the route resolver callback.
659
	 * 
660
	 * @param  \Closure  $callback
661
	 * 
662
	 * @return static
663
	 */
664
	public function setRouteResolver(Closure $callback): static
665
	{
666
		$this->routeResolver = $callback;
667
668
		return $this;
669
	}
670
	
671
	/**
672
	 * Magic method.
673
	 * 
674
	 * Check if an input element is set on the request.
675
	 * 
676
	 * @param  string  $key
677
	 * 
678
	 * @return bool
679
	 */
680
	public function __isset($key)
681
	{
682
		return ! is_null($this->__get($key));
683
	}
684
685
	/**
686
	 * Magic method.
687
	 * 
688
	 * Get an element from the request.
689
	 * 
690
	 * @return string[]
691
	 */
692
	public function __get($key)
693
	{
694
		return Arr::get($this->all(), $key, fn () => $this->route($key));
695
	}
696
697
	/**
698
	 * Magic method.
699
	 * 
700
	 * Returns the Request as an HTTP string.
701
	 * 
702
	 * @return string
703
	 */
704
	public function __toString(): string
705
	{
706
		$content = $this->getContent();
707
708
		$cookieHeader = '';
709
		$cookies      = [];
710
711
		foreach ($this->cookies as $key => $value) {
712
			$cookies[]= is_array($value) ? http_build_query([$key => $value], '', '; ', PHP_QUERY_RFC3986) : "$key=$value";
713
		}
714
715
		if ($cookies) {
716
			$cookieHeader = 'Cookie: '.implode('; ', $cookies)."\r\n";
717
		}
718
		
719
		return sprintf('%s %s %s', $this->getMethod(), $this->getRequestUri(), $this->server->get('SERVER_PROTOCOL'))."\r\n".
720
			$this->headers.
721
			$cookieHeader."\r\n".
722
			$content;
723
	}
724
725
	/**
726
	 * Magic method.
727
	 * 
728
	 * Clones the current request.
729
	 * 
730
	 * @return void
731
	 */
732
	public function __clone()
733
	{
734
		$this->query      = clone $this->query;
735
		$this->request    = clone $this->request;
736
		$this->attributes = clone $this->attributes;
737
		$this->cookies    = clone $this->cookies;
738
		$this->files      = clone $this->files;
739
		$this->server     = clone $this->server;
740
		$this->headers    = clone $this->headers;
741
	}
742
}