Completed
Branch master (990ea3)
by Bálint
05:57 queued 02:43
created

RequestDescription::getProjectedProperty()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
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 QueryType
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
	 * @param SegmentDescriptor[] $segmentDescriptors Description of segments in the resource path.
237
	 * @param Url $requestUri
238
	 * @param Version $serviceMaxVersion
239
	 * @param null|string $requestVersion
240
	 * @param null|string $maxRequestVersion
241
     * @param string $dataType
242
	 */
243
	public function __construct($segmentDescriptors, Url $requestUri, Version $serviceMaxVersion, $requestVersion, $maxRequestVersion, $dataType = null, IService $service = null)
244
    {
245
        $this->segments = $segmentDescriptors;
246
        $this->_segmentCount = count($this->segments);
247
        $this->requestUrl = $requestUri;
248
        $this->lastSegment = $segmentDescriptors[$this->_segmentCount - 1];
249
	    $this->queryType = QueryType::ENTITIES();
250
        $this->_service = $service;
251
        $this->_parts = array();
252
253
        //we use this for validation checks down in validateVersions...but maybe we should check that outside of this object...
254
        $this->maxServiceVersion = $serviceMaxVersion;
255
256
	    //Per OData 1 & 2 spec we must return the smallest size
257
	    //We start at 1.0 and move it up as features are requested
258
        $this->requiredMinResponseVersion = clone Version::v1();
259
        $this->requiredMinRequestVersion = clone Version::v1();
260
261
262
	    //see http://www.odata.org/documentation/odata-v2-documentation/overview/#ProtocolVersioning
263
	    //if requestVersion isn't there, use Service Max Version
264
	    $this->requestVersion = is_null($requestVersion) ? $serviceMaxVersion : self::parseVersionHeader($requestVersion, ODataConstants::ODATAVERSIONHEADER);
265
266
	    //if max version isn't there, use the request version
267
	    $this->requestMaxVersion = is_null($maxRequestVersion) ? $this->requestVersion : self::parseVersionHeader($maxRequestVersion, ODataConstants::ODATAMAXVERSIONHEADER);
268
269
        //if it's OData v3..things change a bit
270
        if ($this->maxServiceVersion == Version::v3()) {
271
            if (is_null($maxRequestVersion))
272
            {
273
                //if max request version isn't specified we use the service max version instead of the request version
274
                //thus we favour newer versions
275
                $this->requestMaxVersion = $this->maxServiceVersion;
276
            }
277
278
            //also we change min response version to be the max version, again favoring later things
279
            //note that if the request max version is specified, it is still respected
280
            $this->requiredMinResponseVersion = clone $this->requestMaxVersion;
281
        }
282
283
284
285
        $this->_containerName = null;
286
        $this->_skipCount = null;
287
        $this->_topCount = null;
288
        $this->_topOptionCount = null;
289
        $this->internalOrderByInfo = null;
290
        $this->_internalSkipTokenInfo = null;
291
292
        $this->_filterInfo = null;
293
        $this->_countValue = null;
294
        $this->_isExecuted = false;
295
296
        // Define data from request body
297
        if ($dataType) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $dataType of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
298
            $this->_readData($dataType);
299
        }
300
    }
301
302
    public function getParts() {
303
        return $this->_parts;
304
    }
305
306
    /**
307
     * Define request data from body
308
     * @param string $dataType
309
     */
