Completed
Push — master ( 69fa8f...7e5572 )
by Alex
50s
created

OrderByParser   B

Complexity

Total Complexity 53

Size/Duplication

Total Lines 458
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 13

Importance

Changes 4
Bugs 2 Features 0
Metric Value
wmc 53
lcom 1
cbo 13
dl 0
loc 458
rs 7.4757
c 4
b 2
f 0

7 Methods

Rating   Name   Duplication   Size   Complexity  
F _buildOrderByTree() 0 195 28
C _createOrderInfo() 0 30 7
A __construct() 0 4 1
B parseOrderByClause() 0 33 2
A _generateTopLevelComparisonFunction() 0 21 4
D _readOrderBy() 0 42 9
A _assertion() 0 6 2

How to fix   Complexity   

Complex Class

Complex classes like OrderByParser often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use OrderByParser, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace POData\UriProcessor\QueryProcessor\OrderByParser;
4
5
use POData\UriProcessor\QueryProcessor\ExpressionParser\ExpressionLexer;
6
use POData\UriProcessor\QueryProcessor\ExpressionParser\ExpressionTokenId;
7
use POData\Providers\ProvidersWrapper;
8
use POData\Providers\Metadata\Type\Binary;
9
use POData\Providers\Metadata\ResourceSetWrapper;
10
use POData\Providers\Metadata\ResourceType;
11
use POData\Providers\Metadata\ResourcePropertyKind;
12
use POData\Common\ODataException;
13
use POData\Common\Messages;
14
15
/**
16
 * Class OrderByParser.
17
 *
18
 * Class to parse $orderby query option and perform syntax validation
19
 * and build 'OrderBy Tree' along with next level of validation, the
20
 * created tree is used for building sort functions and 'OrderByInfo' structure.
21
 *
22
 * The syntax of orderby clause is:
23
 *
24
 * OrderByClause         : OrderByPathSegment [, OrderByPathSegment]*
25
 * OrderByPathSegment    : OrderBySubPathSegment[/OrderBySubPathSegment]*[asc|desc]?
26
 * OrderBySubPathSegment : identifier
27
 */
