Completed
Push — master ( c7e8d1...934444 )
by Bálint
02:50
created

RequestDescription::_readData()   C

Complexity

Conditions 13
Paths 13

Size

Total Lines 76
Code Lines 49

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 76
rs 5.2753
c 0
b 0
f 0
cc 13
eloc 49
nc 13
nop 1

How to fix   Long Method    Complexity   

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:

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\QueryProcessor\QueryProcessor;
16
use POData\UriProcessor\UriProcessor;
17
use POData\UriProcessor\ResourcePathProcessor\SegmentParser\SegmentParser;
18
use POData\UriProcessor\ResourcePathProcessor\SegmentParser\TargetSource;
19
use POData\UriProcessor\ResourcePathProcessor\SegmentParser\TargetKind;
20
use POData\UriProcessor\ResourcePathProcessor\SegmentParser\SegmentDescriptor;
21
use POData\UriProcessor\QueryProcessor\OrderByParser\InternalOrderByInfo;
22
use POData\UriProcessor\QueryProcessor\SkipTokenParser\InternalSkipTokenInfo;
23
use POData\UriProcessor\QueryProcessor\ExpressionParser\FilterInfo;
24
use POData\UriProcessor\QueryProcessor\ExpandProjectionParser\RootProjectionNode;
25
use POData\Providers\Metadata\ResourceType;
26
use POData\Providers\Query\QueryType;
27
use Verdant\XML2Array;
28
29
30
/**
31
 * Class RequestDescription
32
 * @package POData\UriProcessor
33
 */
