SegmentParser::_createFirstSegmentDescriptor()   B
last analyzed

Complexity

Conditions 9
Paths 11

Size

Total Lines 61
Code Lines 38

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 45
CRAP Score 9

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 38
c 1
b 0
f 0
dl 0
loc 61
ccs 45
cts 45
cp 1
rs 7.7564
cc 9
nc 11
nop 3
crap 9

How to fix   Long Method   

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\ResourcePathProcessor\SegmentParser;
4
5
use POData\Providers\Metadata\ResourceType;
6
use POData\Providers\Metadata\ResourceTypeKind;
7
use POData\Providers\Metadata\ResourcePropertyKind;
8
use POData\Providers\ProvidersWrapper;
9
use POData\Common\ODataConstants;
10
use POData\Common\Messages;
11
use POData\Common\ODataException;
12
use POData\UriProcessor\ResourcePathProcessor\SegmentParser\SegmentDescriptor;
13
14
/**
15
 * Class SegmentParser
16
 *
17
 * A parser to parse the segments in OData URI, Uri is made up of bunch of segments,
18
 * each segment is separated by '/' character
19
 * e.g. Customers('ALFKI')/Orders(2134)/Order_Details/Product
20
 *
21
 * Syntax of an OData segment is:
22
 * Segment       : identifier[(keyPredicate)]?            : e.g. Customers, Customers('ALFKI'), Order_Details(OrderID=123, ProductID=11)
23
 * keyPredicate  : keyValue | NamedKeyValue
24
 * NamedKeyValue : keyName=keyValue [, keyName=keyValue]* : e.g. OrderID=123, ProductID=11
25
 * keyValue      : quotedValue | unquotedValue            : e.g. 'ALFKI'
26
 * quotedValue   : "'" nqChar "'"
27
 * unquotedValue : [.*]                                   : Any character
28
 * nqChar        : [^\']                                  : Character other than quotes
29
 *
30
 * @package POData\UriProcessor\ResourcePathProcessor\SegmentParser
31
 */