28
class OrderByParser
29
{
30
    /**
31
     * Collection of anonymous sorter function corresponding to
32
     * each orderby path segment.
33
     *
34
     * @var Callable[]
35
     */
36
    private $_comparisonFunctions = array();
37
38
    /**
39
     * The top level sorter function generated from orderby path
40
     * segments.
41
     *
42
     * @var Callable
43
     */
44
    private $_topLevelComparisonFunction;
45
46
    /**
47
     * The structure holds information about the navigation properties
48
     * used in the orderby clause (if any) and orderby path if IDSQP
49
     * implementor want to perform sorting.
50
     *
51
     * @var OrderByInfo
52
     */
53
    private $_orderByInfo;
54
55
    /**
56
     * Reference to metadata and query provider wrapper.
57
     *
58
     * @var ProvidersWrapper
59
     */
60
    private $_providerWrapper;
61
62
    /**
63
     * This object will be of type of the resource set identified by the
64
     * request uri.
65
     *
66
     * @var mixed
67
     */
68
    private $_dummyObject;
69
70
    /**
71
     * Creates new instance of OrderByParser.
72
     *
73
     * @param ProvidersWrapper $providerWrapper Reference to metadata
74
     *                                          and query provider
75
     *                                          wrapper
76
     */
77
    private function __construct(ProvidersWrapper $providerWrapper)
78
    {
79
        $this->_providerWrapper = $providerWrapper;
80
    }
81
82
    /**
83
     * This function perform the following tasks with the help of internal helper
84
     * functions
85
     * (1) Read the orderby clause and perform basic syntax errors
86
     * (2) Build 'Order By Tree', creates anonymous sorter function for each leaf
87
     *     node and check for error
88
     * (3) Build 'OrderInfo' structure, holds information about the navigation
89
     *     properties used in the orderby clause (if any) and orderby path if
90
     *     IDSQP implementor want to perform sorting
91
     * (4) Build top level anonymous sorter function
92
     * (4) Release resources hold by the 'Order By Tree'
93
     * (5) Create 'InternalOrderInfo' structure, which wraps 'OrderInfo' and top
94
     *     level sorter function.
95
     *
96
     * @param ResourceSetWrapper $resourceSetWrapper ResourceSetWrapper for the resource targeted by resource path
97
     * @param ResourceType       $resourceType       ResourceType for the resource targeted by resource path
98
     * @param string             $orderBy            The orderby clause
99
     * @param ProvidersWrapper   $providerWrapper    Reference to the wrapper for IDSQP and IDSMP impl
100
     *
101
     * @return InternalOrderByInfo
102
     *
103
     * @throws ODataException If any error occur while parsing orderby clause
104
     */
105
    public static function parseOrderByClause(
106
        ResourceSetWrapper $resourceSetWrapper,
107
        ResourceType $resourceType,
108
        $orderBy,
109
        ProvidersWrapper $providerWrapper
110
    ) {
111
        $orderByParser = new self($providerWrapper);
112
        try {
113
            $orderByParser->_dummyObject = $resourceType->getInstanceType()->newInstance();
0 ignored issues
show
Bug introduced by
The method newInstance does only exist in ReflectionClass, but not in POData\Providers\Metadata\Type\IType.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
114
        } catch (\ReflectionException $reflectionException) {
115
            throw ODataException::createInternalServerError(Messages::orderByParserFailedToCreateDummyObject());
116
        }
117
        $orderByParser->_rootOrderByNode = new OrderByRootNode($resourceSetWrapper, $resourceType);
0 ignored issues
show
Bug introduced by
The property _rootOrderByNode does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
118
        $orderByPathSegments = $orderByParser->_readOrderBy($orderBy);
119
120
        $orderByParser->_buildOrderByTree($orderByPathSegments);
121
        $orderByParser->_createOrderInfo($orderByPathSegments);
122
        $orderByParser->_generateTopLevelComparisonFunction();
123
        //Recursively release the resources
124
        $orderByParser->_rootOrderByNode->free();
125
        //creates internal order info wrapper
126
        $internalOrderInfo = new InternalOrderByInfo(
127
            $orderByParser->_orderByInfo,
128
            $orderByParser->_comparisonFunctions,
129
            $orderByParser->_topLevelComparisonFunction,
130
            $orderByParser->_dummyObject,
131
            $resourceType
132
        );
133
        unset($orderByParser->_orderByInfo);
134
        unset($orderByParser->_topLevelComparisonFunction);
135
136
        return $internalOrderInfo;
137
    }
138
139
    /**
140
     * Build 'OrderBy Tree' from the given orderby path segments, also build
141
     * comparsion function for each path segment.
142
     *
143
     * @param array(array) &$orderByPathSegments Collection of orderby path segments,
144
     *                                           this is passed by reference
145
     *                                           since we need this function to
146
     *                                           modify this array in two cases:
147
     *                                           1. if asc or desc present, then the
148
     *                                           corresponding sub path segment
149
     *                                           should be removed
150
     *                                           2. remove duplicate orderby path
151
     *                                           segment
152
     *
153
     * @throws ODataException If any error occurs while processing the orderby path
154
     *                        segments
155
     */
156
    private function _buildOrderByTree(&$orderByPathSegments)
157
    {
158
        foreach ($orderByPathSegments as $index1 => &$orderBySubPathSegments) {
159
            $currentNode = $this->_rootOrderByNode;
160
            $currentObject = $this->_dummyObject;
161
            $ascending = true;
162
            $subPathCount = count($orderBySubPathSegments);
163
            // Check sort order is specified in the path, if so set a
164
            // flag and remove that segment
165
            if ($subPathCount > 1) {
166
                if ($orderBySubPathSegments[$subPathCount - 1] === '*desc') {
167
                    $ascending = false;
168
                    unset($orderBySubPathSegments[$subPathCount - 1]);
169
                    --$subPathCount;
170
                } elseif ($orderBySubPathSegments[$subPathCount - 1] === '*asc') {
171
                    unset($orderBySubPathSegments[$subPathCount - 1]);
172
                    --$subPathCount;
173
                }
174
            }
175
176
            $ancestors = array($this->_rootOrderByNode->getResourceSetWrapper()->getName());
177
            foreach ($orderBySubPathSegments as $index2 => $orderBySubPathSegment) {
178
                $isLastSegment = ($index2 == $subPathCount - 1);
179
                $resourceSetWrapper = null;
180
                $resourceType = $currentNode->getResourceType();
181
                $resourceProperty = $resourceType->resolveProperty($orderBySubPathSegment);
182
                if (is_null($resourceProperty)) {
183
                    throw ODataException::createSyntaxError(
184
                        Messages::orderByParserPropertyNotFound(
185
                            $resourceType->getFullName(),
186
                            $orderBySubPathSegment
187
                        )
188
                    );
189
                }
190
191
                if ($resourceProperty->isKindOf(ResourcePropertyKind::BAG)) {
192
                    throw ODataException::createBadRequestError(
193
                        Messages::orderByParserBagPropertyNotAllowed(
194
                            $resourceProperty->getName()
195
                        )
196
                    );
197
                } elseif ($resourceProperty->isKindOf(ResourcePropertyKind::PRIMITIVE)) {
198
                    if (!$isLastSegment) {
199
                        throw ODataException::createBadRequestError(
200
                            Messages::orderByParserPrimitiveAsIntermediateSegment(
201
                                $resourceProperty->getName()
202
                            )
203
                        );
204
                    }
205
206
                    $type = $resourceProperty->getInstanceType();
207
                    if ($type instanceof Binary) {
208
                        throw ODataException::createBadRequestError(Messages::orderByParserSortByBinaryPropertyNotAllowed($resourceProperty->getName()));
209
                    }
210
                } elseif ($resourceProperty->getKind() == ResourcePropertyKind::RESOURCESET_REFERENCE
211
                    || $resourceProperty->getKind() == ResourcePropertyKind::RESOURCE_REFERENCE
212
                ) {
213
                    $this->_assertion($currentNode instanceof OrderByRootNode || $currentNode instanceof OrderByNode);
214
                    $resourceSetWrapper = $currentNode->getResourceSetWrapper();
215
                    $this->_assertion(!is_null($resourceSetWrapper));
216
                    $resourceSetWrapper
217
                        = $this->_providerWrapper->getResourceSetWrapperForNavigationProperty(
218
                            $resourceSetWrapper,
219
                            $resourceType,
220
                            $resourceProperty
221
                        );
222
                    if (is_null($resourceSetWrapper)) {
223
                        throw ODataException::createBadRequestError(
224
                            Messages::badRequestInvalidPropertyNameSpecified(
225
                                $resourceType->getFullName(),
226
                                $orderBySubPathSegment
227
                            )
228
                        );
229
                    }
230
231
                    if ($resourceProperty->getKind() == ResourcePropertyKind::RESOURCESET_REFERENCE) {
232
                        throw ODataException::createBadRequestError(
233
                            Messages::orderByParserResourceSetReferenceNotAllowed(
234
                                $resourceProperty->getName(),
235
                                $resourceType->getFullName()
236
                            )
237
                        );
238
                    }
239
240
                    $resourceSetWrapper->checkResourceSetRightsForRead(true);
241
                    if ($isLastSegment) {
242
                        throw ODataException::createBadRequestError(
243
                            Messages::orderByParserSortByNavigationPropertyIsNotAllowed(
244
                                $resourceProperty->getName()
245
                            )
246
                        );
247
                    }
248
249
                    $ancestors[] = $orderBySubPathSegment;
250
                } elseif ($resourceProperty->isKindOf(ResourcePropertyKind::COMPLEX_TYPE)) {
251
                    if ($isLastSegment) {
252
                        throw ODataException::createBadRequestError(
253
                            Messages::orderByParserSortByComplexPropertyIsNotAllowed(
254
                                $resourceProperty->getName()
255
                            )
256
                        );
257
                    }
258
259
                    $ancestors[] = $orderBySubPathSegment;
260
                } else {
261
                    throw ODataException::createInternalServerError(
262
                        Messages::orderByParserUnexpectedPropertyType()
263
                    );
264
                }
265
266
                $node = $currentNode->findNode($orderBySubPathSegment);
267
                if (is_null($node)) {
268
                    if ($resourceProperty->isKindOf(ResourcePropertyKind::PRIMITIVE)) {
269
                        $node = new OrderByLeafNode(
270
                            $orderBySubPathSegment,
271
                            $resourceProperty,
272
                            $ascending
273
                        );
274
                        $this->_comparisonFunctions[]
275
                            = $node->buildComparisonFunction($ancestors);
276
                    } elseif ($resourceProperty->getKind() == ResourcePropertyKind::RESOURCE_REFERENCE) {
277
                        $node = new OrderByNode(
278
                            $orderBySubPathSegment,
279
                            $resourceProperty,
280
                            $resourceSetWrapper
0 ignored issues
show
Bug introduced by
It seems like $resourceSetWrapper can be null; however, __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...
281
                        );
282
                        // Initialize this member variable (identified by
283
                        // $resourceProperty) of parent object.
284
                        try {
285
                            $object = $resourceProperty->getInstanceType()->newInstance();
286
                            $resourceType->setPropertyValue($currentObject, $resourceProperty->getName(), $object);
287
                            $currentObject = $object;
288
                        } catch (\ReflectionException $reflectionException) {
289
                            throw ODataException::createInternalServerError(
290
                                Messages::orderByParserFailedToAccessOrInitializeProperty(
291
                                    $resourceProperty->getName(),
292
                                    $resourceType->getName()
293
                                )
294
                            );
295
                        }
296
                    } elseif ($resourceProperty->getKind() == ResourcePropertyKind::COMPLEX_TYPE) {
297
                        $node = new OrderByNode($orderBySubPathSegment, $resourceProperty, null);
0 ignored issues
show
Documentation introduced by
null is of type null, but the function expects a object<POData\Providers\...ata\ResourceSetWrapper>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
298
                        // Initialize this member variable
299
                        // (identified by $resourceProperty)of parent object.
300
                        try {
301
                            $object = $resourceProperty->getInstanceType()->newInstance();
302
                            $resourceType->setPropertyValue($currentObject, $resourceProperty->getName(), $object);
303
                            $currentObject = $object;
304
                        } catch (\ReflectionException $reflectionException) {
305
                            throw ODataException::createInternalServerError(
306
                                Messages::orderByParserFailedToAccessOrInitializeProperty(
307
                                    $resourceProperty->getName(),
308
                                    $resourceType->getName()
309
                                )
310
                            );
311
                        }
312
                    }
313
314
                    $currentNode->addNode($node);
315
                } else {
316
                    try {
317
                        // If a magic method for properties exists (eg Eloquent), dive into it directly and return value
318
                        if (method_exists($currentObject, '__get')) {
319
                            $targProperty = $resourceProperty->getName();
320
321
                            return $currentObject->$targProperty;
322
                        }
323
                        $reflectionClass = new \ReflectionClass(get_class($currentObject));
324
                        $reflectionProperty = $reflectionClass->getProperty($resourceProperty->getName());
325
                        $reflectionProperty->setAccessible(true);
326
                        $currentObject = $reflectionProperty->getValue($currentObject);
327
328
                        //$dummyProperty = new \ReflectionProperty(
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% 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...
329
                        //    $currentObject, $resourceProperty->getName()
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...
330
                        //);
331
                        //$currentObject = $dummyProperty->getValue($currentObject);
0 ignored issues
show
Unused Code Comprehensibility introduced by
64% 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...
332
                    } catch (\ReflectionException $reflectionException) {
333
                        throw ODataException::createInternalServerError(
334
                            Messages::orderByParserFailedToAccessOrInitializeProperty(
335
                                $resourceProperty->getName(),
336
                                $resourceType->getName()
337
                            )
338
                        );
339
                    }
340
341
                    if ($node instanceof OrderByLeafNode) {
342
                        //remove duplicate orderby path
343
                        unset($orderByPathSegments[$index1]);
344
                    }
345
                }
346
347
                $currentNode = $node;
348
            }
349
        }
350
    }
351
352
    /**
353
     * Traverse 'Order By Tree' and create 'OrderInfo' structure.
354
     *
355
     * @param array(array) $orderByPaths The orderby paths
0 ignored issues
show
Documentation introduced by
The doc-type array(array) could not be parsed: Expected "|" or "end of type", but got "(" at position 5. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
356
     *
357
     * @return OrderByInfo
358
     *
359
     * @throws ODataException In case parser found any tree inconsisitent
360
     *                        state, throws unexpected state error
361
     */
362
    private function _createOrderInfo($orderByPaths)
363
    {
364
        $orderByPathSegments = array();
365
        $navigationPropertiesInThePath = array();
366
        foreach ($orderByPaths as $index => $orderBySubPaths) {
367
            $currentNode = $this->_rootOrderByNode;
368
            $orderBySubPathSegments = array();
369
            foreach ($orderBySubPaths as $orderBySubPath) {
370
                $node = $currentNode->findNode($orderBySubPath);
371
                $this->_assertion(!is_null($node));
372
                $resourceProperty = $node->getResourceProperty();
373
                if ($node instanceof OrderByNode && !is_null($node->getResourceSetWrapper())) {
374
                    if (!array_key_exists($index, $navigationPropertiesInThePath)) {
375
                        $navigationPropertiesInThePath[$index] = array();
376
                    }
377
378
                    $navigationPropertiesInThePath[$index][] = $resourceProperty;
379
                }
380
381
                $orderBySubPathSegments[] = new OrderBySubPathSegment($resourceProperty);
382
                $currentNode = $node;
383
            }
384
385
            $this->_assertion($currentNode instanceof OrderByLeafNode);
386
            $orderByPathSegments[] = new OrderByPathSegment($orderBySubPathSegments, $currentNode->isAscending());
387
            unset($orderBySubPathSegments);
388
        }
389
390
        $this->_orderByInfo = new OrderByInfo($orderByPathSegments, empty($navigationPropertiesInThePath) ? null : $navigationPropertiesInThePath);
391
    }
392
393
    /**
394
     * Generates top level comparison function from sub comparison functions.
395
     */
396
    private function _generateTopLevelComparisonFunction()
397
    {
398
        $comparisonFunctionCount = count($this->_comparisonFunctions);
399
        $this->_assertion($comparisonFunctionCount > 0);
400
        if ($comparisonFunctionCount == 1) {
401
            $this->_topLevelComparisonFunction = $this->_comparisonFunctions[0];
402
        } else {
403
            $funcList = $this->_comparisonFunctions;
404
            $BigFunc = function($object1, $object2) use ($funcList) {
405
                $ret = 0;
406
                foreach ($funcList as $f) {
407
                    $ret = $f($object1, $object2);
408
                    if ($ret != 0) {
409
                        return $ret;
410
                    }
411
                }
412
                return $ret;
413
            };
414
            $this->_topLevelComparisonFunction = $BigFunc;
415
        }
416
    }
417
418
    /**
419
     * Read orderby clause.
420
     *
421
     * @param string $value orderby clause to read
422
     *
423
     * @return array(array) An array of 'OrderByPathSegment's, each of which
0 ignored issues
show
Documentation introduced by
The doc-type array(array) could not be parsed: Expected "|" or "end of type", but got "(" at position 5. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
424
     *                      is array of 'OrderBySubPathSegment's
425
     *
426
     * @throws ODataException If any syntax error found while reading the clause
427
     */
428
    private function _readOrderBy($value)
429
    {
430
        $orderByPathSegments = array();
431
        $lexer = new ExpressionLexer($value);
432
        $i = 0;
433
        while ($lexer->getCurrentToken()->Id != ExpressionTokenId::END) {
434
            $orderBySubPathSegment = $lexer->readDottedIdentifier();
435
            if (!array_key_exists($i, $orderByPathSegments)) {
436
                $orderByPathSegments[$i] = array();
437
            }
438
439
            $orderByPathSegments[$i][] = $orderBySubPathSegment;
440
            $tokenId = $lexer->getCurrentToken()->Id;
441
            if ($tokenId != ExpressionTokenId::END) {
442
                if ($tokenId != ExpressionTokenId::SLASH) {
443
                    if ($tokenId != ExpressionTokenId::COMMA) {
444
                        $lexer->validateToken(ExpressionTokenId::IDENTIFIER);
445
                        $identifier = $lexer->getCurrentToken()->Text;
446
                        if ($identifier !== 'asc' && $identifier !== 'desc') {
447
                            // force lexer to throw syntax error as we found
448
                            // unexpected identifier
449
                            $lexer->validateToken(ExpressionTokenId::DOT);
450
                        }
451
452
                        $orderByPathSegments[$i][] = '*' . $identifier;
453
                        $lexer->nextToken();
454
                        $tokenId = $lexer->getCurrentToken()->Id;
455
                        if ($tokenId != ExpressionTokenId::END) {
456
                            $lexer->validateToken(ExpressionTokenId::COMMA);
457
                            ++$i;
458
                        }
459
                    } else {
460
                        ++$i;
461
                    }
462
                }
463
464
                $lexer->nextToken();
465
            }
466
        }
467
468
        return $orderByPathSegments;
469
    }
470
471
    /**
472
     * Assert that the given condition is true, if false throw
473
     * ODataException for unexpected state.
474
     *
475
     * @param bool $condition The condition to assert
476
     *
477
     * @throws ODataException
478
     */
479
    private function _assertion($condition)
480
    {
481
        if (!$condition) {
482
            throw ODataException::createInternalServerError(Messages::orderByParserUnExpectedState());
483
        }
484
    }
485
}
486