34
class RequestDescription
35
{
36
    /**
37
     * Holds the value of HTTP 'DataServiceVersion' header in the request,
38
     * DataServiceVersion header value states the version of the
39
     * Open Data Protocol used by the client to generate the request.
40
     * Refer http://www.odata.org/developers/protocols/overview#ProtocolVersioning
41
     *
42
     * @var Version
43
     */
44
    private $requestVersion = null;
45
46
    /**
47
     * Holds the value of HTTP 'MaxDataServiceVersion' header in the request,
48
     * MaxDataServiceVersion header value specifies the maximum version number
49
     * the client can accept in a response.
50
     * Refer http://www.odata.org/developers/protocols/overview#ProtocolVersioning
51
     *
52
     * @var Version
53
     */
54
    private $requestMaxVersion = null;
55
56
    /**
57
     * This is the value of 'DataServiceVersion' header to be output in the response. this header
58
     * value states the OData version the server used to generate the response.
59
     * While processing the query and result set this value will be keeps on
60
     * updating, after every update this is compared against the
61
     * 'MaxDataServiceVersion' header in the client request to see whether the
62
     * client can interpret the response or not. The client should use this
63
     * value to determine whether it can correctly interpret the response or not.
64
     * Refer http://www.odata.org/developers/protocols/overview#ProtocolVersioning
65
     *
66
     * @var Version
67
     */
68
    private $requiredMinResponseVersion;
69
70
    /**
71
     * The minimum client version requirement, This value keeps getting updated
72
     * during processing of query, this is compared against the
73
     * DataServiceVersion header in the client request and if the client request
74
     * is less than this value then we fail the request (e.g. $count request
75
     * was sent but client said it was Version 1.0).
76
     *
77
     * @var Version
78
     */
79
    private $requiredMinRequestVersion;
80
81
	/** @var Version */
82
	private $maxServiceVersion;
83
84
    /**
85
     * Collection of known data service versions.
86
     *
87
     * @var Version[]
88
     */
89
    private static $_knownDataServiceVersions = null;
90
91
    /**
92
     *
93
     * @var Url
94
     */
95
    private $requestUrl;
96
97
    /**
98
     * Collection of SegmentDescriptor containing information about
99
     * each segment in the resource path part of the request uri.
100
     *
101
     * @var SegmentDescriptor[]
102
     */
103
    private $segments;
104
105
    /**
106
     * Holds reference to the last segment descriptor.
107
     *
108
     * @var SegmentDescriptor
109
     */
110
    private $lastSegment;
111
112
    /**
113
     * The name of the container for results
114
     *
115
     * @var string|null
116
     */
117
    private $_containerName;
118
119
120
	/**
121
	 * The count option specified in the request.
122
	 *
123
	 * @var QueryType
124
	 */
125
	public $queryType;
126
127
    /**
128
     * Number of segments.
129
     *
130
     * @var int
131
     */
132
    private $_segmentCount;
133
134
    /**
135
     * Holds the value of $skip query option, if no $skip option
136
     * found then this parameter will be NULL.
137
     *
138
     * @var int|null
139
     */
140
    private $_skipCount;
141
142
    /**
143
     * Holds the value of take count, this value is depends on
144
     * presence of $top option and configured page size.
145
     *
146
     * @var int|null
147
     */
148
    private $_topCount;
149
150
    /**
151
     * Holds the value of $top query option, if no $top option
152
     * found then this parameter will be NULL.
153
     *
154
     * @var int|null
155
     */
156
    private $_topOptionCount;
157
158
    /**
159
     * Holds the parsed details for sorting, this will
160
     * be set in 3 cases
161
     * (1) if $orderby option is specified in the request uri
162
     * (2) if $skip or $top option is specified in the request uri
163
     * (3) if server side paging is enabled for the resource
164
     *     targeted by the request uri.
165
     *
166
     * @var InternalOrderByInfo|null
167
     */
168
    private $internalOrderByInfo;
169
170
    /**
171
     * Holds the parsed details for $skiptoken option, this will
172
     * be NULL if $skiptoken option is absent.
173
     *
174
     * @var InternalSkipTokenInfo|null
175
     */
176
    private $_internalSkipTokenInfo;
177
178
    /**
179
     * Holds the parsed details for $filter option, this will be NULL if $filter option is absent.
180
     *
181
     * @var FilterInfo|null
182
     */
183
    private $_filterInfo;
184
185
    /**
186
     * Holds reference to the root of the tree describing expand
187
     * and select information, this field will be NULL if no
188
     * $expand or $select specified in the request uri.
189
     *
190
     * @var RootProjectionNode|null
191
     */
192
    private $_rootProjectionNode;
193
194
    /**
195
     * Holds number of entities in the result set, if either $count or
196
     * $inlinecount=allpages is specified, otherwise NULL
197
     *
198
     *
199
     * @var int|null
200
     */
201
    private $_countValue;
202
203
    /**
204
     * Data of request from request body
205
     *
206
     * @var array
207
     */
208
    private $_data;
209
210
    /**
211
     * Flag indicating status of query execution.
212
     *
213
     * @var boolean
214
     */
215
    private $_isExecuted;
216
217
    /**
218
     * Reference to Uri processor.
219
     *
220
     * @var UriProcessor
221
     */
222
    private $_uriProcessor;
223
224
225
    /**
226
     * Reference to the service
227
     * @var IService
228
     */
229
     private $_service;
230
231
    /**
232
     * The parts of a multipart request
233
     * @var array
234
     */
235
     private $_parts;
236
237
	/**
238
	 * @param SegmentDescriptor[] $segmentDescriptors Description of segments in the resource path.
239
	 * @param Url $requestUri
240
	 * @param Version $serviceMaxVersion
241
	 * @param $requestVersion
242
	 * @param $maxRequestVersion
243
     * @param string $dataType
244
	 */
245
	public function __construct($segmentDescriptors, Url $requestUri, Version $serviceMaxVersion, $requestVersion, $maxRequestVersion, $dataType = null, IService $service = null)
246
    {
247
        $this->segments = $segmentDescriptors;
248
        $this->_segmentCount = count($this->segments);
249
        $this->requestUrl = $requestUri;
250
        $this->lastSegment = $segmentDescriptors[$this->_segmentCount - 1];
251
	    $this->queryType = QueryType::ENTITIES();
252
        $this->_service = $service;
253
        $this->_parts = array();
254
255
        //we use this for validation checks down in validateVersions...but maybe we should check that outside of this object...
256
        $this->maxServiceVersion = $serviceMaxVersion;
257
258
	    //Per OData 1 & 2 spec we must return the smallest size
259
	    //We start at 1.0 and move it up as features are requested
260
        $this->requiredMinResponseVersion = clone Version::v1();
261
        $this->requiredMinRequestVersion = clone Version::v1();
262
263
264
	    //see http://www.odata.org/documentation/odata-v2-documentation/overview/#ProtocolVersioning
265
	    //if requestVersion isn't there, use Service Max Version
266
	    $this->requestVersion = is_null($requestVersion) ? $serviceMaxVersion : self::parseVersionHeader($requestVersion, ODataConstants::ODATAVERSIONHEADER);
267
268
	    //if max version isn't there, use the request version
269
	    $this->requestMaxVersion = is_null($maxRequestVersion) ? $this->requestVersion : self::parseVersionHeader($maxRequestVersion, ODataConstants::ODATAMAXVERSIONHEADER);
270
271
        //if it's OData v3..things change a bit
272
        if($this->maxServiceVersion == Version::v3()){
273
            if(is_null($maxRequestVersion))
274
            {
275
                //if max request version isn't specified we use the service max version instead of the request version
276
                //thus we favour newer versions
277
                $this->requestMaxVersion = $this->maxServiceVersion;
278
            }
279
280
            //also we change min response version to be the max version, again favoring later things
281
            //note that if the request max version is specified, it is still respected
282
            $this->requiredMinResponseVersion = clone $this->requestMaxVersion;
283
        }
284
285
286
287
        $this->_containerName = null;
288
        $this->_skipCount = null;
289
        $this->_topCount = null;
290
        $this->_topOptionCount = null;
291
        $this->internalOrderByInfo = null;
292
        $this->_internalSkipTokenInfo = null;
293
294
        $this->_filterInfo = null;
295
        $this->_countValue = null;
296
        $this->_isExecuted = false;
297
298
        // Define data from request body
299
        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...
300
            $this->_readData($dataType);
301
        }
302
    }