310
    private function _readData($dataType) {
311
        $this->_data = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array of property $_data.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
312
        $string = file_get_contents('php://input');
313
        if ($dataType === MimeTypes::MIME_APPLICATION_XML) {
314
            $data = Xml2Array::createArray($string);
315
            if (!empty($data['a:entry']['a:content']['m:properties'])) {
316
                $clearData = $data['a:entry']['a:content']['m:properties'];
317
                if (is_array($clearData)) {
318
                    foreach ($clearData as $key => $value) {
319
                        $this->_data[substr($key, 2)] = $value['@value'];
320
                    }
321
                }
322
            }
323
        } elseif ($dataType === MimeTypes::MIME_APPLICATION_JSON) {
324
            $data = json_decode($string, true);
325
            $this->_data = $data;
0 ignored issues
show
Documentation Bug introduced by
It seems like $data of type * is incompatible with the declared type array of property $_data.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
326
        } elseif (explode(';', $dataType)[0] === MimeTypes::MIME_MULTIPART_MIXED) {
327
            preg_match('/boundary=(.*)$/', $dataType, $matches);
328
            $boundary = $matches[1];
329
330
            // split content by boundary and get rid of last -- element
331
            $a_blocks = preg_split("/-+$boundary/", $string);
332
            array_pop($a_blocks);
333
334
            // loop data blocks
335
            foreach ($a_blocks as $id => $block)
336
            {
337
                if (empty($block)) {
338
                                continue;
339
                }
340
341
                $contentType = null;
342
                $requestMethod = null;
0 ignored issues
show
Unused Code introduced by
$requestMethod is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
343
                $requestUrl = null;
344
345
                foreach (explode("\n", $block) as $line) {
346
                    if (empty($line)) {
347
                        continue;
348
                    }
349
                    $m = array();
350
                    if (preg_match('/Content-Type:\s*(.*?)\s*$/', $line, $m)) {
351
                        $contentType = trim($m[1]);
352
                    } else if (preg_match('/^(GET|PUT|POST|PATCH|DELETE)\s+(.*?)(\s+HTTP\/\d\.\d)?\s*$/', $line, $m)) {
353
                        $requestMethod = trim($m[1]);
0 ignored issues
show
Unused Code introduced by
$requestMethod is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
354
                        $requestUrl = new Url(trim($m[2]), false);
355
                    }
356
                }
357
358
                $host = $this->_service->getHost();
359
                $absoluteServiceUri = $host->getAbsoluteServiceUri();
360
361
                $requestUriSegments = array_slice(
362
                    $requestUrl->getSegments(),
363
                    $absoluteServiceUri->getSegmentCount()
364
                );
365
366
                $segments = SegmentParser::parseRequestUriSegments(
367
                    $requestUriSegments,
368
                    $this->_service->getProvidersWrapper(),
369
                    true
370
                );
371
372
                // you'll have to var_dump $block to understand this and maybe replace \n or \r with a visibile char
373
                $request = new RequestDescription(
374
                    $segments,
375
                    $requestUrl,
0 ignored issues
show
Bug introduced by
It seems like $requestUrl defined by null on line 343 can be null; however, POData\UriProcessor\Requ...cription::__construct() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
376
                    $this->maxServiceVersion,
377
                    null, // $this->requestVersion,
378
                    null, // $this->requestMaxVersion,
379
                    $contentType
380
                );
381
382
                $this->_parts[] = $request;
383
            }
384
        }
385
    }
386
387
    /**
388
     * Get request data from body
389
     */
390
    public function getData() {
391
        return $this->_data;
392
    }
393
394
    /**
395
     * Raise the minimum client version requirement for this request and
396
     * perform capability negotiation.
397
     *
398
     * @param int $major The major segment of the version
399
     * @param int $minor The minor segment of the version
400
     *
401
     * @throws ODataException If capability negotiation fails.
402
     */
403
    public function raiseMinVersionRequirement($major, $minor) {
404
        if ($this->requiredMinRequestVersion->raiseVersion($major, $minor))
405
        {
406
            $this->validateVersions();
407
        }
408
    }
409
410
    /**
411
     * Raise the response version for this request and perform capability negotiation.
412
     *
413
     *
414
     * @param int $major The major segment of the version
415
     * @param int $minor The minor segment of the version
416
     *
417
     * @throws ODataException If capability negotiation fails.
418
     */
419
    public function raiseResponseVersion($major, $minor) {
420
        if($this->requiredMinResponseVersion->raiseVersion($major, $minor)){
421
            $this->validateVersions();
422
        }
423
424
    }
425
426
    /**
427
     * Gets collection of segment descriptors containing information about
428
     * each segment in the resource path part of the request uri.
429
     *
430
     * @return SegmentDescriptor[]
431
     */
