Completed
Push — master ( 44ef42...eaf1a8 )
by Alex
02:56
created

KeyDescriptor   F

Complexity

Total Complexity 64

Size/Duplication

Total Lines 533
Duplicated Lines 6.75 %

Coupling/Cohesion

Components 2
Dependencies 19

Importance

Changes 0
Metric Value
wmc 64
lcom 2
cbo 19
dl 36
loc 533
rs 2.4812
c 0
b 0
f 0

15 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 20 5
A parseAndVerifyRawKeyPredicate() 0 12 2
A getNamedValues() 0 4 1
A getPositionalValues() 0 4 1
A getPositionalValuesByRef() 0 4 1
A getValidatedNamedValues() 0 10 2
A areNamedValues() 0 4 1
A isEmpty() 0 5 2
A valueCount() 0 10 3
A tryParseKeysFromKeyPredicate() 0 8 1
A tryParseValuesFromSkipToken() 0 6 1
C validate() 36 88 11
D tryParseKeysFromRawKeyPredicate() 0 101 17
C getTypeAndValidateKeyValue() 0 47 12
B generateRelativeUri() 0 31 4

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like KeyDescriptor 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 KeyDescriptor, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace POData\UriProcessor\ResourcePathProcessor\SegmentParser;
4
5
use POData\Common\InvalidOperationException;
6
use POData\Common\Messages;
7
use POData\Common\ODataException;
8
use POData\Providers\Metadata\ResourceSet;
9
use POData\Providers\Metadata\ResourceType;
10
use POData\Providers\Metadata\Type\Boolean;
11
use POData\Providers\Metadata\Type\DateTime;
12
use POData\Providers\Metadata\Type\Decimal;
13
use POData\Providers\Metadata\Type\Double;
14
use POData\Providers\Metadata\Type\Guid;
15
use POData\Providers\Metadata\Type\Int32;
16
use POData\Providers\Metadata\Type\Int64;
17
use POData\Providers\Metadata\Type\IType;
18
use POData\Providers\Metadata\Type\Null1;
19
use POData\Providers\Metadata\Type\Single;
20
use POData\Providers\Metadata\Type\StringType;
21
use POData\UriProcessor\QueryProcessor\ExpressionParser\ExpressionLexer;
22
use POData\UriProcessor\QueryProcessor\ExpressionParser\ExpressionTokenId;
23
24
/**
25
 * Class KeyDescriptor.
26
 *
27
 * A type used to represent Key (identifier) for an entity (resource), This class
28
 * can parse an Astoria KeyPredicate, KeyPredicate will be in one of the following
29
 * two formats:
30
 *  1) KeyValue                                      : If the Entry has a single key
31
 *                                                     Property the predicate may
32
 *                                                     include only the value of the
33
 *                                                     key Property.
34
 *      e.g. 'ALFKI' in Customers('ALFKI')
35
 *  2) Property = KeyValue [, Property = KeyValue]*  : If the key is made up of two
36
 *                                                     or more Properties, then its
37
 *                                                     value must be stated using
38
 *                                                     name/value pairs.
39
 *      e.g. 'ALFKI' in Customers(CustomerID = 'ALFKI'),
40
 *          "OrderID=10248,ProductID=11" in Order_Details(OrderID=10248,ProductID=11)
41
 *
42
 * Entity's identifier is a collection of value for key properties. These values
43
 * can be named or positional, depending on how they were specified in the URI.
44
 *  e.g. Named values:
45
 *         Customers(CustomerID = 'ALFKI'), Order_Details(OrderID=10248,ProductID=11)
46
 *       Positional values:
47
 *         Customers('ALFKI'), Order_Details(10248, 11)
48
 * Note: Currently WCF Data Service does not support multiple 'Positional values' so
49
 *       Order_Details(10248, 11) is not valid, but this class can parse both types.
50
 * Note: This type is also used to parse and validate skiptoken value as they are
51
 *       comma separated positional values.
52
 */