303
304
    public function getParts() {
305
        return $this->_parts;
306
    }
307
308
    /**
309
     * Define request data from body
310
     */
311
    private function _readData($dataType) {
312
        $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...
313
        $string = file_get_contents('php://input');
314
        if ($dataType === MimeTypes::MIME_APPLICATION_XML) {
315
            $data = Xml2Array::createArray($string);
316
            if (!empty($data['a:entry']['a:content']['m:properties'])) {
317
                $clearData = $data['a:entry']['a:content']['m:properties'];
318
                if (is_array($clearData)) {
319
                    foreach ($clearData as $key => $value) {
320
                        $this->_data[substr($key, 2)] = $value['@value'];
321
                    }
322
                }
323
            }
324
        }
325
        elseif ($dataType === MimeTypes::MIME_APPLICATION_JSON) {
326
            $data = json_decode($string, true);
327
            $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...
328
        }
329
        elseif (explode(';', $dataType)[0] === MimeTypes::MIME_MULTIPART_MIXED) {
330
            preg_match('/boundary=(.*)$/', $dataType, $matches);
331
            $boundary = $matches[1];
332
333
            // split content by boundary and get rid of last -- element
334
            $a_blocks = preg_split("/-+$boundary/", $string);
335
            array_pop($a_blocks);
336
337
            // loop data blocks
338
            foreach ($a_blocks as $id => $block)
339
            {
340
                if (empty($block))
341
                continue;
342
343
                $contentType = null;
344
                $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...
345
                $requestUrl = null;
346
347
                foreach (explode("\n", $block) as $line) {
348
                    if (empty($line)) continue;
349
                    $m = array();
350
                    if (preg_match('/Content-Type:\s*(.*?)\s*$/', $line, $m)) {
351
                        $contentType = trim($m[1]);
352
                    }
353
                    else if (preg_match('/^(GET|PUT|POST|PATCH|DELETE)\s+(.*?)(\s+HTTP\/\d\.\d)?\s*$/', $line, $m)) {
354
                        $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...
355
                        $requestUrl = new Url(trim($m[2]), false);
356
                    }
357
                }
358
359
                $host = $this->_service->getHost();
360
                $absoluteServiceUri = $host->getAbsoluteServiceUri();
361
362
                $requestUriSegments = array_slice(
363
                    $requestUrl->getSegments(),
364
                    $absoluteServiceUri->getSegmentCount()
365
                );
366
367
                $segments = SegmentParser::parseRequestUriSegments(
368
                    $requestUriSegments,
369
                    $this->_service->getProvidersWrapper(),
370
                    true
371
                );
372
373
                // you'll have to var_dump $block to understand this and maybe replace \n or \r with a visibile char
374
                $request = new RequestDescription(
375
                    $segments,
376
                    $requestUrl,
0 ignored issues
show
Bug introduced by
It seems like $requestUrl defined by null on line 345 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...
377
                    $this->maxServiceVersion,
378
                    null, // $this->requestVersion,
379
                    null, // $this->requestMaxVersion,
380
                    $contentType
381
                );
382
383
                $this->_parts[] = $request;
384
            }
385
        }
386
    }
387
388
    /**
389
     * Get request data from body
390
     */
391
    public function getData() {
392
        return $this->_data;
393
    }
394
395
    /**
396
     * Raise the minimum client version requirement for this request and
397
     * perform capability negotiation.
398
     *
399
     * @param int $major The major segment of the version
400
     * @param int $minor The minor segment of the version
401
     *
402
     * @throws ODataException If capability negotiation fails.
403
     */
404
    public function raiseMinVersionRequirement($major, $minor) {
405
        if($this->requiredMinRequestVersion->raiseVersion($major, $minor))
406
        {
407
	        $this->validateVersions();
408
        }
409
    }
410
411
    /**
412
     * Raise the response version for this request and perform capability negotiation.
413
     *
414
     *
415
     * @param int $major The major segment of the version
416
     * @param int $minor The minor segment of the version
417
     *
418
     * @throws ODataException If capability negotiation fails.
419
     */
420
    public function raiseResponseVersion($major, $minor) {
421
        if($this->requiredMinResponseVersion->raiseVersion($major, $minor)){
422
	        $this->validateVersions();
423
        }
424
425
    }