432
    public function getSegments()
433
    {
434
        return $this->segments;
435
    }
436
437
    /**
438
     * Gets reference to the descriptor of last segment.
439
     *
440
     * @return SegmentDescriptor
441
     */
442
    public function getLastSegment()
443
    {
444
        return $this->lastSegment;
445
    }
446
447
    /**
448
     * Gets kind of resource targeted by the resource path.
449
     *
450
     * @return TargetKind
451
     */
452
    public function getTargetKind()
453
    {
454
        return $this->lastSegment->getTargetKind();
455
    }
456
457
    /**
458
     * Gets kind of 'source of data' targeted by the resource path.
459
     *
460
     * @return TargetSource
461
     */
462
    public function getTargetSource()
463
    {
464
        /*
0 ignored issues
show
Unused Code Comprehensibility introduced by
62% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
465
        if (!empty($this->_parts)) {
466
            $results = array();
467
            foreach ($this->_parts as &$part) {
468
                $results[] = $part->lastSegment->getTargetSource();
469
            }
470
            return $results;
471
        }
472
        */
473
        return $this->lastSegment->getTargetSource();
474
    }
475
476
    /**
477
     * Gets reference to the ResourceSetWrapper instance targeted by
478
     * the resource path, ResourceSetWrapper will present in the
479
     * following cases:
480
     * if the last segment descriptor describes
481
     *      (a) resource set
482
     *          http://server/NW.svc/Customers
483
     *          http://server/NW.svc/Customers('ALFKI')
484
     *          http://server/NW.svc/Customers('ALFKI')/Orders
485
     *          http://server/NW.svc/Customers('ALFKI')/Orders(123)
486
     *          http://server/NW.svc/Customers('ALFKI')/$links/Orders
487
     *      (b) resource set reference
488
     *          http://server/NW.svc/Orders(123)/Customer
489
     *          http://server/NW.svc/Orders(123)/$links/Customer
490
     *      (c) $count
491
     *          http://server/NW.svc/Customers/$count
492
     * ResourceSet wrapper will be absent (NULL) in the following cases:
493
     * if the last segment descriptor describes
494
     *      (a) Primitive
495
     *          http://server/NW.svc/Customers('ALFKI')/Country
496
     *      (b) $value on primitive type
497
     *          http://server/NW.svc/Customers('ALFKI')/Country/$value
498
     *      (c) Complex
499
     *          http://server/NW.svc/Customers('ALFKI')/Address
500
     *      (d) Bag
501
     *          http://server/NW.svc/Employees(123)/Emails
502
     *      (e) MLE
503
     *          http://server/NW.svc/Employees(123)/$value
504
     *      (f) Named Stream
505
     *          http://server/NW.svc/Employees(123)/Thumnail48_48
506
     *      (g) metadata
507
     *          http://server/NW.svc/$metadata
508
     *      (h) service directory
509
     *          http://server/NW.svc
510
     *      (i) $bath
511
     *          http://server/NW.svc/$batch
512
     *
513
     * @return ResourceSetWrapper|null
514
     */
515
    public function getTargetResourceSetWrapper()
516
    {
517
        return $this->lastSegment->getTargetResourceSetWrapper();
518
    }