32
class SegmentParser
33
{
34
    /**
35
     * The wrapper of IMetadataProvider and IQueryProvider
36
     *
37
     * @var ProvidersWrapper
38
     */
39
    private $providerWrapper;
40
41
    /**
42
     * Array of SegmentDescriptor describing each segment in the request Uri
43
     *
44
     * @var SegmentDescriptor[]
45
     */
46
    private $_segmentDescriptors = array();
47
48
    /**
49
     * Constructs a new instance of SegmentParser
50
     *
51
     * @param ProvidersWrapper $providerWrapper Reference to metadata and query provider wrapper
52
     *
53
     */
54 105
    private function __construct(ProvidersWrapper $providerWrapper) {
55 105
        $this->providerWrapper = $providerWrapper;
56
    }
57
58
    /**
59
     * Parse the given Uri segments
60
     *
61
     * @param string[] $segments Array of segments in the request Uri
62
     *
63
     * @param ProvidersWrapper $providerWrapper Reference to metadata and query provider wrapper
64
     * @param boolean $checkForRights  Whether to check for rights on the resource sets in the segments
65
     *
66
     * @return SegmentDescriptor[]
67
     *
68
     * @throws ODataException If any error occurs while processing segment
69
     */
70 105
    public static function parseRequestUriSegments($segments, ProvidersWrapper $providerWrapper, $checkForRights = true) {
71 105
        $segmentParser = new SegmentParser($providerWrapper);
72 105
        $segmentParser->createSegmentDescriptors($segments, $checkForRights);
73 103
        return $segmentParser->_segmentDescriptors;
74
    }
75
76
    /**
77
     * Extract identifier and key predicate from a segment
78
     *
79
     * @param string $segment The segment from which identifier and key
80
     * @param string &$identifier   On return, this parameter will contain identifier part of the segment
81
     * @param string &$keyPredicate On return, this parameter will contain key predicate part of the segment, null if predicate is absent
82
     *
83
     * @throws ODataException If any error occurs while processing segment
84
     */
85 104
    private function extractSegmentIdentifierAndKeyPredicate($segment, &$identifier, &$keyPredicate) {
86 104
        $predicateStart = strpos($segment, '(');
87 104
        if ($predicateStart === false) {
88 102
            $identifier = $segment;
89 102
            $keyPredicate = null;
90 102
            return;
91
        }
92
93 36
        $segmentLength = strlen($segment);
94 36
        if (strrpos($segment, ')') !== $segmentLength - 1) {
95 1
            throw ODataException::createSyntaxError(Messages::syntaxError());
96
        }
97
98 35
        $identifier = substr($segment, 0, $predicateStart);
99 35
        $predicateStart++;
100 35
        $keyPredicate = substr($segment, $predicateStart, $segmentLength - $predicateStart - 1);
101
    }
102
103
    /**
104
     * Process a collection of OData URI segment strings and turn them into segment descriptors
105
     *
106
     * @param string[] $segments array of segments strings to parse
107
     * @param boolean $checkRights Whether to check for rights or not
108
     *
109
     * @throws ODataException Exception in case of any error found while precessing segments
110
     */
111 105
    private function createSegmentDescriptors($segments, $checkRights)
112
    {
113 105
        if (empty($segments)) {
114
            //If there's no segments, then it's the service root
115 2
            $descriptor = new SegmentDescriptor();
116 2
            $descriptor->setTargetKind(TargetKind::SERVICE_DIRECTORY);
117 2
            $this->_segmentDescriptors[] = $descriptor;
118 2
            return;
119
        }
120
121 104
        $segmentCount = count($segments);
122 104
        $identifier = $keyPredicate = null;
123 104
        $this->extractSegmentIdentifierAndKeyPredicate($segments[0], $identifier, $keyPredicate);
124 103
        $previous = $this->_createFirstSegmentDescriptor(
125 103
            $identifier, $keyPredicate, $checkRights
126 103
        );
127 103
        $this->_segmentDescriptors[0] = $previous;
128
129 103
        for ($i = 1; $i < $segmentCount; $i++) {
130 48
            $current = $this->createNextSegment($previous, $segments[$i], $checkRights);
131
132
133 47
            $current->setPrevious($previous);
134 47
            $previous->setNext($current);
135 47
            $this->_segmentDescriptors[] = $current;
136 47
            $previous = $current;
137
        }
138
139
        //At this point $previous is the final segment..which cannot be a $link
140 102
        if ($previous->getTargetKind() == TargetKind::LINK) {
141 1
            throw ODataException::createBadRequestError(Messages::segmentParserMissingSegmentAfterLink());
142
        }
143
    }
144
145
    /**
146
     * @param string $segment
147
     * @param boolean $checkRights
148
     */
149 48
    private function createNextSegment(SegmentDescriptor $previous, $segment, $checkRights) {
150 48
        $previousKind = $previous->getTargetKind();
151 48
        if ($previousKind == TargetKind::METADATA
152 48
            || $previousKind == TargetKind::BATCH
153 47
            || $previousKind == TargetKind::PRIMITIVE_VALUE
154 47
            || $previousKind == TargetKind::BAG
155 48
            || $previousKind == TargetKind::MEDIA_RESOURCE
156
        ) {
157
            //All these targets are terminal segments, there cannot be anything after them.
158 4
            throw ODataException::resourceNotFoundError(
159 4
                Messages::segmentParserMustBeLeafSegment($previous->getIdentifier())
160 4
            );
161
        }
162
163 47
        $identifier = $keyPredicate = null;
164 47
        $this->extractSegmentIdentifierAndKeyPredicate($segment, $identifier, $keyPredicate);
165 47
        $hasPredicate = !is_null($keyPredicate);
0 ignored issues
show
introduced by
The condition is_null($keyPredicate) is always true.
Loading history...
166 47
        $current = null;
167 47
        if ($previousKind == TargetKind::PRIMITIVE) {
168 3
            if ($identifier !== ODataConstants::URI_VALUE_SEGMENT) {
0 ignored issues
show
introduced by
The condition $identifier !== POData\C...ants::URI_VALUE_SEGMENT is always true.
Loading history...
169 2
                throw ODataException::resourceNotFoundError(
170 2
                    Messages::segmentParserOnlyValueSegmentAllowedAfterPrimitivePropertySegment(
171 2
                        $identifier, $previous->getIdentifier()
172 2
                    )
173 2
                );
174
            }
175
176 2
            $this->_assertion(!$hasPredicate);
177 2
            $current = SegmentDescriptor::createFrom($previous);
178 2
            $current->setIdentifier(ODataConstants::URI_VALUE_SEGMENT);
179 2
            $current->setTargetKind(TargetKind::PRIMITIVE_VALUE);
180 2
            $current->setSingleResult(true);
181 47
        } else if (!is_null($previous->getPrevious()) && $previous->getPrevious()->getIdentifier() === ODataConstants::URI_LINK_SEGMENT && $identifier !== ODataConstants::URI_COUNT_SEGMENT) {
182 1
            throw ODataException::createBadRequestError(
183 1
                Messages::segmentParserNoSegmentAllowedAfterPostLinkSegment($identifier)
184 1
            );
185 47
        } else if ($previousKind == TargetKind::RESOURCE
186 47
            && $previous->isSingleResult()
187 47
            && $identifier === ODataConstants::URI_LINK_SEGMENT
0 ignored issues
show
introduced by
The condition $identifier === POData\C...tants::URI_LINK_SEGMENT is always false.
Loading history...
188
        ) {
189 11
            $this->_assertion(!$hasPredicate);
190 11
            $current = SegmentDescriptor::createFrom($previous);
191 11
            $current->setIdentifier(ODataConstants::URI_LINK_SEGMENT);
192 11
            $current->setTargetKind(TargetKind::LINK);
193
        } else {
194
            //Do a sanity check here
195 47
            if ($previousKind != TargetKind::COMPLEX_OBJECT
196 47
                && $previousKind != TargetKind::RESOURCE
197 47
                && $previousKind != TargetKind::LINK
198
            ) {
199
                throw ODataException::createInternalServerError(
200
                    Messages::segmentParserInconsistentTargetKindState()
201
                );
202
            }
203
204 47
            if (!$previous->isSingleResult() && $identifier !== ODataConstants::URI_COUNT_SEGMENT) {
205 1
                throw ODataException::createBadRequestError(
206 1
                    Messages::segmentParserCannotQueryCollection($previous->getIdentifier())
207 1
                );
208
            }
209
210 47
            $current = new SegmentDescriptor();
211 47
            $current->setIdentifier($identifier);
212 47
            $current->setTargetSource(TargetSource::PROPERTY);
0 ignored issues
show
Bug introduced by
POData\UriProcessor\Reso...\TargetSource::PROPERTY of type integer is incompatible with the type POData\UriProcessor\Reso...mentParser\TargetSource expected by parameter $targetSource of POData\UriProcessor\Reso...ptor::setTargetSource(). ( Ignorable by Annotation )

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

212
            $current->setTargetSource(/** @scrutinizer ignore-type */ TargetSource::PROPERTY);
Loading history...
213 47
            $projectedProperty = $previous->getTargetResourceType()->resolveProperty($identifier);
214 47
            $current->setProjectedProperty($projectedProperty);
215
216 47
            if ($identifier === ODataConstants::URI_COUNT_SEGMENT) {
0 ignored issues
show
introduced by
The condition $identifier === POData\C...ants::URI_COUNT_SEGMENT is always false.
Loading history...
217 17
                if ($previousKind != TargetKind::RESOURCE) {
218 2
                    throw ODataException::createBadRequestError(
219 2
                        Messages::segmentParserCountCannotBeApplied($previous->getIdentifier())
220 2
                    );
221
                }
222
223 16
                if ($previous->isSingleResult()) {
224 1
                    throw ODataException::createBadRequestError(
225 1
                        Messages::segmentParserCountCannotFollowSingleton($previous->getIdentifier())
226 1
                    );
227
                }
228
229 16
                $current->setTargetKind(TargetKind::PRIMITIVE_VALUE);
230 16
                $current->setSingleResult(true);
231 16
                $current->setTargetResourceSetWrapper(
232 16
                    $previous->getTargetResourceSetWrapper()
233 16
                );
234 16
                $current->setTargetResourceType(
235 16
                    $previous->getTargetResourceType()
236 16
                );
237 33
            } else if ($identifier === ODataConstants::URI_VALUE_SEGMENT
0 ignored issues
show
introduced by
The condition $identifier === POData\C...ants::URI_VALUE_SEGMENT is always false.
Loading history...
238 33
                && $previousKind == TargetKind::RESOURCE
239
            ) {
240 1
                $current->setSingleResult(true);
241 1
                $current->setTargetResourceType(
242 1
                    $previous->getTargetResourceType()
243 1
                );
244 1
                $current->setTargetKind(TargetKind::MEDIA_RESOURCE);
245 33
            } else if (is_null($projectedProperty)) {
246 2
                if (!is_null($previous->getTargetResourceType())
247 2
                    && !is_null($previous->getTargetResourceType()->tryResolveNamedStreamByName($identifier))
248
                ) {
249 1
                    $current->setTargetKind(TargetKind::MEDIA_RESOURCE);
250 1
                    $current->setSingleResult(true);
251 1
                    $current->setTargetResourceType(
252 1
                        $previous->getTargetResourceType()
253 1
                    );
254
                } else {
255 2
                    throw ODataException::createResourceNotFoundError($identifier);
256
                }
257
            } else {
258 33
                $current->setTargetResourceType($projectedProperty->getResourceType());
259 33
                $current->setSingleResult($projectedProperty->getKind() != ResourcePropertyKind::RESOURCESET_REFERENCE);
260 33
                if ($previousKind == TargetKind::LINK
261 33
                    && $projectedProperty->getTypeKind() != ResourceTypeKind::ENTITY
262
                ) {
263 1
                    throw ODataException::createBadRequestError(
264 1
                        Messages::segmentParserLinkSegmentMustBeFollowedByEntitySegment(
265 1
                            $identifier
266 1
                        )
267 1
                    );
268
                }
269
270 33
                switch ($projectedProperty->getKind()) {
271
                    case ResourcePropertyKind::COMPLEX_TYPE:
272 4
                        $current->setTargetKind(TargetKind::COMPLEX_OBJECT);
273 4
                        break;
274
                    case ResourcePropertyKind::BAG | ResourcePropertyKind::PRIMITIVE:
275
                    case ResourcePropertyKind::BAG | ResourcePropertyKind::COMPLEX_TYPE:
276 4
                        $current->setTargetKind(TargetKind::BAG);
277 4
                        break;
278
                    case ResourcePropertyKind::RESOURCE_REFERENCE:
279
                    case ResourcePropertyKind::RESOURCESET_REFERENCE:
280 26
                        $current->setTargetKind(TargetKind::RESOURCE);
281 26
                        $resourceSetWrapper = $this->providerWrapper->getResourceSetWrapperForNavigationProperty($previous->getTargetResourceSetWrapper(), $previous->getTargetResourceType(), $projectedProperty);
282 26
                        if (is_null($resourceSetWrapper)) {
283 1
                            throw ODataException::createResourceNotFoundError($projectedProperty->getName());
284
                        }
285
286 26
                        $current->setTargetResourceSetWrapper($resourceSetWrapper);
287 26
                        break;
288
                    default:
289 6
                        if (!$projectedProperty->isKindOf(ResourcePropertyKind::PRIMITIVE)) {
290
                            throw ODataException::createInternalServerError(
291
                                Messages::segmentParserUnExpectedPropertyKind(
292
                                    'Primitive'
293
                                )
294
                            );
295
                        }
296
297 6
                        $current->setTargetKind(TargetKind::PRIMITIVE);
298 6
                        break;
299
                }
300
301 33
                if ($hasPredicate) {
0 ignored issues
show
introduced by
The condition $hasPredicate is always false.
Loading history...
302 8
                    $this->_assertion(!$current->isSingleResult());
303 8
                    $keyDescriptor = $this->_createKeyDescriptor(
304 8
                        $identifier . '(' . $keyPredicate . ')',
305 8
                        $projectedProperty->getResourceType(),
306 8
                        $keyPredicate
307 8
                    );
308 8
                    $current->setKeyDescriptor($keyDescriptor);
309 8
                    if (!$keyDescriptor->isEmpty()) {
310 8
                        $current->setSingleResult(true);
311
                    }
312
                }
313
314 33
                if ($checkRights && !is_null($current->getTargetResourceSetWrapper())) {
315 26
                    $current->getTargetResourceSetWrapper()
316 26
                        ->checkResourceSetRightsForRead(
317 26
                            $current->isSingleResult()
318 26
                        );
319
                }
320
            }
321
        }
322
323 47
        return $current;
324
    }
325
326
    /**
327
     * Create SegmentDescriptor for the first segment
328
     *
329
     * @param string $segmentIdentifier The identifier part of the first segment
330
     * @param string $keyPredicate The predicate part of the first segment if any else NULL
331
     * @param boolean $checkRights Whether to check the rights on this segment
332
     *
333
     * @return SegmentDescriptor Descriptor for the first segment
334
     *
335
     * @throws ODataException Exception if any validation fails
336
     */
337 103
    private function _createFirstSegmentDescriptor($segmentIdentifier, $keyPredicate, $checkRights) {
338 103
        $descriptor = new SegmentDescriptor();
339 103
        $descriptor->setIdentifier($segmentIdentifier);
340
341 103
        if ($segmentIdentifier === ODataConstants::URI_METADATA_SEGMENT) {
342 3
            $this->_assertion(is_null($keyPredicate));
343 3
            $descriptor->setTargetKind(TargetKind::METADATA);
344 3
            return $descriptor;
345
        }
346
347 103
        if ($segmentIdentifier === ODataConstants::URI_BATCH_SEGMENT) {
348 3
            $this->_assertion(is_null($keyPredicate));
349 3
            $descriptor->setTargetKind(TargetKind::BATCH);
350 3
            $descriptor->setTargetSource(TargetSource::ENTITY_SET);
0 ignored issues
show
Bug introduced by
POData\UriProcessor\Reso...argetSource::ENTITY_SET of type integer is incompatible with the type POData\UriProcessor\Reso...mentParser\TargetSource expected by parameter $targetSource of POData\UriProcessor\Reso...ptor::setTargetSource(). ( Ignorable by Annotation )

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

350
            $descriptor->setTargetSource(/** @scrutinizer ignore-type */ TargetSource::ENTITY_SET);
Loading history...
351 3
            return $descriptor;
352
        }
353
354 101
        if ($segmentIdentifier === ODataConstants::URI_COUNT_SEGMENT) {
355 1
            throw ODataException::createBadRequestError(
356 1
                Messages::segmentParserSegmentNotAllowedOnRoot(
357 1
                    ODataConstants::URI_COUNT_SEGMENT
358 1
                )
359 1
            );
360
        }
361
362 101
        if ($segmentIdentifier === ODataConstants::URI_LINK_SEGMENT) {
363 1
            throw ODataException::createBadRequestError(
364 1
                Messages::segmentParserSegmentNotAllowedOnRoot(
365 1
                    ODataConstants::URI_LINK_SEGMENT
366 1
                )
367 1
            );
368
        }
369
370 101
        $resourceSetWrapper = $this->providerWrapper->resolveResourceSet($segmentIdentifier);
371 101
        if ($resourceSetWrapper === null) {
372 1
            throw ODataException::createResourceNotFoundError($segmentIdentifier);
373
        }
374
375 101
        $descriptor->setTargetResourceSetWrapper($resourceSetWrapper);
376 101
        $descriptor->setTargetResourceType($resourceSetWrapper->getResourceType());
377 101
        $descriptor->setTargetSource(TargetSource::ENTITY_SET);
378 101
        $descriptor->setTargetKind(TargetKind::RESOURCE);
379 101
        if ($keyPredicate !== null) {
0 ignored issues
show
introduced by
The condition $keyPredicate !== null is always true.
Loading history...
380 35
            $keyDescriptor = $this->_createKeyDescriptor(
381 35
                $segmentIdentifier . '(' . $keyPredicate . ')',
382 35
                $resourceSetWrapper->getResourceType(),
383 35
                $keyPredicate
384 35
            );
385 35
            $descriptor->setKeyDescriptor($keyDescriptor);
386 35
            if (!$keyDescriptor->isEmpty()) {
387 35
                $descriptor->setSingleResult(true);
388
            }
389
        }
390
391 101
        if ($checkRights) {
392 101
            $resourceSetWrapper->checkResourceSetRightsForRead(
393 101
                $descriptor->isSingleResult()
394 101
            );
395
        }
396
397 101
        return $descriptor;
398
    }
399
400
    /**
401
     * Creates an instance of KeyDescriptor by parsing a key predicate, also
402
     * validates the KeyDescriptor
403
     *
404
     * @param string       $segment      The uri segment in the form identifier
405
     *                                   (keyPredicate)
406
     * @param ResourceType $resourceType The Resource type whose keys need to
407
     *                                   be parsed
408
     * @param string       $keyPredicate The key predicate to parse and generate
409
     *                                   KeyDescriptor for
410
     *
411
     * @return KeyDescriptor Describes the key values in the $keyPredicate
412
     *
413
     * @throws ODataException Exception if any error occurs while parsing and
414
     *                                  validating the key predicate
415
     */
416 35
    private function _createKeyDescriptor($segment, ResourceType $resourceType, $keyPredicate) {
417
        /**
418
         * @var KeyDescriptor $keyDescriptor
419
         */
420 35
        $keyDescriptor = null;
421 35
        if (!KeyDescriptor::tryParseKeysFromKeyPredicate($keyPredicate, $keyDescriptor)) {
422
            throw ODataException::createSyntaxError(Messages::syntaxError());
423
        }
424
425
        // Note: Currently WCF Data Service does not support multiple
426
        // 'Positional values' so Order_Details(10248, 11) is not valid
427 35
        if (!$keyDescriptor->isEmpty()
428 35
            && !$keyDescriptor->areNamedValues()
429 35
            && $keyDescriptor->valueCount() > 1
430
        ) {
431 1
            throw ODataException::createSyntaxError(
432 1
                Messages::segmentParserKeysMustBeNamed($segment)
433 1
            );
434
        }
435
436
437 35
        $keyDescriptor->validate($segment, $resourceType);
438
439
440 35
        return $keyDescriptor;
441
    }
442
443
    /**
444
     * Assert that the given condition is true, if false throw
445
     * ODataException for syntax error
446
     *
447
     * @param boolean $condition The condition to assert
448
     *
449
     * @return void
450
     *
451
     * @throws ODataException
452
     */
453 18
    private function _assertion($condition)
454
    {
455 18
        if (!$condition) {
456 2
            throw ODataException::createSyntaxError(Messages::syntaxError());
457
        }
458
    }
459
}
460