RequestDescription::__construct()   B
last analyzed

Complexity

Conditions 6
Paths 24

Size

Total Lines 58
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 29
CRAP Score 6.0012

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 29
c 2
b 0
f 0
dl 0
loc 58
ccs 29
cts 30
cp 0.9667
rs 8.8337
cc 6
nc 24
nop 9
crap 6.0012

How to fix   Long Method    Many Parameters   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
namespace POData\UriProcessor;
4
5
use POData\Common\Url;
6
use POData\Common\ODataConstants;
7
use POData\Common\Messages;
8
use POData\Common\MimeTypes;
9
use POData\Common\Version;
10
use POData\Common\ODataException;
11
use POData\IService;
12
use POData\Providers\Metadata\ResourceProperty;
13
use POData\Providers\Metadata\ResourceSetWrapper;
14
use POData\Providers\Metadata\ResourceStreamInfo;
15
use POData\UriProcessor\UriProcessor;
16
use POData\UriProcessor\ResourcePathProcessor\SegmentParser\SegmentParser;
17
use POData\UriProcessor\ResourcePathProcessor\SegmentParser\TargetSource;
18
use POData\UriProcessor\ResourcePathProcessor\SegmentParser\TargetKind;
19
use POData\UriProcessor\ResourcePathProcessor\SegmentParser\SegmentDescriptor;
20
use POData\UriProcessor\QueryProcessor\OrderByParser\InternalOrderByInfo;
21
use POData\UriProcessor\QueryProcessor\SkipTokenParser\InternalSkipTokenInfo;
22
use POData\UriProcessor\QueryProcessor\ExpressionParser\FilterInfo;
23
use POData\UriProcessor\QueryProcessor\ExpandProjectionParser\RootProjectionNode;
24
use POData\Providers\Metadata\ResourceType;
25
use POData\Providers\Query\QueryType;
26
/**
27
 * Class RequestDescription
28
 * @package POData\UriProcessor
29
 */