519
520
    /**
521
     * Gets reference to the ResourceType instance targeted by
522
     * the resource path, ResourceType will present in the
523
     * following cases:
524
     * if the last segment descriptor describes
525
     *      (a) resource set
526
     *          http://server/NW.svc/Customers
527
     *          http://server/NW.svc/Customers('ALFKI')
528
     *          http://server/NW.svc/Customers('ALFKI')/Orders
529
     *          http://server/NW.svc/Customers('ALFKI')/Orders(123)
530
     *          http://server/NW.svc/Customers('ALFKI')/$links/Orders
531
     *      (b) resource set reference
532
     *          http://server/NW.svc/Orders(123)/Customer
533
     *          http://server/NW.svc/Orders(123)/$links/Customer
534
     *      (c) $count
535
     *          http://server/NW.svc/Customers/$count
536
     *      (d) Primitive
537
     *          http://server/NW.svc/Customers('ALFKI')/Country
538
     *      (e) $value on primitive type
539
     *          http://server/NW.svc/Customers('ALFKI')/Country/$value
540
     *      (f) Complex
541
     *          http://server/NW.svc/Customers('ALFKI')/Address
542
     *      (g) Bag
543
     *          http://server/NW.svc/Employees(123)/Emails
544
     *      (h) MLE
545
     *          http://server/NW.svc/Employees(123)/$value
546
     *      (i) Named Stream
547
     *          http://server/NW.svc/Employees(123)/Thumnail48_48
548
     * ResourceType will be absent (NULL) in the following cases:
549
     * if the last segment descriptor describes
550
     *      (a) metadata
551
     *          http://server/NW.svc/$metadata
552
     *      (b) service directory
553
     *          http://server/NW.svc
554
     *      (c) $bath
555
     *          http://server/NW.svc/$batch
556
     *
557
     * @return ResourceType|null
558
     */
559
    public function getTargetResourceType()
560
    {
561
        return $this->lastSegment->getTargetResourceType();
562
    }
563
564
    /**
565
     * Gets reference to the ResourceProperty instance targeted by
566
     * the resource path, ResourceProperty will present in the
567
     * following cases:
568
     * if the last segment descriptor describes
569
     *      (a) resource set (after 1 level)
570
     *          http://server/NW.svc/Customers('ALFKI')/Orders
571
     *          http://server/NW.svc/Customers('ALFKI')/Orders(123)
572
     *          http://server/NW.svc/Customers('ALFKI')/$links/Orders
573
     *      (b) resource set reference
574
     *          http://server/NW.svc/Orders(123)/Customer
575
     *          http://server/NW.svc/Orders(123)/$links/Customer
576
     *      (c) $count
577
     *          http://server/NW.svc/Customers/$count
578
     *      (d) Primitive
579
     *          http://server/NW.svc/Customers('ALFKI')/Country
580
     *      (e) $value on primitive type
581
     *          http://server/NW.svc/Customers('ALFKI')/Country/$value
582
     *      (f) Complex
583
     *          http://server/NW.svc/Customers('ALFKI')/Address
584
     *      (g) Bag
585
     *          http://server/NW.svc/Employees(123)/Emails
586
     *      (h) MLE
587
     *          http://server/NW.svc/Employees(123)/$value
588
     *
589
     * ResourceType will be absent (NULL) in the following cases:
590
     * if the last segment descriptor describes
591
     *      (a) If last segment is the only segment pointing to
592
     *          ResourceSet (single or multiple)
593
     *          http://server/NW.svc/Customers
594
     *          http://server/NW.svc/Customers('ALFKI')
595
     *      (b) Named Stream
596
     *          http://server/NW.svc/Employees(123)/Thumnail48_48
597
     *      (c) metadata
598
     *          http://server/NW.svc/$metadata
599
     *      (d) service directory
600
     *          http://server/NW.svc
601
     *      (e) $bath
602
     *          http://server/NW.svc/$batch
603
     *
604
     * @return ResourceProperty|null
605
     */
606
    public function getProjectedProperty()
607
    {
608
        return  $this->lastSegment->getProjectedProperty();
609
    }
610
611
    /**
612
     * Gets the name of the container for results.
613
     *
614
     * @return string|null
615
     */
616
    public function getContainerName()
617
    {
618
        return $this->_containerName;
619
    }
620
621
    /**
622
     * Sets the name of the container for results.
623
     *
624
     * @param string $containerName The container name.
625
     *
626
     * @return void
627
     */
628
    public function setContainerName($containerName)
629
    {
630
        $this->_containerName = $containerName;
631
    }
632
633
    /**
634
     * Whether thr request targets a single result or not.
635
     *
636
     * @return boolean
637
     */
638
    public function isSingleResult()
639
    {
640
        return $this->lastSegment->isSingleResult();
641
    }