426
427
    /**
428
     * Gets collection of segment descriptors containing information about
429
     * each segment in the resource path part of the request uri.
430
     *
431
     * @return SegmentDescriptor[]
432
     */
433
    public function getSegments()
434
    {
435
        return $this->segments;
436
    }
437
438
    /**
439
     * Gets reference to the descriptor of last segment.
440
     *
441
     * @return SegmentDescriptor
442
     */
443
    public function getLastSegment()
444
    {
445
        return $this->lastSegment;
446
    }
447
448
    /**
449
     * Gets kind of resource targeted by the resource path.
450
     *
451
     * @return TargetKind
452
     */
453
    public function getTargetKind()
454
    {
455
        return $this->lastSegment->getTargetKind();
456
    }
457
458
    /**
459
     * Gets kind of 'source of data' targeted by the resource path.
460
     *
461
     * @return TargetSource
462
     */
463
    public function getTargetSource()
464
    {
465
        /*
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...
466
        if (!empty($this->_parts)) {
467
            $results = array();
468
            foreach ($this->_parts as &$part) {
469
                $results[] = $part->lastSegment->getTargetSource();
470
            }
471
            return $results;
472
        }
473
        */
474
        return $this->lastSegment->getTargetSource();
475
    }
476
477
    /**
478
     * Gets reference to the ResourceSetWrapper instance targeted by
479
     * the resource path, ResourceSetWrapper will present in the
480
     * following cases:
481
     * if the last segment descriptor describes
482
     *      (a) resource set
483
     *          http://server/NW.svc/Customers
484
     *          http://server/NW.svc/Customers('ALFKI')
485
     *          http://server/NW.svc/Customers('ALFKI')/Orders
486
     *          http://server/NW.svc/Customers('ALFKI')/Orders(123)
487
     *          http://server/NW.svc/Customers('ALFKI')/$links/Orders
488
     *      (b) resource set reference
489
     *          http://server/NW.svc/Orders(123)/Customer
490
     *          http://server/NW.svc/Orders(123)/$links/Customer
491
     *      (c) $count
492
     *          http://server/NW.svc/Customers/$count
493
     * ResourceSet wrapper will be absent (NULL) in the following cases:
494
     * if the last segment descriptor describes
495
     *      (a) Primitive
496
     *          http://server/NW.svc/Customers('ALFKI')/Country
497
     *      (b) $value on primitive type
498
     *          http://server/NW.svc/Customers('ALFKI')/Country/$value
499
     *      (c) Complex
500
     *          http://server/NW.svc/Customers('ALFKI')/Address
501
     *      (d) Bag
502
     *          http://server/NW.svc/Employees(123)/Emails
503
     *      (e) MLE
504
     *          http://server/NW.svc/Employees(123)/$value
505
     *      (f) Named Stream
506
     *          http://server/NW.svc/Employees(123)/Thumnail48_48
507
     *      (g) metadata
508
     *          http://server/NW.svc/$metadata
509
     *      (h) service directory
510
     *          http://server/NW.svc
511
     *      (i) $bath
512
     *          http://server/NW.svc/$batch
513
     *
514
     * @return ResourceSetWrapper|null
515
     */
516
    public function getTargetResourceSetWrapper()
517
    {
518
        return $this->lastSegment->getTargetResourceSetWrapper();
519
    }
520
521
    /**
522
     * Gets reference to the ResourceType instance targeted by
523
     * the resource path, ResourceType will present in the
524
     * following cases:
525
     * if the last segment descriptor describes
526
     *      (a) resource set
527
     *          http://server/NW.svc/Customers
528
     *          http://server/NW.svc/Customers('ALFKI')
529
     *          http://server/NW.svc/Customers('ALFKI')/Orders
530
     *          http://server/NW.svc/Customers('ALFKI')/Orders(123)
531
     *          http://server/NW.svc/Customers('ALFKI')/$links/Orders
532
     *      (b) resource set reference
533
     *          http://server/NW.svc/Orders(123)/Customer
534
     *          http://server/NW.svc/Orders(123)/$links/Customer
535
     *      (c) $count
536
     *          http://server/NW.svc/Customers/$count
537
     *      (d) Primitive
538
     *          http://server/NW.svc/Customers('ALFKI')/Country
539
     *      (e) $value on primitive type
540
     *          http://server/NW.svc/Customers('ALFKI')/Country/$value
541
     *      (f) Complex
542
     *          http://server/NW.svc/Customers('ALFKI')/Address
543
     *      (g) Bag
544
     *          http://server/NW.svc/Employees(123)/Emails
545
     *      (h) MLE
546
     *          http://server/NW.svc/Employees(123)/$value
547
     *      (i) Named Stream
548
     *          http://server/NW.svc/Employees(123)/Thumnail48_48
549
     * ResourceType will be absent (NULL) in the following cases:
550
     * if the last segment descriptor describes
551
     *      (a) metadata
552
     *          http://server/NW.svc/$metadata
553
     *      (b) service directory
554
     *          http://server/NW.svc
555
     *      (c) $bath
556
     *          http://server/NW.svc/$batch
557
     *
558
     * @return ResourceType|null
559
     */
