Test Failed
Push — master ( a1e735...534e7d )
by Bálint
13:42 queued 13s
created

RequestDescription::__construct()   B

Complexity

Conditions 6
Paths 24

Size

Total Lines 58
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 29
c 2
b 0
f 0
dl 0
loc 58
rs 8.8337
cc 6
nc 24
nop 9

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

354
            array_pop(/** @scrutinizer ignore-type */ $a_blocks);
Loading history...
355
356
            // loop data blocks
357
            foreach ($a_blocks as $id => $block)
358
            {
359
                if (empty($block)) {
360
                                continue;
361
                }
362
363
                $requestMethod = null;
364
                $requestUrl = null;
365
366
                $reader_state = RequestDescription::STATE_PART_START;
367
                $body = null;
368
                $headers = [];
369
                $part_headers = [];
370
371
                foreach (explode("\n", $block) as $line) {
372
                    if (empty($line)) {
373
                        if ($reader_state == RequestDescription::STATE_PART_START) {
374
                            $reader_state = RequestDescription::STATE_HTTP_STATUS;
375
                        } else if ($reader_state == RequestDescription::STATE_HEADERS) {
376
                            $reader_state = RequestDescription::STATE_BODY;
377
                            $body = '';
378
                        }
379
                        continue;
380
                    }
381
                    $m = array();
382
                    if (in_array($reader_state, [RequestDescription::STATE_HTTP_STATUS, RequestDescription::STATE_HEADERS]) && preg_match('/(?<key>[^:]+):\s*(?<value>.*?)\s*$/', $line, $m)) {
383
                        if ($reader_state == RequestDescription::STATE_HTTP_STATUS) {
384
                            $headers[$m['key']] = $m['value'];
385
                        } else if ($reader_state == RequestDescription::STATE_HEADERS) {
386
                            $part_headers[$m['key']] = $m['value'];
387
                        }
388
                    } else if (preg_match('/^(GET|PUT|POST|PATCH|DELETE)\s+(.*?)(\s+HTTP\/\d\.\d)?\s*$/', $line, $m)) {
389
                        $requestMethod = trim($m[1]);
390
                        $requestUrl = new Url(trim($m[2]), false);
391
                        $reader_state = RequestDescription::STATE_HEADERS;
392
                    } else if ($reader_state = RequestDescription::STATE_BODY) {
393
                        $body .= $line."\n";
394
                    }
395
                }
396
397
                $host = $this->_service->getHost();
398
                $absoluteServiceUri = $host->getAbsoluteServiceUri();
399
400
                $requestUriSegments = array_slice(
401
                    $requestUrl->getSegments(),
402
                    $absoluteServiceUri->getSegmentCount()
403
                );
404
405
                $segments = SegmentParser::parseRequestUriSegments(
406
                    $requestUriSegments,
407
                    $this->_service->getProvidersWrapper(),
408
                    true
409
                );
410
411
                $contentType = $part_headers['Content-Type'] ?? null;
412
413
                // you'll have to var_dump $block to understand this and maybe replace \n or \r with a visibile char
414
                $request = new RequestDescription(
415
                    $segments,
416
                    $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

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