642
643
    /**
644
     * Gets the identifier associated with the the resource path.
645
     *
646
     * @return string
647
     */
648
    public function getIdentifier()
649
    {
650
        return $this->lastSegment->getIdentifier();
651
    }
652
653
    /**
654
     * Gets the request uri.
655
     *
656
     * @return Url
657
     */
658
    public function getRequestUrl()
659
    {
660
        return $this->requestUrl;
661
    }
662
663
    /**
664
     * Gets the value of $skip query option
665
     *
666
     * @return int|null The value of $skip query option, NULL if $skip is absent.
667
     *
668
     */
669
    public function getSkipCount()
670
    {
671
        return $this->_skipCount;
672
    }
673
674
    /**
675
     * Sets skip value
676
     *
677
     * @param int $skipCount The value of $skip query option.
678
     *
679
     * @return void
680
     */
681
    public function setSkipCount($skipCount)
682
    {
683
        $this->_skipCount = $skipCount;
684
    }
685
686
    /**
687
     * Gets the value of take count
688
     *
689
     * @return int|null The value of take, NULL if no take to be applied.
690
     *
691
     */
692
    public function getTopCount()
693
    {
694
        return $this->_topCount;
695
    }
696
697
    /**
698
     * Sets the value of take count
699
     *
700
     * @param int $topCount The value of take query option
701
     *
702
     * @return void
703
     */
704
    public function setTopCount($topCount)
705
    {
706
        $this->_topCount = $topCount;
707
    }
708
709
    /**
710
     * Gets the value of $top query option
711
     *
712
     * @return int|null The value of $top query option, NULL if $top is absent.
713
     *
714
     */
715
    public function getTopOptionCount()
716
    {
717
        return $this->_topOptionCount;
718
    }
719
720
    /**
721
     * Sets top value
722
     *
723
     * @param int $topOptionCount The value of $top query option
724
     *
725
     * @return void
726
     */
727
    public function setTopOptionCount($topOptionCount)
728
    {
729
        $this->_topOptionCount = $topOptionCount;
730
    }
731
732
    /**
733
     * Gets sorting (orderby) information, this function return
734
     * sorting information in 3 cases:
735
     * (1) if $orderby option is specified in the request uri
736
     * (2) if $skip or $top option is specified in the request uri
737
     * (3) if server side paging is enabled for the resource targeted
738
     *     by the request uri.
739
     *
740
     * @return InternalOrderByInfo|null
741
     */
742
    public function getInternalOrderByInfo()
743
    {
744
        return $this->internalOrderByInfo;
745
    }
746
747
    /**
748
     * Sets sorting (orderby) information.
749
     *
750
     * @param InternalOrderByInfo &$internalOrderByInfo The sorting information.
751
     *
752
     * @return void
753
     */
754
    public function setInternalOrderByInfo(InternalOrderByInfo &$internalOrderByInfo)
755
    {
756
        $this->internalOrderByInfo = $internalOrderByInfo;
757
    }
758
759
    /**
760
     * Gets the parsed details for $skiptoken option.
761
     *
762
     * @return InternalSkipTokenInfo|null Returns parsed details of $skiptoken option, NULL if $skiptoken is absent.
763
     *
764
     */
765
    public function getInternalSkipTokenInfo()
766
    {
767
        return $this->_internalSkipTokenInfo;
768
    }
769
770
    /**
771
     * Sets $skiptoken information.
772
     *
773
     * @param InternalSkipTokenInfo &$internalSkipTokenInfo The paging information.
774
     *
775
     * @return void
776
     */
777
    public function setInternalSkipTokenInfo(
778
        InternalSkipTokenInfo &$internalSkipTokenInfo
779
    ) {
780
        $this->_internalSkipTokenInfo = $internalSkipTokenInfo;
781
    }
782
783
    /**
784
     *
785
     * @return FilterInfo|null Returns parsed details of $filter option, NULL if $filter is absent.
786
     *
787
     */
788
    public function getFilterInfo()
789
    {
790
        return $this->_filterInfo;
791
    }
792
793
    /**
794
     *
795
     * @param FilterInfo $filterInfo The filter information.
796
     *
797
     */