560
    public function getTargetResourceType()
561
    {
562
        return $this->lastSegment->getTargetResourceType();
563
    }
564
565
    /**
566
     * Gets reference to the ResourceProperty instance targeted by
567
     * the resource path, ResourceProperty will present in the
568
     * following cases:
569
     * if the last segment descriptor describes
570
     *      (a) resource set (after 1 level)
571
     *          http://server/NW.svc/Customers('ALFKI')/Orders
572
     *          http://server/NW.svc/Customers('ALFKI')/Orders(123)
573
     *          http://server/NW.svc/Customers('ALFKI')/$links/Orders
574
     *      (b) resource set reference
575
     *          http://server/NW.svc/Orders(123)/Customer
576
     *          http://server/NW.svc/Orders(123)/$links/Customer
577
     *      (c) $count
578
     *          http://server/NW.svc/Customers/$count
579
     *      (d) Primitive
580
     *          http://server/NW.svc/Customers('ALFKI')/Country
581
     *      (e) $value on primitive type
582
     *          http://server/NW.svc/Customers('ALFKI')/Country/$value
583
     *      (f) Complex
584
     *          http://server/NW.svc/Customers('ALFKI')/Address
585
     *      (g) Bag
586
     *          http://server/NW.svc/Employees(123)/Emails
587
     *      (h) MLE
588
     *          http://server/NW.svc/Employees(123)/$value
589
     *
590
     * ResourceType will be absent (NULL) in the following cases:
591
     * if the last segment descriptor describes
592
     *      (a) If last segment is the only segment pointing to
593
     *          ResourceSet (single or multiple)
594
     *          http://server/NW.svc/Customers
595
     *          http://server/NW.svc/Customers('ALFKI')
596
     *      (b) Named Stream
597
     *          http://server/NW.svc/Employees(123)/Thumnail48_48
598
     *      (c) metadata
599
     *          http://server/NW.svc/$metadata
600
     *      (d) service directory
601
     *          http://server/NW.svc
602
     *      (e) $bath
603
     *          http://server/NW.svc/$batch
604
     *
605
     * @return ResourceProperty|null
606
     */
607
    public function getProjectedProperty()
608
    {
609
        return  $this->lastSegment->getProjectedProperty();
610
    }
611
612
    /**
613
     * Gets the name of the container for results.
614
     *
615
     * @return string|null
616
     */
617
    public function getContainerName()
618
    {
619
        return $this->_containerName;
620
    }
621
622
    /**
623
     * Sets the name of the container for results.
624
     *
625
     * @param string $containerName The container name.
626
     *
627
     * @return void
628
     */
629
    public function setContainerName($containerName)
630
    {
631
        $this->_containerName = $containerName;
632
    }
633
634
    /**
635
     * Whether thr request targets a single result or not.
636
     *
637
     * @return boolean
638
     */
639
    public function isSingleResult()
640
    {
641
        return $this->lastSegment->isSingleResult();
642
    }
643
644
    /**
645
     * Gets the identifier associated with the the resource path.
646
     *
647
     * @return string
648
     */
649
    public function getIdentifier()
650
    {
651
        return $this->lastSegment->getIdentifier();
652
    }
653
654
    /**
655
     * Gets the request uri.
656
     *
657
     * @return Url
658
     */
659
    public function getRequestUrl()
660
    {
661
        return $this->requestUrl;
662
    }
663
664
    /**
665
     * Gets the value of $skip query option
666
     *
667
     * @return int|null The value of $skip query option, NULL if $skip is absent.
668
     *
669
     */
670
    public function getSkipCount()
671
    {
672
        return $this->_skipCount;
673
    }
674
675
    /**
676
     * Sets skip value
677
     *
678
     * @param int $skipCount The value of $skip query option.
679
     *
680
     * @return void
681
     */
682
    public function setSkipCount($skipCount)
683
    {
684
        $this->_skipCount = $skipCount;
685
    }
686
687
    /**
688
     * Gets the value of take count
689
     *
690
     * @return int|null The value of take, NULL if no take to be applied.
691
     *
692
     */
693
    public function getTopCount()
694
    {
695
        return $this->_topCount;
696
    }
697
698
    /**
699
     * Sets the value of take count
700
     *
701
     * @param int $topCount The value of take query option
702
     *
703
     * @return void
704
     */
705
    public function setTopCount($topCount)
706
    {
707
        $this->_topCount = $topCount;
708
    }