53
class KeyDescriptor
0 ignored issues
show
Coding Style introduced by
Since you have declared the constructor as private, maybe you should also declare the class as final.
Loading history...
54
{
55
    /**
56
     * Holds collection of named key values
57
     * For e.g. the keypredicate Order_Details(OrderID=10248,ProductID=11) will
58
     * stored in this array as:
59
     * Array([OrderID] => Array( [0] => 10248 [1] => Object(Int32)),
60
     *       [ProductID] => Array( [0] => 11 [1] => Object(Int32)))
61
     * Note: This is mutually exclusive with $_positionalValues. These values
62
     * are not validated against entity's ResourceType, validation will happen
63
     * once validate function is called, $_validatedNamedValues will hold
64
     * validated values.
65
     *
66
     * @var array
67
     */
68
    private $namedValues = [];
69
70
    /**
71
     * Holds collection of positional key values
72
     * For e.g. the keypredicate Order_Details(10248, 11) will
73
     * stored in this array as:
74
     * Array([0] => Array( [0] => 10248 [1] => Object(Int32)),
75
     *       [1] => Array( [0] => 11 [1] => Object(Int32)))
76
     * Note: This is mutually exclusive with $_namedValues. These values are not
77
     * validated against entity's ResourceType, validation will happen once validate
78
     * function is called, $_validatedNamedValues will hold validated values.
79
     *
80
     * @var array
81
     */
82
    private $positionalValues = [];
83
84
    /**
85
     * Holds collection of positional or named values as named values. The validate
86
     * function populates this collection.
87
     *
88
     * @var array
89
     */
90
    private $validatedNamedValues = [];
91
92
    /**
93
     * Creates new instance of KeyDescriptor
94
     * Note: The arguments $namedValues and $positionalValues are mutually
95
     * exclusive. Either both or one will be empty array.
96
     *
97
     * @param array $namedValues      Collection of named key values
98
     * @param array $positionalValues Collection of positional key values
99
     */
100
    private function __construct(array $namedValues, array $positionalValues)
101
    {
102
        $namedCount = count($namedValues);
103
        $posCount = count($positionalValues);
104
        assert(0 == min($namedCount, $posCount), 'At least one of named and positional values arrays must be empty');
105
        if (0 < $namedCount) {
106
            $keys = array_keys($namedValues);
107
            for ($i = 0; $i < $namedCount; $i++) {
108
                $namedValues[$keys[$i]][0] = urldecode($namedValues[$keys[$i]][0]);
109
            }
110
        }
111
        if (0 < $posCount) {
112
            for ($i = 0; $i < $posCount; $i++) {
113
                $positionalValues[$i][0] = urldecode($positionalValues[$i][0]);
114
            }
115
        }
116
        $this->namedValues = $namedValues;
117
        $this->positionalValues = $positionalValues;
118
        $this->validatedNamedValues = [];
119
    }
120
121
    /**
122
     * @param string $keyString
123
     * @param bool $isKey
124
     * @param KeyDescriptor|null $keyDescriptor
125
     * @return bool
126
     */
127
    protected static function parseAndVerifyRawKeyPredicate($keyString, $isKey, KeyDescriptor &$keyDescriptor = null)
128
    {
129
        $result = self::tryParseKeysFromRawKeyPredicate(
130
            $keyString,
131
            $isKey,
132
            !$isKey,
133
            $keyDescriptor
134
        );
135
        assert(true === $result || false === $result, 'Result must be boolean');
136
        assert($result === isset($keyDescriptor), 'Result must match existence of keyDescriptor');
137
        return $result;
138
    }
139
140
    /**
141
     * Gets collection of named key values.
142
     *
143
     * @return array[]
144
     */
145
    public function getNamedValues()
146
    {
147
        return $this->namedValues;
148
    }
149
150
    /**
151
     * Gets collection of positional key values.
152
     *
153
     * @return array[]
154
     */
155
    public function getPositionalValues()
156
    {
157
        return $this->positionalValues;
158
    }
159
160
    /**
161
     * Gets collection of positional key values by reference.
162
     *
163
     * @return array[]
164
     */
165
    public function &getPositionalValuesByRef()
166
    {
167
        return $this->positionalValues;
168
    }
169
170
    /**
171
     * Gets validated named key values, this array will be populated
172
     * in validate function.
173
     *
174
     * @throws InvalidOperationException If this function invoked before invoking validate function
175
     *
176
     * @return array[]
177
     */
178
    public function getValidatedNamedValues()
179
    {
180
        if (empty($this->validatedNamedValues)) {
181
            throw new InvalidOperationException(
182
                Messages::keyDescriptorValidateNotCalled()
183
            );
184
        }
185
186
        return $this->validatedNamedValues;
187
    }
188
189
    /**
190
     * Checks whether the key values have name.
191
     *
192
     * @return bool
193
     */
194
    public function areNamedValues()
0 ignored issues
show
Coding Style introduced by
function areNamedValues() does not seem to conform to the naming convention (^(?:is|has|should|may|supports)).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
195
    {
196
        return !empty($this->namedValues);
197
    }
198
199
    /**
200
     * Check whether this KeyDesciption has any key values.
201
     *
202
     * @return bool
203
     */
204
    public function isEmpty()
205
    {
206
        return empty($this->namedValues)
207
             && empty($this->positionalValues);
208
    }
209
210
    /**
211
     * Gets number of values in the key.
212
     *
213
     * @return int
214
     */
215
    public function valueCount()
216
    {
217
        if ($this->isEmpty()) {
218
            return 0;
219
        } elseif (!empty($this->namedValues)) {
220
            return count($this->namedValues);
221
        }
222
223
        return count($this->positionalValues);
224
    }
225
226
    /**
227
     * Attempts to parse value(s) of resource key(s) from the given key predicate
228
     *  and creates instance of KeyDescription representing the same, Once parsing
229
     *  is done one should call validate function to validate the created
230
     *  KeyDescription.
231
     *
232
     * @param string             $keyPredicate  The predicate to parse
233
     * @param KeyDescriptor|null $keyDescriptor On return, Description of key after parsing
234
     *
235
     * @return bool True if the given values were parsed; false if there was a syntax error
236
     */
237
    public static function tryParseKeysFromKeyPredicate(
0 ignored issues
show
Coding Style introduced by
function tryParseKeysFromKeyPredicate() does not seem to conform to the naming convention (^(?:is|has|should|may|supports)).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
238
        $keyPredicate,
239
        KeyDescriptor &$keyDescriptor = null
240
    ) {
241
        $isKey = true;
242
        $keyString = $keyPredicate;
243
        return self::parseAndVerifyRawKeyPredicate($keyString, $isKey, $keyDescriptor);
244
    }
245
246
    /**
247
     * Attempt to parse comma separated values representing a skiptoken and creates
248
     * instance of KeyDescriptor representing the same.
249
     *
250
     * @param string        $skipToken      The skiptoken value to parse
251
     * @param KeyDescriptor &$keyDescriptor On return, Description of values
252
     *                                      after parsing
253
     *
254
     * @return bool True if the given values were parsed; false if there was a syntax error
255
     */
256
    public static function tryParseValuesFromSkipToken($skipToken, &$keyDescriptor)
0 ignored issues
show
Coding Style introduced by
function tryParseValuesFromSkipToken() does not seem to conform to the naming convention (^(?:is|has|should|may|supports)).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
257
    {
258
        $isKey = false;
259
        $keyString = $skipToken;
260
        return self::parseAndVerifyRawKeyPredicate($keyString, $isKey, $keyDescriptor);
261
    }
262
263
    /**
264
     * Validate this KeyDescriptor, If valid, this function populates
265
     * _validatedNamedValues array with key as keyName and value as an array of
266
     * key value and key type.
267
     *
268
     * @param string       $segmentAsString The segment in the form identifier
269
     *                                      (keyPredicate) which this descriptor
270
     *                                      represents
271
     * @param ResourceType $resourceType    The type of the identifier in the segment
272
     *
273
     * @throws ODataException If validation fails
274
     */
275
    public function validate($segmentAsString, ResourceType $resourceType)
276
    {
277
        if ($this->isEmpty()) {
278
            $this->validatedNamedValues = [];
279
280
            return;
281
        }
282
283
        $keyProperties = $resourceType->getKeyProperties();
284
        $keyPropertiesCount = count($keyProperties);
285
        if (!empty($this->namedValues)) {
286 View Code Duplication
            if (count($this->namedValues) != $keyPropertiesCount) {
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...
287
                throw ODataException::createSyntaxError(
288
                    Messages::keyDescriptorKeyCountNotMatching(
289
                        $segmentAsString,
290
                        $keyPropertiesCount,
291
                        count($this->namedValues)
292
                    )
293
                );
294
            }
295
296
            foreach ($keyProperties as $keyName => $keyResourceProperty) {
297
                if (!array_key_exists($keyName, $this->namedValues)) {
298
                    $keysAsString = null;
299
                    foreach (array_keys($keyProperties) as $key) {
300
                        $keysAsString .= $key . ', ';
301
                    }
302
303
                    $keysAsString = rtrim($keysAsString, ' ,');
304
                    throw ODataException::createSyntaxError(
305
                        Messages::keyDescriptorMissingKeys(
306
                            $segmentAsString,
307
                            $keysAsString
308
                        )
309
                    );
310
                }
311
312
                $typeProvided = $this->namedValues[$keyName][1];
313
                $expectedType = $keyResourceProperty->getInstanceType();
314
                assert($expectedType instanceof IType, get_class($expectedType));
315 View Code Duplication
                if (!$expectedType->isCompatibleWith($typeProvided)) {
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...
316
                    throw ODataException::createSyntaxError(
317
                        Messages::keyDescriptorInCompatibleKeyType(
318
                            $segmentAsString,
319
                            $keyName,
320
                            $expectedType->getFullTypeName(),
321
                            $typeProvided->getFullTypeName()
322
                        )
323
                    );
324
                }
325
326
                $this->validatedNamedValues[$keyName] = $this->namedValues[$keyName];
327
            }
328
        } else {
329 View Code Duplication
            if (count($this->positionalValues) != $keyPropertiesCount) {
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...
330
                throw ODataException::createSyntaxError(
331
                    Messages::keyDescriptorKeyCountNotMatching(
332
                        $segmentAsString,
333
                        $keyPropertiesCount,
334
                        count($this->positionalValues)
335
                    )
336
                );
337
            }
338
339
            $i = 0;
340
            foreach ($keyProperties as $keyName => $keyResourceProperty) {
341
                $typeProvided = $this->positionalValues[$i][1];
342
                $expectedType = $keyResourceProperty->getInstanceType();
343
                assert($expectedType instanceof IType, get_class($expectedType));
344
345 View Code Duplication
                if (!$expectedType->isCompatibleWith($typeProvided)) {
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...
346
                    throw ODataException::createSyntaxError(
347
                        Messages::keyDescriptorInCompatibleKeyTypeAtPosition(
348
                            $segmentAsString,
349
                            $keyResourceProperty->getName(),
350
                            $i,
351
                            $expectedType->getFullTypeName(),
352
                            $typeProvided->getFullTypeName()
353
                        )
354
                    );
355
                }
356
357
                $this->validatedNamedValues[$keyName]
358
                    = $this->positionalValues[$i];
359
                ++$i;
360
            }
361
        }
362
    }
363
364
    /**
365
     * Attempts to parse value(s) of resource key(s) from the key predicate and
366
     * creates instance of KeyDescription representing the same, Once parsing is
367
     * done, one should call validate function to validate the created KeyDescription.
368
     *
369
     * @param string        $keyPredicate     The key predicate to parse
370
     * @param bool          $allowNamedValues Set to true if parser should accept
371
     *                                        named values(Property = KeyValue),
372
     *                                        if false then parser will fail on
373
     *                                        such constructs
374
     * @param bool          $allowNull        Set to true if parser should accept
375
     *                                        null values for positional key
376
     *                                        values, if false then parser will
377
     *                                        fail on seeing null values
378
     * @param KeyDescriptor &$keyDescriptor   On return, Description of key after
379
     *                                        parsing
380
     *
381
     * @return bool True if the given values were parsed; false if there was a syntax error
382
     */
383
    private static function tryParseKeysFromRawKeyPredicate(
0 ignored issues
show
Coding Style introduced by
function tryParseKeysFromRawKeyPredicate() does not seem to conform to the naming convention (^(?:is|has|should|may|supports)).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
384
        $keyPredicate,
385
        $allowNamedValues,
386
        $allowNull,
387
        &$keyDescriptor
388
    ) {
389
        $expressionLexer = new ExpressionLexer($keyPredicate);
390
        $currentToken = $expressionLexer->getCurrentToken();
391
392
        //Check for empty predicate e.g. Customers(  )
393
        if ($currentToken->Id == ExpressionTokenId::END) {
394
            $keyDescriptor = new self([], []);
395
396
            return true;
397
        }
398
399
        $namedValues = [];
400
        $positionalValues = [];
401
402
        do {
403
            if (($currentToken->Id == ExpressionTokenId::IDENTIFIER)
404
                && $allowNamedValues
405
            ) {
406
                //named and positional values are mutually exclusive
407
                if (!empty($positionalValues)) {
408
                    return false;
409
                }
410
411
                //expecting keyName=keyValue, verify it
412
                $identifier = $currentToken->getIdentifier();
413
                $expressionLexer->nextToken();
414
                $currentToken = $expressionLexer->getCurrentToken();
415
                if ($currentToken->Id != ExpressionTokenId::EQUAL) {
416
                    return false;
417
                }
418
419
                $expressionLexer->nextToken();
420
                $currentToken = $expressionLexer->getCurrentToken();
421
                if (!$currentToken->isKeyValueToken()) {
422
                    return false;
423
                }
424
425
                if (array_key_exists($identifier, $namedValues)) {
426
                    //Duplication of KeyName not allowed
427
                    return false;
428
                }
429
430
                //Get type of keyValue and validate keyValue
431
                $outValue = $outType = null;
432
                if (!self::getTypeAndValidateKeyValue(
433
                    $currentToken->Text,
434
                    $currentToken->Id,
435
                    $outValue,
436
                    $outType
437
                )
438
                ) {
439
                    return false;
440
                }
441
442
                $namedValues[$identifier] = [$outValue, $outType];
443
            } elseif ($currentToken->isKeyValueToken()
444
                || ($currentToken->Id == ExpressionTokenId::NULL_LITERAL && $allowNull)
445
            ) {
446
                //named and positional values are mutually exclusive
447
                if (!empty($namedValues)) {
448
                    return false;
449
                }
450
451
                //Get type of keyValue and validate keyValue
452
                $outValue = $outType = null;
453
                if (!self::getTypeAndValidateKeyValue(
454
                    $currentToken->Text,
455
                    $currentToken->Id,
456
                    $outValue,
457
                    $outType
458
                )
459
                ) {
460
                    return false;
461
                }
462
463
                $positionalValues[] = [$outValue, $outType];
464
            } else {
465
                return false;
466
            }
467
468
            $expressionLexer->nextToken();
469
            $currentToken = $expressionLexer->getCurrentToken();
470
            if ($currentToken->Id == ExpressionTokenId::COMMA) {
471
                $expressionLexer->nextToken();
472
                $currentToken = $expressionLexer->getCurrentToken();
473
                //end of text and comma, Trailing comma not allowed
474
                if ($currentToken->Id == ExpressionTokenId::END) {
475
                    return false;
476
                }
477
            }
478
        } while ($currentToken->Id != ExpressionTokenId::END);
479
480
        $keyDescriptor = new self($namedValues, $positionalValues);
481
482
        return true;
483
    }
484
485
    /**
486
     * Get the type of an Astoria URI key value, validate the value against the type. If valid, this function
487
     * provides the PHP value equivalent to the Astoria URI key value.
488
     *
489
     * @param string            $value     The Astoria URI key value
490
     * @param ExpressionTokenId $tokenId   The tokenId for $value literal
491
     * @param mixed|null        &$outValue After the invocation, this parameter holds the PHP equivalent to $value,
492
     *                                     if $value is not valid then this parameter will be null
493
     * @param IType|null        &$outType  After the invocation, this parameter holds the type of $value, if $value is
494
     *                                     not a valid key value type then this parameter will be null
495
     *
496
     * @return bool True if $value is a valid type, else false
497
     */
498
    private static function getTypeAndValidateKeyValue($value, $tokenId, &$outValue, &$outType)
0 ignored issues
show
Coding Style introduced by
function getTypeAndValidateKeyValue() does not seem to conform to the naming convention (^(?:is|has|should|may|supports)).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
499
    {
500
        switch ($tokenId) {
501
            case ExpressionTokenId::BOOLEAN_LITERAL:
502
                $outType = new Boolean();
503
                break;
504
            case ExpressionTokenId::DATETIME_LITERAL:
505
                $outType = new DateTime();
506
                break;
507
            case ExpressionTokenId::GUID_LITERAL:
508
                $outType = new Guid();
509
                break;
510
            case ExpressionTokenId::STRING_LITERAL:
511
                $outType = new StringType();
512
                break;
513
            case ExpressionTokenId::INTEGER_LITERAL:
514
                $outType = new Int32();
515
                break;
516
            case ExpressionTokenId::DECIMAL_LITERAL:
517
                $outType = new Decimal();
518
                break;
519
            case ExpressionTokenId::DOUBLE_LITERAL:
520
                $outType = new Double();
521
                break;
522
            case ExpressionTokenId::INT64_LITERAL:
523
                $outType = new Int64();
524
                break;
525
            case ExpressionTokenId::SINGLE_LITERAL:
526
                $outType = new Single();
527
                break;
528
            case ExpressionTokenId::NULL_LITERAL:
529
                $outType = new Null1();
530
                break;
531
            default:
532
                $outType = null;
533
534
                return false;
535
        }
536
537
        if (!$outType->validate($value, $outValue)) {
538
            $outType = $outValue = null;
539
540
            return false;
541
        }
542
543
        return true;
544
    }
545
546
    /**
547
     * Generate relative edit url for this key descriptor and supplied resource set
548
     *
549
     * @param ResourceSet $resourceSet
550
     *
551
     * @return string
552
     * @throws \InvalidArgumentException
553
     */
554
    public function generateRelativeUri(ResourceSet $resourceSet)
555
    {
556
        $resourceType = $resourceSet->getResourceType();
557
        $keys = $resourceType->getKeyProperties();
558
559
        $namedKeys = $this->getNamedValues();
560
        assert(0 !== count($keys), 'count($keys) == 0');
561
        if (count($keys) !== count($namedKeys)) {
562
            $msg = 'Mismatch between supplied key predicates and number of keys defined on resource set';
563
            throw new \InvalidArgumentException($msg);
564
        }
565
        $editUrl = $resourceSet->getName() . '(';
566
        $comma = null;
567
        foreach ($keys as $keyName => $resourceProperty) {
568
            if (!array_key_exists($keyName, $namedKeys)) {
569
                $msg = 'Key predicate '.$keyName.' not present in named values';
570
                throw new \InvalidArgumentException($msg);
571
            }
572
            $keyType = $resourceProperty->getInstanceType();
573
            assert($keyType instanceof IType, '$keyType not instanceof IType');
574
            $keyValue = $namedKeys[$keyName][0];
575
            $keyValue = $keyType->convertToOData($keyValue);
576
577
            $editUrl .= $comma . $keyName . '=' . $keyValue;
578
            $comma = ',';
579
        }
580
581
        $editUrl .= ')';
582
583
        return $editUrl;
584
    }
585
}
586