798
    public function setFilterInfo(FilterInfo $filterInfo)
799
    {
800
        $this->_filterInfo = $filterInfo;
801
    }
802
803
    /**
804
     * Sets $expand and $select information.
805
     *
806
     * @param RootProjectionNode &$rootProjectionNode Root of the projection tree.
807
     *
808
     * @return void
809
     */
810
    public function setRootProjectionNode(RootProjectionNode &$rootProjectionNode)
811
    {
812
        $this->_rootProjectionNode = $rootProjectionNode;
813
    }
814
815
    /**
816
     * Gets the root of the tree describing expand and select options,
817
     *
818
     * @return RootProjectionNode|null Returns parsed details of $expand
819
     *                                 and $select options, NULL if
820
     *                                 $both options are absent.
821
     */
822
    public function getRootProjectionNode()
823
    {
824
        return $this->_rootProjectionNode;
825
    }
826
827
828
    /**
829
     * Gets the count of result set if $count or $inlinecount=allpages
830
     * has been applied otherwise NULL
831
     *
832
     * @return int|null
833
     */
834
    public function getCountValue()
835
    {
836
        return $this->_countValue;
837
    }
838
839
    /**
840
     * Sets the count of result set.
841
     *
842
     * @param int $countValue The count value.
843
     *
844
     * @return void
845
     */
846
    public function setCountValue($countValue)
847
    {
848
        $this->_countValue = $countValue;
849
    }
850
851
    /**
852
     * To set the flag indicating the execution status as true.
853
     *
854
     * @return void
855
     */
856
    public function setExecuted()
857
    {
858
        $this->_isExecuted = true;
859
    }
860
861
    /**
862
     * To check whether to execute the query using IDSQP.
863
     *
864
     * @return boolean True if query need to be executed, False otherwise.
865
     */
866
    public function needExecution()
867
    {
868
        return !$this->_isExecuted
869
            && ($this->lastSegment->getTargetKind() != TargetKind::METADATA())
870
            && ($this->lastSegment->getTargetKind() != TargetKind::SERVICE_DIRECTORY());
871
    }
872
873
    /**
874
     * To check if the resource path is a request for link uri.
875
     *
876
     * @return boolean True if request is for link uri else false.
877
     */
878
    public function isLinkUri()
879
    {
880
        return (($this->_segmentCount > 2) && ($this->segments[$this->_segmentCount - 2]->getTargetKind() == TargetKind::LINK()));
881
    }
882
883
    /**
884
     * To check if the resource path is a request for meida resource
885
     *
886
     * @return boolean True if request is for media resource else false.
887
     */
888
    public function isMediaResource()
889
    {
890
        return ($this->lastSegment->getTargetKind() == TargetKind::MEDIA_RESOURCE());
891
    }
892
893
    /**
894
     * To check if the resource path is a request for named stream
895
     *
896
     * @return boolean True if request is for named stream else false.
897
     */
898
    public function isNamedStream()
899
    {
900
        return $this->isMediaResource() && !($this->lastSegment->getIdentifier() === ODataConstants::URI_VALUE_SEGMENT);
901
    }
902
903
    /**
904
     * Get ResourceStreamInfo for the media link entry or named stream request.
905
     *
906
     * @return ResourceStreamInfo|null Instance of ResourceStreamInfo if the
907
     *         current request targets named stream, NULL for MLE
908
     */
909
    public function getResourceStreamInfo()
910
    {
911
        //assert($this->isMediaResource)
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
912
        if ($this->isNamedStream()) {
913
            return $this->getTargetResourceType()
914
                ->tryResolveNamedStreamByName(
915
                    $this->lastSegment->getIdentifier()
916
                );
917
        }
918
919
        return null;
920
    }
921
922
    /**
923
     * Gets the resource instance targeted by the request uri.
924
     * Note: This value will be populated after query execution only.
925
     *
926
     * @return mixed
927
     */
928
    public function getTargetResult()