709
710
    /**
711
     * Gets the value of $top query option
712
     *
713
     * @return int|null The value of $top query option, NULL if $top is absent.
714
     *
715
     */
716
    public function getTopOptionCount()
717
    {
718
        return $this->_topOptionCount;
719
    }
720
721
    /**
722
     * Sets top value
723
     *
724
     * @param int $topOptionCount The value of $top query option
725
     *
726
     * @return void
727
     */
728
    public function setTopOptionCount($topOptionCount)
729
    {
730
        $this->_topOptionCount = $topOptionCount;
731
    }
732
733
    /**
734
     * Gets sorting (orderby) information, this function return
735
     * sorting information in 3 cases:
736
     * (1) if $orderby option is specified in the request uri
737
     * (2) if $skip or $top option is specified in the request uri
738
     * (3) if server side paging is enabled for the resource targeted
739
     *     by the request uri.
740
     *
741
     * @return InternalOrderByInfo|null
742
     */
743
    public function getInternalOrderByInfo()
744
    {
745
        return $this->internalOrderByInfo;
746
    }
747
748
    /**
749
     * Sets sorting (orderby) information.
750
     *
751
     * @param InternalOrderByInfo &$internalOrderByInfo The sorting information.
752
     *
753
     * @return void
754
     */
755
    public function setInternalOrderByInfo(InternalOrderByInfo &$internalOrderByInfo)
756
    {
757
        $this->internalOrderByInfo = $internalOrderByInfo;
758
    }
759
760
    /**
761
     * Gets the parsed details for $skiptoken option.
762
     *
763
     * @return InternalSkipTokenInfo|null Returns parsed details of $skiptoken option, NULL if $skiptoken is absent.
764
     *
765
     */
766
    public function getInternalSkipTokenInfo()
767
    {
768
        return $this->_internalSkipTokenInfo;
769
    }
770
771
    /**
772
     * Sets $skiptoken information.
773
     *
774
     * @param InternalSkipTokenInfo &$internalSkipTokenInfo The paging information.
775
     *
776
     * @return void
777
     */
778
    public function setInternalSkipTokenInfo(
779
        InternalSkipTokenInfo &$internalSkipTokenInfo
780
    ) {
781
        $this->_internalSkipTokenInfo = $internalSkipTokenInfo;
782
    }
783
784
    /**
785
     *
786
     * @return FilterInfo|null Returns parsed details of $filter option, NULL if $filter is absent.
787
     *
788
     */
789
    public function getFilterInfo()
790
    {
791
        return $this->_filterInfo;
792
    }
793
794
    /**
795
     *
796
     * @param FilterInfo $filterInfo The filter information.
797
     *
798
     */
799
    public function setFilterInfo(FilterInfo $filterInfo)
800
    {
801
        $this->_filterInfo = $filterInfo;
802
    }
803
804
    /**
805
     * Sets $expand and $select information.
806
     *
807
     * @param RootProjectionNode &$rootProjectionNode Root of the projection tree.
808
     *
809
     * @return void
810
     */
811
    public function setRootProjectionNode(RootProjectionNode &$rootProjectionNode)
812
    {
813
        $this->_rootProjectionNode =  $rootProjectionNode;
814
    }
815
816
    /**
817
     * Gets the root of the tree describing expand and select options,
818
     *
819
     * @return RootProjectionNode|null Returns parsed details of $expand
820
     *                                 and $select options, NULL if
821
     *                                 $both options are absent.
822
     */
823
    public function getRootProjectionNode()
824
    {
825
        return $this->_rootProjectionNode;
826
    }
827
828
829
    /**
830
     * Gets the count of result set if $count or $inlinecount=allpages
831
     * has been applied otherwise NULL
832
     *
833
     * @return int|null
834
     */
835
    public function getCountValue()
836
    {
837
        return $this->_countValue;
838
    }
839
840
    /**
841
     * Sets the count of result set.
842
     *
843
     * @param int $countValue The count value.
844
     *
845
     * @return void
846
     */
847
    public function setCountValue($countValue)
848
    {
849
        $this->_countValue = $countValue;
850
    }
851
852
    /**
853
     * To set the flag indicating the execution status as true.
854
     *
855
     * @return void
856
     */
857
    public function setExecuted()
858
    {
859
        $this->_isExecuted = true;
860
    }
861
862
    /**
863
     * To check whether to execute the query using IDSQP.
864
     *
865
     * @return boolean True if query need to be executed, False otherwise.
866
     */
867
    public function needExecution()
868
    {
869
        return !$this->_isExecuted
870
            && ($this->lastSegment->getTargetKind() != TargetKind::METADATA())
871
            && ($this->lastSegment->getTargetKind() != TargetKind::SERVICE_DIRECTORY());
872
    }
873
874
    /**
875
     * To check if the resource path is a request for link uri.
876
     *
877
     * @return boolean True if request is for link uri else false.
878
     */