30
class RequestDescription
31
{
32
    /**
33
     * Holds the value of HTTP 'DataServiceVersion' header in the request,
34
     * DataServiceVersion header value states the version of the
35
     * Open Data Protocol used by the client to generate the request.
36
     * Refer http://www.odata.org/developers/protocols/overview#ProtocolVersioning
37
     *
38
     * @var Version
39
     */
40
    private $requestVersion = null;
41
42
    /**
43
     * Holds the value of HTTP 'MaxDataServiceVersion' header in the request,
44
     * MaxDataServiceVersion header value specifies the maximum version number
45
     * the client can accept in a response.
46
     * Refer http://www.odata.org/developers/protocols/overview#ProtocolVersioning
47
     *
48
     * @var Version
49
     */
50
    private $requestMaxVersion = null;
51
52
    /**
53
     * This is the value of 'DataServiceVersion' header to be output in the response. this header
54
     * value states the OData version the server used to generate the response.
55
     * While processing the query and result set this value will be keeps on
56
     * updating, after every update this is compared against the
57
     * 'MaxDataServiceVersion' header in the client request to see whether the
58
     * client can interpret the response or not. The client should use this
59
     * value to determine whether it can correctly interpret the response or not.
60
     * Refer http://www.odata.org/developers/protocols/overview#ProtocolVersioning
61
     *
62
     * @var Version
63
     */
64
    private $requiredMinResponseVersion;
65
66
    /**
67
     * The minimum client version requirement, This value keeps getting updated
68
     * during processing of query, this is compared against the
69
     * DataServiceVersion header in the client request and if the client request
70
     * is less than this value then we fail the request (e.g. $count request
71
     * was sent but client said it was Version 1.0).
72
     *
73
     * @var Version
74
     */
75
    private $requiredMinRequestVersion;
76
77
    /** @var Version */
78
    private $maxServiceVersion;
79
80
    /**
81
     * Collection of known data service versions.
82
     *
83
     * @var Version[]
84
     */
85
    private static $_knownDataServiceVersions = null;
86
87
    /**
88
     *
89
     * @var Url
90
     */
91
    private $requestUrl;
92
93
    /**
94
     * Collection of SegmentDescriptor containing information about
95
     * each segment in the resource path part of the request uri.
96
     *
97
     * @var SegmentDescriptor[]
98
     */
99
    private $segments;
100
101
    /**
102
     * Holds reference to the last segment descriptor.
103
     *
104
     * @var SegmentDescriptor
105
     */
106
    private $lastSegment;
107
108
    /**
109
     * The name of the container for results
110
     *
111
     * @var string|null
112
     */
113
    private $_containerName;
114
115
116
    /**
117
     * The count option specified in the request.
118
     *
119
     * @var string
120
     */
121
    public $queryType;
122
123
    /**
124
     * Number of segments.
125
     *
126
     * @var int
127
     */
128
    private $_segmentCount;
129
130
    /**
131
     * Holds the value of $skip query option, if no $skip option
132
     * found then this parameter will be NULL.
133
     *
134
     * @var int|null
135
     */
136
    private $_skipCount;
137
138
    /**
139
     * Holds the value of take count, this value is depends on
140
     * presence of $top option and configured page size.
141
     *
142
     * @var int|null
143
     */
144
    private $_topCount;
145
146
    /**
147
     * Holds the value of $top query option, if no $top option
148
     * found then this parameter will be NULL.
149
     *
150
     * @var int|null
151
     */
152
    private $_topOptionCount;
153
154
    /**
155
     * Holds the parsed details for sorting, this will
156
     * be set in 3 cases
157
     * (1) if $orderby option is specified in the request uri
158
     * (2) if $skip or $top option is specified in the request uri
159
     * (3) if server side paging is enabled for the resource
160
     *     targeted by the request uri.
161
     *
162
     * @var InternalOrderByInfo|null
163
     */
164
    private $internalOrderByInfo;
165
166
    /**
167
     * Holds the parsed details for $skiptoken option, this will
168
     * be NULL if $skiptoken option is absent.
169
     *
170
     * @var InternalSkipTokenInfo|null
171
     */
172
    private $_internalSkipTokenInfo;
173
174
    /**
175
     * Holds the parsed details for $filter option, this will be NULL if $filter option is absent.
176
     *
177
     * @var FilterInfo|null
178
     */
179
    private $_filterInfo;
180
181
    /**
182
     * Holds reference to the root of the tree describing expand
183
     * and select information, this field will be NULL if no
184
     * $expand or $select specified in the request uri.
185
     *
186
     * @var RootProjectionNode|null
187
     */
188
    private $_rootProjectionNode;
189
190
    /**
191
     * Holds number of entities in the result set, if either $count or
192
     * $inlinecount=allpages is specified, otherwise NULL
193
     *
194
     *
195
     * @var int|null
196
     */
197
    private $_countValue;
198
199
    /**
200
     * Data of request from request body
201
     *
202
     * @var array
203
     */
204
    private $_data;
205
206
    /**
207
     * Flag indicating status of query execution.
208
     *
209
     * @var boolean
210
     */
211
    private $_isExecuted;
212
213
    /**
214
     * Reference to Uri processor.
215
     *
216
     * @var UriProcessor
217
     */
218
    private $_uriProcessor;
219
220
221
    /**
222
     * Reference to the service
223
     * @var IService
224
     */
225
    private $_service;
226
227
    /**
228
     * The parts of a multipart request
229
     * @var array
230
     */
231
    private $_parts;
232
233
    /**
234
     * The content of the Content-ID header of a request part
235
     * @var string
236
     */
237
    private $_contentId;
238
239
    /**
240
     * The HTTP request method of a request part
241
     * @var string
242
     */
243
    private $_requestMethod;
244
245
    /**
246
     * @param SegmentDescriptor[] $segmentDescriptors Description of segments in the resource path.
247
     * @param Url $requestUri
248
     * @param Version $serviceMaxVersion
249
     * @param null|string $requestVersion
250
     * @param null|string $maxRequestVersion
251
     * @param string $dataType
252
     */
253 128
    public function __construct($segmentDescriptors, Url $requestUri, Version $serviceMaxVersion, $requestVersion, $maxRequestVersion, $dataType = null, IService $service = null, $contentId = null, $requestMethod = null)
254
    {
255 128
        $this->segments = $segmentDescriptors;
256 128
        $this->_segmentCount = count($this->segments);
257 128
        $this->requestUrl = $requestUri;
258 128
        $this->lastSegment = $segmentDescriptors[$this->_segmentCount - 1];
259 128
        $this->queryType = QueryType::ENTITIES;
260 128
        $this->_service = $service;
261 128
        $this->_parts = array();
262
263
        //we use this for validation checks down in validateVersions...but maybe we should check that outside of this object...
264 128
        $this->maxServiceVersion = $serviceMaxVersion;
265
266
        //Per OData 1 & 2 spec we must return the smallest size
267
        //We start at 1.0 and move it up as features are requested
268 128
        $this->requiredMinResponseVersion = clone Version::v1();
269 128
        $this->requiredMinRequestVersion = clone Version::v1();
270
271
272
        //see http://www.odata.org/documentation/odata-v2-documentation/overview/#ProtocolVersioning
273
        //if requestVersion isn't there, use Service Max Version
274 128
        $this->requestVersion = is_null($requestVersion) ? $serviceMaxVersion : self::parseVersionHeader($requestVersion, ODataConstants::ODATAVERSIONHEADER);
275
276
        //if max version isn't there, use the request version
277 128
        $this->requestMaxVersion = is_null($maxRequestVersion) ? $this->requestVersion : self::parseVersionHeader($maxRequestVersion, ODataConstants::ODATAMAXVERSIONHEADER);
278
279
        //if it's OData v3..things change a bit
280 128
        if ($this->maxServiceVersion == Version::v3()) {
281 61
            if (is_null($maxRequestVersion))
282
            {
283
                //if max request version isn't specified we use the service max version instead of the request version
284
                //thus we favour newer versions
285 6
                $this->requestMaxVersion = $this->maxServiceVersion;
286
            }
287
288
            //also we change min response version to be the max version, again favoring later things
289
            //note that if the request max version is specified, it is still respected
290 61
            $this->requiredMinResponseVersion = clone $this->requestMaxVersion;
291
        }
292
293
294
295 128
        $this->_containerName = null;
296 128
        $this->_skipCount = null;
297 128
        $this->_topCount = null;
298 128
        $this->_topOptionCount = null;
299 128
        $this->internalOrderByInfo = null;
300 128
        $this->_internalSkipTokenInfo = null;
301
302 128
        $this->_filterInfo = null;
303 128
        $this->_countValue = null;
304 128
        $this->_isExecuted = false;
305 128
        $this->_contentId = $contentId;
306 128
        $this->_requestMethod = $requestMethod;
307
308
        // Define data from request body
309 128
        if ($dataType) {
310
            $this->_readData($dataType);
311
        }
312
    }
313
314 94
    public function getParts() {
315 94
        return $this->_parts;
316
    }
317
318
    public function getContentID() {
319
        return $this->_contentId;
320
    }
321
322
    public function getRequestMethod() {
323
        return $this->_requestMethod;
324
    }
325
326
    const STATE_PART_START = 'STATE_PART_START';
327
    const STATE_HTTP_STATUS = 'STATE_HTTP_STATUS';
328
    const STATE_HEADERS = 'STATE_HEADERS';
329
    const STATE_BODY = 'STATE_BODY';
330
331
    private function _processData(string $string, string $dataType)
332
    {
333
        if ($dataType === MimeTypes::MIME_APPLICATION_XML) {
334
            $data = Xml2Array::createArray($string);
0 ignored issues
show
Bug introduced by
The type POData\UriProcessor\Xml2Array 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...
335
            if (!empty($data['a:entry']['a:content']['m:properties'])) {
336
                $clearData = $data['a:entry']['a:content']['m:properties'];
337
                if (is_array($clearData)) {
338
                    foreach ($clearData as $key => $value) {
339
                        $this->_data[substr($key, 2)] = $value['@value'];
340
                    }
341
                }
342
            }
343
        } elseif ($dataType === MimeTypes::MIME_APPLICATION_JSON) {
344
            $data = json_decode($string, true);
345
            $this->_data = $data;
346
        } elseif (explode(';', $dataType)[0] === MimeTypes::MIME_MULTIPART_MIXED) {
347
            preg_match('/boundary=(.*)$/', $dataType, $matches);
348
            $boundary = $matches[1];
349
350
            // split content by boundary and get rid of last -- element
351
            $a_blocks = preg_split("/-+$boundary/", $string);
352
            array_pop($a_blocks);
353
354
            // loop data blocks
355
            foreach ($a_blocks as $id => $block)
356
            {
357
                if (empty($block)) {
358
                                continue;
359
                }
360
361
                $requestMethod = null;
362
                $requestUrl = null;
363
364
                $reader_state = RequestDescription::STATE_PART_START;
365
                $body = '';
366
                $headers = [];
367
                $part_headers = [];
368
369
                foreach (explode("\n", $block) as $line) {
370
                    if (empty($line)) {
371
                        if ($reader_state == RequestDescription::STATE_PART_START) {
372
                            $reader_state = RequestDescription::STATE_HTTP_STATUS;
373
                        } else if ($reader_state == RequestDescription::STATE_HEADERS) {
374
                            $reader_state = RequestDescription::STATE_BODY;
375
                            $body = '';
376
                        }
377
                        continue;
378
                    }
379
                    $m = array();
380
                    if (in_array($reader_state, [RequestDescription::STATE_HTTP_STATUS, RequestDescription::STATE_HEADERS]) && preg_match('/(?<key>[^:]+):\s*(?<value>.*?)\s*$/', $line, $m)) {
381
                        if ($reader_state == RequestDescription::STATE_HTTP_STATUS) {
382
                            $headers[$m['key']] = $m['value'];
383
                        } else if ($reader_state == RequestDescription::STATE_HEADERS) {
384
                            $part_headers[$m['key']] = $m['value'];
385
                        }
386
                    } else if (preg_match('/^(GET|PUT|POST|PATCH|DELETE)\s+(.*?)(\s+HTTP\/\d\.\d)?\s*$/', $line, $m)) {
387
                        $requestMethod = trim($m[1]);
388
                        $requestUrl = new Url(trim($m[2]), false);
389
                        $reader_state = RequestDescription::STATE_HEADERS;
390
                    } else if ($reader_state = RequestDescription::STATE_BODY) {
391
                        $body .= $line . "\n";
392
                    }
393
                }
394
395
                $host = $this->_service->getHost();
396
                $absoluteServiceUri = $host->getAbsoluteServiceUri();
397
398
                $requestUriSegments = array_slice(
399
                    $requestUrl->getSegments(),
400
                    $absoluteServiceUri->getSegmentCount()
401
                );
402
403
                $segments = SegmentParser::parseRequestUriSegments(
404
                    $requestUriSegments,
405
                    $this->_service->getProvidersWrapper(),
406
                    true
407
                );
408
409
                $contentType = $part_headers['Content-Type'] ?? '';
410
411
                // you'll have to var_dump $block to understand this and maybe replace \n or \r with a visibile char
412
                $request = new RequestDescription(
413
                    $segments,
414
                    $requestUrl,
0 ignored issues
show
Bug introduced by
It seems like $requestUrl can also be of type null; however, parameter $requestUri of POData\UriProcessor\Requ...cription::__construct() does only seem to accept POData\Common\Url, maybe add an additional type check? ( Ignorable by Annotation )

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

414
                    /** @scrutinizer ignore-type */ $requestUrl,
Loading history...
415
                    $this->maxServiceVersion,
416
                    null, // $this->requestVersion,
417
                    null, // $this->requestMaxVersion,
418
                    $contentType,
419
                    null,
420
                    $headers['Content-ID'] ?? null,
421
                    $requestMethod
422
                );
423
424
                $request->_setBody($body, $contentType);
425
426
                $this->_parts[] = $request;
427
            }
428
        }
429
    }
430
431
    /**
432
     * Define request data from body
433
     * @param string $dataType
434
     */
435
    private function _readData(string $dataType) {
436
        $this->_data = null;
437
        $string = file_get_contents('php://input');
438
        $this->_processData($string, $dataType);
439
    }
440
441
    /**
442
     * Get request data from body
443
     */
444
    private function _setBody(string $string, string $dataType) {
445
        $this->_data = null;
446
        $this->_processData($string, $dataType);
447
    }
448
    /**
449
     * Get request data from body
450
     */
451
    public function getData() {
452
        return $this->_data;
453
    }
454
455
    /**
456
     * Raise the minimum client version requirement for this request and
457
     * perform capability negotiation.
458
     *
459
     * @param int $major The major segment of the version
460
     * @param int $minor The minor segment of the version
461
     *
462
     * @throws ODataException If capability negotiation fails.
463
     */
464 22
    public function raiseMinVersionRequirement($major, $minor) {
465 22
        if ($this->requiredMinRequestVersion->raiseVersion($major, $minor))
466
        {
467 22
            $this->validateVersions();
468
        }
469
    }
470
471
    /**
472
     * Raise the response version for this request and perform capability negotiation.
473
     *
474
     *
475
     * @param int $major The major segment of the version
476
     * @param int $minor The minor segment of the version
477
     *
478
     * @throws ODataException If capability negotiation fails.
479
     */
480 109
    public function raiseResponseVersion($major, $minor) {
481 109
        if ($this->requiredMinResponseVersion->raiseVersion($major, $minor)) {
482 59
            $this->validateVersions();
483
        }
484
485
    }
486
487
    /**
488
     * Gets collection of segment descriptors containing information about
489
     * each segment in the resource path part of the request uri.
490
     *
491
     * @return SegmentDescriptor[]
492
     */
493 94
    public function getSegments()
494
    {
495 94
        return $this->segments;
496
    }
497
498
    /**
499
     * Gets reference to the descriptor of last segment.
500
     *
501
     * @return SegmentDescriptor
502
     */
503 1
    public function getLastSegment()
504
    {
505 1
        return $this->lastSegment;
506
    }
507
508
    /**
509
     * Gets kind of resource targeted by the resource path.
510
     *
511
     * @return int
512
     */
513 94
    public function getTargetKind()
514
    {
515 94
        return $this->lastSegment->getTargetKind();
516
    }
517
518
    /**
519
     * Gets kind of 'source of data' targeted by the resource path.
520
     *
521
     * @return int
522
     */
523 88
    public function getTargetSource()
524
    {
525
        /*
526
        if (!empty($this->_parts)) {
527
            $results = array();
528
            foreach ($this->_parts as &$part) {
529
                $results[] = $part->lastSegment->getTargetSource();
530
            }
531
            return $results;
532
        }
533
        */
534 88
        return $this->lastSegment->getTargetSource();
535
    }
536
537
    /**
538
     * Gets reference to the ResourceSetWrapper instance targeted by
539
     * the resource path, ResourceSetWrapper will present in the
540
     * following cases:
541
     * if the last segment descriptor describes
542
     *      (a) resource set
543
     *          http://server/NW.svc/Customers
544
     *          http://server/NW.svc/Customers('ALFKI')
545
     *          http://server/NW.svc/Customers('ALFKI')/Orders
546
     *          http://server/NW.svc/Customers('ALFKI')/Orders(123)
547
     *          http://server/NW.svc/Customers('ALFKI')/$links/Orders
548
     *      (b) resource set reference
549
     *          http://server/NW.svc/Orders(123)/Customer
550
     *          http://server/NW.svc/Orders(123)/$links/Customer
551
     *      (c) $count
552
     *          http://server/NW.svc/Customers/$count
553
     * ResourceSet wrapper will be absent (NULL) in the following cases:
554
     * if the last segment descriptor describes
555
     *      (a) Primitive
556
     *          http://server/NW.svc/Customers('ALFKI')/Country
557
     *      (b) $value on primitive type
558
     *          http://server/NW.svc/Customers('ALFKI')/Country/$value
559
     *      (c) Complex
560
     *          http://server/NW.svc/Customers('ALFKI')/Address
561
     *      (d) Bag
562
     *          http://server/NW.svc/Employees(123)/Emails
563
     *      (e) MLE
564
     *          http://server/NW.svc/Employees(123)/$value
565
     *      (f) Named Stream
566
     *          http://server/NW.svc/Employees(123)/Thumnail48_48
567
     *      (g) metadata
568
     *          http://server/NW.svc/$metadata
569
     *      (h) service directory
570
     *          http://server/NW.svc
571
     *      (i) $bath
572
     *          http://server/NW.svc/$batch
573
     *
574
     * @return ResourceSetWrapper|null
575
     */
576 88
    public function getTargetResourceSetWrapper()
577
    {
578 88
        return $this->lastSegment->getTargetResourceSetWrapper();
579
    }
580
581
    /**
582
     * Gets reference to the ResourceType instance targeted by
583
     * the resource path, ResourceType will present in the
584
     * following cases:
585
     * if the last segment descriptor describes
586
     *      (a) resource set
587
     *          http://server/NW.svc/Customers
588
     *          http://server/NW.svc/Customers('ALFKI')
589
     *          http://server/NW.svc/Customers('ALFKI')/Orders
590
     *          http://server/NW.svc/Customers('ALFKI')/Orders(123)
591
     *          http://server/NW.svc/Customers('ALFKI')/$links/Orders
592
     *      (b) resource set reference
593
     *          http://server/NW.svc/Orders(123)/Customer
594
     *          http://server/NW.svc/Orders(123)/$links/Customer
595
     *      (c) $count
596
     *          http://server/NW.svc/Customers/$count
597
     *      (d) Primitive
598
     *          http://server/NW.svc/Customers('ALFKI')/Country
599
     *      (e) $value on primitive type
600
     *          http://server/NW.svc/Customers('ALFKI')/Country/$value
601
     *      (f) Complex
602
     *          http://server/NW.svc/Customers('ALFKI')/Address
603
     *      (g) Bag
604
     *          http://server/NW.svc/Employees(123)/Emails
605
     *      (h) MLE
606
     *          http://server/NW.svc/Employees(123)/$value
607
     *      (i) Named Stream
608
     *          http://server/NW.svc/Employees(123)/Thumnail48_48
609
     * ResourceType will be absent (NULL) in the following cases:
610
     * if the last segment descriptor describes
611
     *      (a) metadata
612
     *          http://server/NW.svc/$metadata
613
     *      (b) service directory
614
     *          http://server/NW.svc
615
     *      (c) $bath
616
     *          http://server/NW.svc/$batch
617
     *
618
     * @return ResourceType|null
619
     */
620 88
    public function getTargetResourceType()
621
    {
622 88
        return $this->lastSegment->getTargetResourceType();
623
    }
624
625
    /**
626
     * Gets reference to the ResourceProperty instance targeted by
627
     * the resource path, ResourceProperty will present in the
628
     * following cases:
629
     * if the last segment descriptor describes
630
     *      (a) resource set (after 1 level)
631
     *          http://server/NW.svc/Customers('ALFKI')/Orders
632
     *          http://server/NW.svc/Customers('ALFKI')/Orders(123)
633
     *          http://server/NW.svc/Customers('ALFKI')/$links/Orders
634
     *      (b) resource set reference
635
     *          http://server/NW.svc/Orders(123)/Customer
636
     *          http://server/NW.svc/Orders(123)/$links/Customer
637
     *      (c) $count
638
     *          http://server/NW.svc/Customers/$count
639
     *      (d) Primitive
640
     *          http://server/NW.svc/Customers('ALFKI')/Country
641
     *      (e) $value on primitive type
642
     *          http://server/NW.svc/Customers('ALFKI')/Country/$value
643
     *      (f) Complex
644
     *          http://server/NW.svc/Customers('ALFKI')/Address
645
     *      (g) Bag
646
     *          http://server/NW.svc/Employees(123)/Emails
647
     *      (h) MLE
648
     *          http://server/NW.svc/Employees(123)/$value
649
     *
650
     * ResourceType will be absent (NULL) in the following cases:
651
     * if the last segment descriptor describes
652
     *      (a) If last segment is the only segment pointing to
653
     *          ResourceSet (single or multiple)
654
     *          http://server/NW.svc/Customers
655
     *          http://server/NW.svc/Customers('ALFKI')
656
     *      (b) Named Stream
657
     *          http://server/NW.svc/Employees(123)/Thumnail48_48
658
     *      (c) metadata
659
     *          http://server/NW.svc/$metadata
660
     *      (d) service directory
661
     *          http://server/NW.svc
662
     *      (e) $bath
663
     *          http://server/NW.svc/$batch
664
     *
665
     * @return ResourceProperty|null
666
     */
667 1
    public function getProjectedProperty()
668
    {
669 1
        return  $this->lastSegment->getProjectedProperty();
670
    }
671
672
    /**
673
     * Gets the name of the container for results.
674
     *
675
     * @return string|null
676
     */
677
    public function getContainerName()
678
    {
679
        return $this->_containerName;
680
    }
681
682
    /**
683
     * Sets the name of the container for results.
684
     *
685
     * @param string $containerName The container name.
686
     *
687
     * @return void
688
     */
689 94
    public function setContainerName($containerName)
690
    {
691 94
        $this->_containerName = $containerName;
692
    }
693
694
    /**
695
     * Whether thr request targets a single result or not.
696
     *
697
     * @return boolean
698
     */
699 88
    public function isSingleResult()
700
    {
701 88
        return $this->lastSegment->isSingleResult();
702
    }
703
704
    /**
705
     * Gets the identifier associated with the the resource path.
706
     *
707
     * @return string
708
     */
709 94
    public function getIdentifier()
710
    {
711 94
        return $this->lastSegment->getIdentifier();
712
    }
713
714
    /**
715
     * Gets the request uri.
716
     *
717
     * @return Url
718
     */
719 1
    public function getRequestUrl()
720
    {
721 1
        return $this->requestUrl;
722
    }
723
724
    /**
725
     * Gets the value of $skip query option
726
     *
727
     * @return int|null The value of $skip query option, NULL if $skip is absent.
728
     *
729
     */
730 85
    public function getSkipCount()
731
    {
732 85
        return $this->_skipCount;
733
    }
734
735
    /**
736
     * Sets skip value
737
     *
738
     * @param int $skipCount The value of $skip query option.
739
     *
740
     * @return void
741
     */
742 5
    public function setSkipCount($skipCount)
743
    {
744 5
        $this->_skipCount = $skipCount;
745
    }
746
747
    /**
748
     * Gets the value of take count
749
     *
750
     * @return int|null The value of take, NULL if no take to be applied.
751
     *
752
     */
753 85
    public function getTopCount()
754
    {
755 85
        return $this->_topCount;
756
    }
757
758
    /**
759
     * Sets the value of take count
760
     *
761
     * @param int $topCount The value of take query option
762
     *
763
     * @return void
764
     */
765 57
    public function setTopCount($topCount)
766
    {
767 57
        $this->_topCount = $topCount;
768
    }
769
770
    /**
771
     * Gets the value of $top query option
772
     *
773
     * @return int|null The value of $top query option, NULL if $top is absent.
774
     *
775
     */
776 6
    public function getTopOptionCount()
777
    {
778 6
        return $this->_topOptionCount;
779
    }
780
781
    /**
782
     * Sets top value
783
     *
784
     * @param int $topOptionCount The value of $top query option
785
     *
786
     * @return void
787
     */
788 7
    public function setTopOptionCount($topOptionCount)
789
    {
790 7
        $this->_topOptionCount = $topOptionCount;
791
    }
792
793
    /**
794
     * Gets sorting (orderby) information, this function return
795
     * sorting information in 3 cases:
796
     * (1) if $orderby option is specified in the request uri
797
     * (2) if $skip or $top option is specified in the request uri
798
     * (3) if server side paging is enabled for the resource targeted
799
     *     by the request uri.
800
     *
801
     * @return InternalOrderByInfo|null
802
     */
803 66
    public function getInternalOrderByInfo()
804
    {
805 66
        return $this->internalOrderByInfo;
806
    }
807
808
    /**
809
     * Sets sorting (orderby) information.
810
     *
811
     * @param InternalOrderByInfo &$internalOrderByInfo The sorting information.
812
     *
813
     * @return void
814
     */
815 57
    public function setInternalOrderByInfo(InternalOrderByInfo &$internalOrderByInfo)
816
    {
817 57
        $this->internalOrderByInfo = $internalOrderByInfo;
818
    }
819
820
    /**
821
     * Gets the parsed details for $skiptoken option.
822
     *
823
     * @return InternalSkipTokenInfo|null Returns parsed details of $skiptoken option, NULL if $skiptoken is absent.
824
     *
825
     */
826 10
    public function getInternalSkipTokenInfo()
827
    {
828 10
        return $this->_internalSkipTokenInfo;
829
    }
830
831
    /**
832
     * Sets $skiptoken information.
833
     *
834
     * @param InternalSkipTokenInfo &$internalSkipTokenInfo The paging information.
835
     *
836
     * @return void
837
     */
838 3
    public function setInternalSkipTokenInfo(
839
        InternalSkipTokenInfo &$internalSkipTokenInfo
840
    ) {
841 3
        $this->_internalSkipTokenInfo = $internalSkipTokenInfo;
842
    }
843
844
    /**
845
     *
846
     * @return FilterInfo|null Returns parsed details of $filter option, NULL if $filter is absent.
847
     *
848
     */
849 51
    public function getFilterInfo()
850
    {
851 51
        return $this->_filterInfo;
852
    }
853
854
    /**
855
     *
856
     * @param FilterInfo $filterInfo The filter information.
857
     *
858
     */
859 44
    public function setFilterInfo(FilterInfo $filterInfo)
860
    {
861 44
        $this->_filterInfo = $filterInfo;
862
    }
863
864
    /**
865
     * Sets $expand and $select information.
866
     *
867
     * @param RootProjectionNode &$rootProjectionNode Root of the projection tree.
868
     *
869
     * @return void
870
     */
871 58
    public function setRootProjectionNode(RootProjectionNode &$rootProjectionNode)
872
    {
873 58
        $this->_rootProjectionNode = $rootProjectionNode;
874
    }
875
876
    /**
877
     * Gets the root of the tree describing expand and select options,
878
     *
879
     * @return RootProjectionNode|null Returns parsed details of $expand
880
     *                                 and $select options, NULL if
881
     *                                 $both options are absent.
882
     */
883 12
    public function getRootProjectionNode()
884
    {
885 12
        return $this->_rootProjectionNode;
886
    }
887
888
889
    /**
890
     * Gets the count of result set if $count or $inlinecount=allpages
891
     * has been applied otherwise NULL
892
     *
893
     * @return int|null
894
     */
895 6
    public function getCountValue()
896
    {
897 6
        return $this->_countValue;
898
    }
899
900
    /**
901
     * Sets the count of result set.
902
     *
903
     * @param int $countValue The count value.
904
     *
905
     * @return void
906
     */
907 4
    public function setCountValue($countValue)
908
    {
909 4
        $this->_countValue = $countValue;
910
    }
911
912
    /**
913
     * To set the flag indicating the execution status as true.
914
     *
915
     * @return void
916
     */
917
    public function setExecuted()
918
    {
919
        $this->_isExecuted = true;
920
    }
921
922
    /**
923
     * To check whether to execute the query using IDSQP.
924
     *
925
     * @return boolean True if query need to be executed, False otherwise.
926
     */
927
    public function needExecution()
928
    {
929
        return !$this->_isExecuted
930
            && ($this->lastSegment->getTargetKind() != TargetKind::METADATA)
931
            && ($this->lastSegment->getTargetKind() != TargetKind::SERVICE_DIRECTORY);
932
    }
933
934
    /**
935
     * To check if the resource path is a request for link uri.
936
     *
937
     * @return boolean True if request is for link uri else false.
938
     */
939 84
    public function isLinkUri()
940
    {
941 84
        return (($this->_segmentCount > 2) && ($this->segments[$this->_segmentCount - 2]->getTargetKind() == TargetKind::LINK));
942
    }
943
944
    /**
945
     * To check if the resource path is a request for meida resource
946
     *
947
     * @return boolean True if request is for media resource else false.
948
     */
949 80
    public function isMediaResource()
950
    {
951 80
        return ($this->lastSegment->getTargetKind() == TargetKind::MEDIA_RESOURCE);
952
    }
953
954
    /**
955
     * To check if the resource path is a request for named stream
956
     *
957
     * @return boolean True if request is for named stream else false.
958
     */
959 80
    public function isNamedStream()
960
    {
961 80
        return $this->isMediaResource() && !($this->lastSegment->getIdentifier() === ODataConstants::URI_VALUE_SEGMENT);
962
    }
963
964
    /**
965
     * Get ResourceStreamInfo for the media link entry or named stream request.
966
     *
967
     * @return ResourceStreamInfo|null Instance of ResourceStreamInfo if the
968
     *         current request targets named stream, NULL for MLE
969
     */
970 1
    public function getResourceStreamInfo()
971
    {
972
        //assert($this->isMediaResource)
973 1
        if ($this->isNamedStream()) {
974
            return $this->getTargetResourceType()
975
                ->tryResolveNamedStreamByName(
976
                    $this->lastSegment->getIdentifier()
977
                );
978
        }
979
980 1
        return null;
981
    }
982
983
    /**
984
     * Gets the resource instance targeted by the request uri.
985
     * Note: This value will be populated after query execution only.
986
     *
987
     * @return mixed
988
     */
989 6
    public function getTargetResult()
990
    {
991 6
        if (!empty($this->_parts)) {
992
            $results = array();
993
            foreach ($this->_parts as &$part) {
994
                $results[] = $part->lastSegment->getResult();
995
            }
996
            return $results;
997
        } else {
998 6
            return $this->lastSegment->getResult();
999
        }
1000
    }
1001
1002
    /**
1003
     * Gets the OData version the server used to generate the response.
1004
     *
1005
     * @return Version
1006
     */
1007 34
    public function getResponseVersion()
1008
    {
1009
1010 34
        return $this->requiredMinResponseVersion;
1011
    }
1012
1013
    /**
1014
     * Checks whether etag headers are allowed for this request.
1015
     *
1016
     * @return boolean True if ETag header (If-Match or If-NoneMatch)
1017
     *                 is allowed for the request, False otherwise.
1018
     */
1019
    public function isETagHeaderAllowed()
1020
    {
1021
        return $this->lastSegment->isSingleResult()
1022
            && ($this->queryType != QueryType::COUNT)
1023
            && !$this->isLinkUri()
1024
            && (is_null($this->_rootProjectionNode)
1025
                || !($this->_rootProjectionNode->isExpansionSpecified())
1026
                );
1027
    }
1028
1029
    /**
1030
     * Gets collection of known data service versions, currently 1.0, 2.0 and 3.0.
1031
     *
1032
     * @return Version[]
1033
     */
1034 115
    public static function getKnownDataServiceVersions()
1035
    {
1036 115
        if (is_null(self::$_knownDataServiceVersions)) {
0 ignored issues
show
introduced by
The condition is_null(self::_knownDataServiceVersions) is always false.
Loading history...
1037 1
            self::$_knownDataServiceVersions = array(
1038 1
                new Version(1, 0),
1039 1
                new Version(2, 0),
1040 1
                new Version(3, 0)
1041 1
            );
1042
        }
1043
1044 115
        return self::$_knownDataServiceVersions;
1045
    }
1046
1047
    /**
1048
     * This function is used to perform following checking (validation)
1049
     * for capability negotiation.
1050
     *  (1) Check client request's 'DataServiceVersion' header value is
1051
     *      less than or equal to the minimum version required to intercept
1052
     *      the response
1053
     *  (2) Check client request's 'MaxDataServiceVersion' header value is
1054
     *      less than or equal to the version of protocol required to generate
1055
     *      the response
1056
     *  (3) Check the configured maximum protocol version is less than or equal
1057
     *      to the version of protocol required to generate the response
1058
     *  In addition to these checking, this function is also responsible for
1059
     *  initializing the properties representing 'DataServiceVersion' and
1060
     *  'MaxDataServiceVersion'.
1061
     *
1062
     *
1063
     * @throws ODataException If any of the above 3 check fails.
1064
     */
1065 61
    public function validateVersions() {
1066
1067
        //If the request version is below the minimum version required by supplied request arguments..throw an exception
1068 61
        if ($this->requestVersion->compare($this->requiredMinRequestVersion) < 0) {
1069 4
            throw ODataException::createBadRequestError(
1070 4
                Messages::requestVersionTooLow(
1071 4
                    $this->requestVersion->toString(),
1072 4
                    $this->requiredMinRequestVersion->toString()
1073 4
                )
1074 4
            );
1075
        }
1076
1077
        //If the requested max version is below the version required to fulfill the response...throw an exception
1078 59
        if ($this->requestMaxVersion->compare($this->requiredMinResponseVersion) < 0) {
1079 27
            throw ODataException::createBadRequestError(
1080 27
                Messages::requestVersionTooLow(
1081 27
                    $this->requestMaxVersion->toString(),
1082 27
                    $this->requiredMinResponseVersion->toString()
1083 27
                )
1084 27
            );
1085
        }
1086
1087
        //If the max version supported by the service is below the version required to fulfill the response..throw an exception
1088 38
        if ($this->maxServiceVersion->compare($this->requiredMinResponseVersion) < 0) {
1089 9
            throw ODataException::createBadRequestError(
1090 9
                Messages::requestVersionIsBiggerThanProtocolVersion(
1091 9
                    $this->requiredMinResponseVersion->toString(),
1092 9
                    $this->maxServiceVersion->toString()
1093 9
                )
1094 9
            );
1095
        }
1096
    }
1097
1098
    /**
1099
     * Validates the given version in string format and returns the version as instance of Version
1100
     *
1101
     * @param string $versionHeader The DataServiceVersion or MaxDataServiceVersion header value
1102
     * @param string $headerName    The name of the header
1103
     *
1104
     * @return Version
1105
     *
1106
     * @throws ODataException If the version is malformed or not supported
1107
     */
1108 115
    private static function parseVersionHeader($versionHeader, $headerName)
1109
    {
1110 115
        $libName = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $libName is dead and can be removed.
Loading history...
1111 115
        $versionHeader = trim($versionHeader);
1112 115
        $libNameIndex = strpos($versionHeader, ';');
1113 115
        if ($libNameIndex !== false) {
1114
            $libName = substr($versionHeader, $libNameIndex);
1115
        } else {
1116 115
            $libNameIndex = strlen($versionHeader);
1117
        }
1118
1119 115
        $dotIndex = -1;
1120 115
        for ($i = 0; $i < $libNameIndex; $i++) {
1121 115
            if ($versionHeader[$i] == '.') {
1122
1123
                //Throw an exception if we find more than 1 dot
1124 115
                if ($dotIndex != -1) {
1125
                    throw ODataException::createBadRequestError(
1126
                        Messages::requestDescriptionInvalidVersionHeader(
1127
                            $versionHeader,
1128
                            $headerName
1129
                        )
1130
                    );
1131
                }
1132
1133 115
                $dotIndex = $i;
1134 115
            } else if ($versionHeader[$i] < '0' || $versionHeader[$i] > '9') {
1135
                throw ODataException::createBadRequestError(
1136
                    Messages::requestDescriptionInvalidVersionHeader(
1137
                        $versionHeader,
1138
                        $headerName
1139
                    )
1140
                );
1141
            }
1142
        }
1143
1144
1145 115
        $major = intval(substr($versionHeader, 0, $dotIndex));
1146 115
        $minor = 0;
1147
1148
        //Apparently the . is optional
1149 115
        if ($dotIndex != -1) {
1150 115
            if ($dotIndex == 0) {
1151
                //If it starts with a ., throw an exception
1152
                throw ODataException::createBadRequestError(
1153
                    Messages::requestDescriptionInvalidVersionHeader(
1154
                        $versionHeader,
1155
                        $headerName
1156
                    )
1157
                );
1158
            }
1159 115
            $minor = intval(substr($versionHeader, $dotIndex + 1, $libNameIndex));
1160
        }
1161
1162
1163 115
        $version = new Version($major, $minor);
1164
1165
        //TODO: move this somewhere...
1166 115
        $isSupportedVersion = false;
1167 115
        foreach (self::getKnownDataServiceVersions() as $version1) {
1168 115
            if ($version->compare($version1) == 0) {
1169 115
                $isSupportedVersion = true;
1170 115
                break;
1171
            }
1172
        }
1173
1174 115
        if (!$isSupportedVersion) {
1175
            $availableVersions = null;
1176
            foreach (self::getKnownDataServiceVersions() as $version1) {
1177
                $availableVersions .= $version1->toString() . ', ';
1178
            }
1179
1180
            $availableVersions = rtrim($availableVersions, ', ');
0 ignored issues
show
Bug introduced by
It seems like $availableVersions can also be of type null; however, parameter $string of rtrim() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

1180
            $availableVersions = rtrim(/** @scrutinizer ignore-type */ $availableVersions, ', ');
Loading history...
1181
            throw ODataException::createBadRequestError(
1182
                Messages::requestDescriptionUnSupportedVersion(
1183
                    $headerName,
1184
                    $versionHeader,
1185
                    $availableVersions
1186
                )
1187
            );
1188
        }
1189
1190 115
        return $version;
1191
    }
1192
1193
    /**
1194
     * Gets reference to the UriProcessor instance.
1195
     *
1196
     * @return UriProcessor
1197
     */
1198
    public function getUriProcessor()
1199
    {
1200
        return $this->_uriProcessor;
1201
    }
1202
1203
    /**
1204
     * Set reference to UriProcessor instance.
1205
     *
1206
     * @param UriProcessor $uriProcessor Reference to the UriProcessor
1207
     *
1208
     * @return void
1209
     */
1210 88
    public function setUriProcessor(UriProcessor $uriProcessor)
1211
    {
1212 88
        $this->_uriProcessor = $uriProcessor;
1213
    }
1214
1215 88
    public function getQueryStringItem(string $item)
1216
    {
1217 88
        return $this->requestUrl->getQueryStringItem($item);
1218
    }
1219
}
1220