929
    {
930
        if (!empty($this->_parts)) {
931
            $results = array();
932
            foreach ($this->_parts as &$part) {
933
                $results[] = $part->lastSegment->getResult();
934
            }
935
            return $results;
936
        } else {
937
            return $this->lastSegment->getResult();
938
        }
939
    }
940
941
    /**
942
     * Gets the OData version the server used to generate the response.
943
     *
944
     * @return Version
945
     */
946
    public function getResponseVersion()
947
    {
948
949
        return $this->requiredMinResponseVersion;
950
    }
951
952
    /**
953
     * Checks whether etag headers are allowed for this request.
954
     *
955
     * @return boolean True if ETag header (If-Match or If-NoneMatch)
956
     *                 is allowed for the request, False otherwise.
957
     */
958
    public function isETagHeaderAllowed()
959
    {
960
        return $this->lastSegment->isSingleResult()
961
            && ($this->queryType != QueryType::COUNT())
962
            && !$this->isLinkUri()
963
            && (is_null($this->_rootProjectionNode)
964
                || !($this->_rootProjectionNode->isExpansionSpecified())
965
                );
966
    }
967
968
    /**
969
     * Gets collection of known data service versions, currently 1.0, 2.0 and 3.0.
970
     *
971
     * @return Version[]
972
     */
973
    public static function getKnownDataServiceVersions()
974
    {
975 View Code Duplication
        if (is_null(self::$_knownDataServiceVersions)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
976
            self::$_knownDataServiceVersions = array(
977
                new Version(1, 0),
978
                new Version(2, 0),
979
                new Version(3, 0)
980
            );
981
        }
982
983
        return self::$_knownDataServiceVersions;
984
    }
985
986
    /**
987
     * This function is used to perform following checking (validation)
988
     * for capability negotiation.
989
     *  (1) Check client request's 'DataServiceVersion' header value is
990
     *      less than or equal to the minimum version required to intercept
991
     *      the response
992
     *  (2) Check client request's 'MaxDataServiceVersion' header value is
993
     *      less than or equal to the version of protocol required to generate
994
     *      the response
995
     *  (3) Check the configured maximum protocol version is less than or equal
996
     *      to the version of protocol required to generate the response
997
     *  In addition to these checking, this function is also responsible for
998
     *  initializing the properties representing 'DataServiceVersion' and
999
     *  'MaxDataServiceVersion'.
1000
     *
1001
     *
1002
     * @throws ODataException If any of the above 3 check fails.
1003
     */
1004
    public function validateVersions() {
1005
1006
        //If the request version is below the minimum version required by supplied request arguments..throw an exception
1007
        if ($this->requestVersion->compare($this->requiredMinRequestVersion) < 0) {
1008
            throw ODataException::createBadRequestError(
1009
                Messages::requestVersionTooLow(
1010
                    $this->requestVersion->toString(),
1011
                    $this->requiredMinRequestVersion->toString()
1012
                )
1013
            );
1014
        }
1015
1016
        //If the requested max version is below the version required to fulfill the response...throw an exception
1017
        if ($this->requestMaxVersion->compare($this->requiredMinResponseVersion) < 0) {
1018
            throw ODataException::createBadRequestError(
1019
                Messages::requestVersionTooLow(
1020
                    $this->requestMaxVersion->toString(),
1021
                    $this->requiredMinResponseVersion->toString()
1022
                )
1023
            );
1024
        }
1025
1026
        //If the max version supported by the service is below the version required to fulfill the response..throw an exception
1027
        if ($this->maxServiceVersion->compare($this->requiredMinResponseVersion) < 0) {
1028
            throw ODataException::createBadRequestError(
1029
                Messages::requestVersionIsBiggerThanProtocolVersion(
1030
                    $this->requiredMinResponseVersion->toString(),
1031
                    $this->maxServiceVersion->toString()
1032
                )
1033
            );
1034
        }
1035
    }
1036
1037
    /**
1038
     * Validates the given version in string format and returns the version as instance of Version
1039
     *
1040
     * @param string $versionHeader The DataServiceVersion or MaxDataServiceVersion header value
1041
     * @param string $headerName    The name of the header
1042
     *
1043
     * @return Version
1044
     *
1045
     * @throws ODataException If the version is malformed or not supported
1046
     */