879
    public function isLinkUri()
880
    {
881
        return (($this->_segmentCount > 2) && ($this->segments[$this->_segmentCount - 2]->getTargetKind() == TargetKind::LINK()));
882
    }
883
884
    /**
885
     * To check if the resource path is a request for meida resource
886
     *
887
     * @return boolean True if request is for media resource else false.
888
     */
889
    public function isMediaResource()
890
    {
891
        return ($this->lastSegment->getTargetKind() == TargetKind::MEDIA_RESOURCE());
892
    }
893
894
    /**
895
     * To check if the resource path is a request for named stream
896
     *
897
     * @return boolean True if request is for named stream else false.
898
     */
899
    public function isNamedStream()
900
    {
901
        return $this->isMediaResource() && !($this->lastSegment->getIdentifier() === ODataConstants::URI_VALUE_SEGMENT);
902
    }
903
904
    /**
905
     * Get ResourceStreamInfo for the media link entry or named stream request.
906
     *
907
     * @return ResourceStreamInfo|null Instance of ResourceStreamInfo if the
908
     *         current request targets named stream, NULL for MLE
909
     */
910
    public function getResourceStreamInfo()
911
    {
912
        //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...
913
        if ($this->isNamedStream()) {
914
            return $this->getTargetResourceType()
915
                ->tryResolveNamedStreamByName(
916
                    $this->lastSegment->getIdentifier()
917
                );
918
        }
919
920
        return null;
921
    }
922
923
    /**
924
     * Gets the resource instance targeted by the request uri.
925
     * Note: This value will be populated after query execution only.
926
     *
927
     * @return mixed
928
     */
929
    public function getTargetResult()
930
    {
931
        if (!empty($this->_parts)) {
932
            $results = array();
933
            foreach ($this->_parts as &$part) {
934
                $results[] = $part->lastSegment->getResult();
935
            }
936
            return $results;
937
        } else {
938
            return $this->lastSegment->getResult();
939
        }
940
    }
941
942
    /**
943
     * Gets the OData version the server used to generate the response.
944
     *
945
     * @return Version
946
     */
947
    public function getResponseVersion()
948
    {
949
950
	    return $this->requiredMinResponseVersion;
951
    }
952
953
    /**
954
     * Checks whether etag headers are allowed for this request.
955
     *
956
     * @return boolean True if ETag header (If-Match or If-NoneMatch)
957
     *                 is allowed for the request, False otherwise.
958
     */
959
    public function isETagHeaderAllowed()
960
    {
961
        return $this->lastSegment->isSingleResult()
962
            && ($this->queryType != QueryType::COUNT())
963
            && !$this->isLinkUri()
964
            && (is_null($this->_rootProjectionNode)
965
                || !($this->_rootProjectionNode->isExpansionSpecified())
966
                );
967
    }
968
969
    /**
970
     * Gets collection of known data service versions, currently 1.0, 2.0 and 3.0.
971
     *
972
     * @return Version[]
973
     */
974
    public static function getKnownDataServiceVersions()
975
    {
976 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...
977
            self::$_knownDataServiceVersions = array(
978
	            new Version(1, 0),
979
                new Version(2, 0),
980
                new Version(3, 0)
981
            );
982
        }
983
984
        return self::$_knownDataServiceVersions;
985
    }
986
987
    /**
988
     * This function is used to perform following checking (validation)
989
     * for capability negotiation.
990
     *  (1) Check client request's 'DataServiceVersion' header value is
991
     *      less than or equal to the minimum version required to intercept
992
     *      the response
993
     *  (2) Check client request's 'MaxDataServiceVersion' header value is
994
     *      less than or equal to the version of protocol required to generate
995
     *      the response
996
     *  (3) Check the configured maximum protocol version is less than or equal
997
     *      to the version of protocol required to generate the response
998
     *  In addition to these checking, this function is also responsible for
999
     *  initializing the properties representing 'DataServiceVersion' and
1000
     *  'MaxDataServiceVersion'.
1001
     *
1002
     *
1003
     * @throws ODataException If any of the above 3 check fails.
1004
     */
1005
    public function validateVersions() {
1006
1007
	    //If the request version is below the minimum version required by supplied request arguments..throw an exception
1008
        if ($this->requestVersion->compare($this->requiredMinRequestVersion) < 0) {
1009
			throw ODataException::createBadRequestError(
1010
                Messages::requestVersionTooLow(
1011
	                $this->requestVersion->toString(),
1012
	                $this->requiredMinRequestVersion->toString()
1013
                )
1014
            );
1015
        }
1016
1017
	    //If the requested max version is below the version required to fulfill the response...throw an exception
1018
        if ($this->requestMaxVersion->compare($this->requiredMinResponseVersion) < 0) {
1019
			throw ODataException::createBadRequestError(
1020
                Messages::requestVersionTooLow(
1021
	                $this->requestMaxVersion->toString(),
1022
	                $this->requiredMinResponseVersion->toString()
1023
                )
1024
            );
1025
        }
1026
1027
        //If the max version supported by the service is below the version required to fulfill the response..throw an exception
1028
        if ($this->maxServiceVersion->compare($this->requiredMinResponseVersion) < 0) {
1029
			throw ODataException::createBadRequestError(
1030
                Messages::requestVersionIsBiggerThanProtocolVersion(
1031
	                $this->requiredMinResponseVersion->toString(),
1032
	                $this->maxServiceVersion->toString()
1033
                )
1034
            );
1035
        }
1036
    }
1037
1038
    /**
1039
     * Validates the given version in string format and returns the version as instance of Version
1040
     *
1041
     * @param string $versionHeader The DataServiceVersion or MaxDataServiceVersion header value
1042
     * @param string $headerName    The name of the header
1043
     *
1044
     * @return Version
1045
     *
1046
     * @throws ODataException If the version is malformed or not supported
1047
     */
1048
    private static function parseVersionHeader($versionHeader, $headerName)
1049
    {
1050
        $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...
1051
        $versionHeader = trim($versionHeader);
1052
        $libNameIndex = strpos($versionHeader, ';');
1053
        if ($libNameIndex !== false) {
1054
            $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...
1055
        } else {
1056
            $libNameIndex = strlen($versionHeader);
1057
        }
1058
1059
        $dotIndex = -1;
1060
        for ($i = 0; $i < $libNameIndex; $i++) {
1061
            if ($versionHeader[$i] == '.') {
1062
1063
	            //Throw an exception if we find more than 1 dot
1064
	            if ($dotIndex != -1) {
1065
					throw ODataException::createBadRequestError(
1066
                        Messages::requestDescriptionInvalidVersionHeader(
1067
                            $versionHeader,
1068
                            $headerName
1069
                        )
1070
                    );
1071
                }
1072
1073
                $dotIndex = $i;
1074
            } else if ($versionHeader[$i] < '0' || $versionHeader[$i] > '9') {
1075
				throw ODataException::createBadRequestError(
1076
                    Messages::requestDescriptionInvalidVersionHeader(
1077
                        $versionHeader,
1078
                        $headerName
1079
                    )
1080
                );
1081
            }
1082
        }
1083
1084
1085
	    $major = intval(substr($versionHeader, 0, $dotIndex));
1086
	    $minor = 0;
1087
1088
	   //Apparently the . is optional
1089
        if ($dotIndex != -1) {
1090
            if ($dotIndex == 0) {
1091
	            //If it starts with a ., throw an exception
1092
				throw ODataException::createBadRequestError(
1093
                    Messages::requestDescriptionInvalidVersionHeader(
1094
                        $versionHeader,
1095
                        $headerName
1096
                    )
1097
                );
1098
            }
1099
            $minor = intval(substr($versionHeader, $dotIndex + 1, $libNameIndex));
1100
        }
1101
1102
1103
        $version = new Version($major, $minor);
1104
1105
	    //TODO: move this somewhere...
1106
	    /*
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...
1107
        $isSupportedVersion = false;
1108
        foreach (self::getKnownDataServiceVersions() as $version1) {
1109
            if ($version->compare($version1) == 0) {
1110
                $isSupportedVersion = true;
1111
                break;
1112
            }
1113
        }
1114
1115
        if (!$isSupportedVersion) {
1116
            $availableVersions = null;
1117
            foreach (self::getKnownDataServiceVersions() as $version1) {
1118
                $availableVersions .= $version1->toString() . ', ';
1119
            }
1120
1121
            $availableVersions = rtrim($availableVersions, ', ');
1122
            throw ODataException::createBadRequestError(
1123
                Messages::requestDescriptionUnSupportedVersion(
1124
                    $headerName,
1125
                    $versionHeader,
1126
	                $availableVersions
1127
                )
1128
            );
1129
        }
1130
	    */
1131
1132
        return $version;
1133
    }
1134
1135
    /**
1136
     * Gets reference to the UriProcessor instance.
1137
     *
1138
     * @return UriProcessor
1139
     */
1140
    public function getUriProcessor()
1141
    {
1142
        return $this->_uriProcessor;
1143
    }
1144
1145
    /**
1146
     * Set reference to UriProcessor instance.
1147
     *
1148
     * @param UriProcessor $uriProcessor Reference to the UriProcessor
1149
     *
1150
     * @return void
1151
     */
1152
    public function setUriProcessor(UriProcessor $uriProcessor)
1153
    {
1154
        $this->_uriProcessor = $uriProcessor;
1155
    }
1156
}
1157