1047
    private static function parseVersionHeader($versionHeader, $headerName)
1048
    {
1049
        $libName = null;
0 ignored issues
show
Unused Code introduced by
$libName is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1050
        $versionHeader = trim($versionHeader);
1051
        $libNameIndex = strpos($versionHeader, ';');
1052
        if ($libNameIndex !== false) {
1053
            $libName = substr($versionHeader, $libNameIndex);
0 ignored issues
show
Unused Code introduced by
$libName is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1054
        } else {
1055
            $libNameIndex = strlen($versionHeader);
1056
        }
1057
1058
        $dotIndex = -1;
1059
        for ($i = 0; $i < $libNameIndex; $i++) {
1060
            if ($versionHeader[$i] == '.') {
1061
1062
                //Throw an exception if we find more than 1 dot
1063
                if ($dotIndex != -1) {
1064
                    throw ODataException::createBadRequestError(
1065
                        Messages::requestDescriptionInvalidVersionHeader(
1066
                            $versionHeader,
1067
                            $headerName
1068
                        )
1069
                    );
1070
                }
1071
1072
                $dotIndex = $i;
1073
            } else if ($versionHeader[$i] < '0' || $versionHeader[$i] > '9') {
1074
                throw ODataException::createBadRequestError(
1075
                    Messages::requestDescriptionInvalidVersionHeader(
1076
                        $versionHeader,
1077
                        $headerName
1078
                    )
1079
                );
1080
            }
1081
        }
1082
1083
1084
        $major = intval(substr($versionHeader, 0, $dotIndex));
1085
        $minor = 0;
1086
1087
        //Apparently the . is optional
1088
        if ($dotIndex != -1) {
1089
            if ($dotIndex == 0) {
1090
                //If it starts with a ., throw an exception
1091
                throw ODataException::createBadRequestError(
1092
                    Messages::requestDescriptionInvalidVersionHeader(
1093
                        $versionHeader,
1094
                        $headerName
1095
                    )
1096
                );
1097
            }
1098
            $minor = intval(substr($versionHeader, $dotIndex + 1, $libNameIndex));
1099
        }
1100
1101
1102
        $version = new Version($major, $minor);
1103
1104
        //TODO: move this somewhere...
1105
        /*
0 ignored issues
show
Unused Code Comprehensibility introduced by
54% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1106
        $isSupportedVersion = false;
1107
        foreach (self::getKnownDataServiceVersions() as $version1) {
1108
            if ($version->compare($version1) == 0) {
1109
                $isSupportedVersion = true;
1110
                break;
1111
            }
1112
        }
1113
1114
        if (!$isSupportedVersion) {
1115
            $availableVersions = null;
1116
            foreach (self::getKnownDataServiceVersions() as $version1) {
1117
                $availableVersions .= $version1->toString() . ', ';
1118
            }
1119
1120
            $availableVersions = rtrim($availableVersions, ', ');
1121
            throw ODataException::createBadRequestError(
1122
                Messages::requestDescriptionUnSupportedVersion(
1123
                    $headerName,
1124
                    $versionHeader,
1125
	                $availableVersions
1126
                )
1127
            );
1128
        }
1129
	    */
1130
1131
        return $version;
1132
    }
1133
1134
    /**
1135
     * Gets reference to the UriProcessor instance.
1136
     *
1137
     * @return UriProcessor
1138
     */
1139
    public function getUriProcessor()
1140
    {
1141
        return $this->_uriProcessor;
1142
    }
1143
1144
    /**
1145
     * Set reference to UriProcessor instance.
1146
     *
1147
     * @param UriProcessor $uriProcessor Reference to the UriProcessor
1148
     *
1149
     * @return void
1150
     */
1151
    public function setUriProcessor(UriProcessor $uriProcessor)
1152
    {
1153
        $this->_uriProcessor = $uriProcessor;
1154
    }
1155
}
1156