Passed
Pull Request — master (#120)
by Alex
03:44
created

KeyDescriptor::getValidatedNamedValues()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 10
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 5
nc 2
nop 0
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\ResourceType;
9
use POData\Providers\Metadata\Type\Boolean;
10
use POData\Providers\Metadata\Type\DateTime;
11
use POData\Providers\Metadata\Type\Decimal;
12
use POData\Providers\Metadata\Type\Double;
13
use POData\Providers\Metadata\Type\Guid;
14
use POData\Providers\Metadata\Type\Int32;
15
use POData\Providers\Metadata\Type\Int64;
16
use POData\Providers\Metadata\Type\IType;
17
use POData\Providers\Metadata\Type\Null1;
18
use POData\Providers\Metadata\Type\Single;
19
use POData\Providers\Metadata\Type\StringType;
20
use POData\UriProcessor\QueryProcessor\ExpressionParser\ExpressionLexer;
21
use POData\UriProcessor\QueryProcessor\ExpressionParser\ExpressionTokenId;
22
23
/**
24
 * Class KeyDescriptor.
25
 *
26
 * A type used to represent Key (identifier) for an entity (resource), This class
27
 * can parse an Astoria KeyPredicate, KeyPredicate will be in one of the following
28
 * two formats:
29
 *  1) KeyValue                                      : If the Entry has a single key
30
 *                                                     Property the predicate may
31
 *                                                     include only the value of the
32
 *                                                     key Property.
33
 *      e.g. 'ALFKI' in Customers('ALFKI')
34
 *  2) Property = KeyValue [, Property = KeyValue]*  : If the key is made up of two
35
 *                                                     or more Properties, then its
36
 *                                                     value must be stated using
37
 *                                                     name/value pairs.
38
 *      e.g. 'ALFKI' in Customers(CustomerID = 'ALFKI'),
39
 *          "OrderID=10248,ProductID=11" in Order_Details(OrderID=10248,ProductID=11)
40
 *
41
 * Entity's identifier is a collection of value for key properties. These values
42
 * can be named or positional, depending on how they were specified in the URI.
43
 *  e.g. Named values:
44
 *         Customers(CustomerID = 'ALFKI'), Order_Details(OrderID=10248,ProductID=11)
45
 *       Positional values:
46
 *         Customers('ALFKI'), Order_Details(10248, 11)
47
 * Note: Currently WCF Data Service does not support multiple 'Positional values' so
48
 *       Order_Details(10248, 11) is not valid, but this class can parse both types.
49
 * Note: This type is also used to parse and validate skiptoken value as they are
50
 *       comma separated positional values.
51
 */
52
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...
53
{
54
    /**
55
     * Holds collection of named key values
56
     * For e.g. the keypredicate Order_Details(OrderID=10248,ProductID=11) will
57
     * stored in this array as:
58
     * Array([OrderID] => Array( [0] => 10248 [1] => Object(Int32)),
59
     *       [ProductID] => Array( [0] => 11 [1] => Object(Int32)))
60
     * Note: This is mutually exclusive with $_positionalValues. These values
61
     * are not validated aganist entity's ResourceType, validation will happen
62
     * once validate function is called, $_validatedNamedValues will hold
63
     * validated values.
64
     *
65
     * @var array
66
     */
67
    private $namedValues = [];
68
69
    /**
70
     * Holds collection of positional key values
71
     * For e.g. the keypredicate Order_Details(10248, 11) will
72
     * stored in this array as:
73
     * Array([0] => Array( [0] => 10248 [1] => Object(Int32)),
74
     *       [1] => Array( [0] => 11 [1] => Object(Int32)))
75
     * Note: This is mutually exclusive with $_namedValues. These values are not
76
     * validated aganist entity's ResourceType, validation will happen once validate
77
     * function is called, $_validatedNamedValues will hold validated values.
78
     *
79
     * @var array
80
     */
81
    private $positionalValues = [];
82
83
    /**
84
     * Holds collection of positional or named values as named values. The validate
85
     * function populates this collection.
86
     *
87
     * @var array
88
     */
89
    private $validatedNamedValues = [];
90
91
    /**
92
     * Creates new instance of KeyDescriptor
93
     * Note: The arguments $namedValues and $positionalValues are mutually
94
     * exclusive. Either both or one will be empty array.
95
     *
96
     * @param array $namedValues      Collection of named key values
97
     * @param array $positionalValues Collection of positional key values
98
     */
99
    private function __construct(array $namedValues, array $positionalValues)
100
    {
101
        $this->namedValues = $namedValues;
102
        $this->positionalValues = $positionalValues;
103
        $this->validatedNamedValues = [];
104
    }
105
106
    /**
107
     * Gets collection of named key values.
108
     *
109
     * @return array[]
110
     */
111
    public function getNamedValues()
112
    {
113
        return $this->namedValues;
114
    }
115
116
    /**
117
     * Gets collection of positional key values.
118
     *
119
     * @return array[]
120
     */
121
    public function getPositionalValues()
122
    {
123
        return $this->positionalValues;
124
    }
125
126
    /**
127
     * Gets collection of positional key values by reference.
128
     *
129
     * @return array[]
130
     */
131
    public function &getPositionalValuesByRef()
132
    {
133
        return $this->positionalValues;
134
    }
135
136
    /**
137
     * Gets validated named key values, this array will be populated
138
     * in validate function.
139
     *
140
     * @throws InvalidOperationException    If this function invoked before invoking validate function
141
     *
142
     * @return array[]
143
     */
144
    public function getValidatedNamedValues()
145
    {
146
        if (empty($this->validatedNamedValues)) {
147
            throw new InvalidOperationException(
148
                Messages::keyDescriptorValidateNotCalled()
149
            );
150
        }
151
152
        return $this->validatedNamedValues;
153
    }
154
155
    /**
156
     * Checks whether the key values have name.
157
     *
158
     * @return bool
159
     */
160
    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...
161
    {
162
        return !empty($this->namedValues);
163
    }
164
165
    /**
166
     * Check whether this KeyDesciption has any key values.
167
     *
168
     * @return bool
169
     */
170
    public function isEmpty()
171
    {
172
        return empty($this->namedValues)
173
             && empty($this->positionalValues);
174
    }
175
176
    /**
177
     * Gets number of values in the key.
178
     *
179
     * @return int
180
     */
181
    public function valueCount()
182
    {
183
        if ($this->isEmpty()) {
184
            return 0;
185
        } elseif (!empty($this->namedValues)) {
186
            return count($this->namedValues);
187
        }
188
189
        return count($this->positionalValues);
190
    }
191
192
    /**
193
     * Attempts to parse value(s) of resource key(s) from the given key predicate
194
     *  and creates instance of KeyDescription representing the same, Once parsing
195
     *  is done one should call validate function to validate the created
196
     *  KeyDescription.
197
     *
198
     * @param string $keyPredicate The predicate to parse
199
     * @param KeyDescriptor KeyDescriptor On return, Description of key after
0 ignored issues
show
Documentation introduced by
Should the type for parameter $keyDescriptor not be null|KeyDescriptor?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
200
     *                                      parsing
201
     *
202
     * @return bool True if the given values were parsed; false if there was a syntax error
203
     */
204
    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...
205
        $keyPredicate,
206
        KeyDescriptor &$keyDescriptor = null
207
    ) {
208
        return self::tryParseKeysFromRawKeyPredicate(
209
            $keyPredicate,
210
            true,
211
            false,
212
            $keyDescriptor
213
        );
214
    }
215
216
    /**
217
     * Attempt to parse comma seperated values representing a skiptoken and creates
218
     * instance of KeyDescriptor representing the same.
219
     *
220
     * @param string        $skipToken      The skiptoken value to parse
221
     * @param KeyDescriptor &$keyDescriptor On return, Description of values
222
     *                                      after parsing
223
     *
224
     * @return bool True if the given values were parsed; false if there was a syntax error
225
     */
226
    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...
227
    {
228
        return self::tryParseKeysFromRawKeyPredicate(
229
            $skipToken,
230
            false,
231
            true,
232
            $keyDescriptor
233
        );
234
    }
235
236
    /**
237
     * Validate this KeyDescriptor, If valid, this function populates
238
     * _validatedNamedValues array with key as keyName and value as an array of
239
     * key value and key type.
240
     *
241
     * @param string       $segmentAsString The segment in the form identifier
242
     *                                      (keyPredicate) which this descriptor
243
     *                                      represents
244
     * @param ResourceType $resourceType    The type of the identifier in the segment
245
     *
246
     * @throws ODataException If validation fails
247
     */
248
    public function validate($segmentAsString, ResourceType $resourceType)
249
    {
250
        if ($this->isEmpty()) {
251
            $this->validatedNamedValues = [];
252
253
            return;
254
        }
255
256
        $keyProperties = $resourceType->getKeyProperties();
257
        $keyPropertiesCount = count($keyProperties);
258
        if (!empty($this->namedValues)) {
259 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...
260
                throw ODataException::createSyntaxError(
261
                    Messages::keyDescriptorKeyCountNotMatching(
262
                        $segmentAsString,
263
                        $keyPropertiesCount,
264
                        count($this->namedValues)
265
                    )
266
                );
267
            }
268
269
            foreach ($keyProperties as $keyName => $keyResourceProperty) {
270
                if (!array_key_exists($keyName, $this->namedValues)) {
271
                    $keysAsString = null;
272
                    foreach (array_keys($keyProperties) as $key) {
273
                        $keysAsString .= $key . ', ';
274
                    }
275
276
                    $keysAsString = rtrim($keysAsString, ' ,');
277
                    throw ODataException::createSyntaxError(
278
                        Messages::keyDescriptorMissingKeys(
279
                            $segmentAsString,
280
                            $keysAsString
281
                        )
282
                    );
283
                }
284
285
                $typeProvided = $this->namedValues[$keyName][1];
286
                $expectedType = $keyResourceProperty->getInstanceType();
287 View Code Duplication
                if (!$expectedType->isCompatibleWith($typeProvided)) {
0 ignored issues
show
Bug introduced by
The method isCompatibleWith does only exist in POData\Providers\Metadata\Type\IType, but not in ReflectionClass.

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...
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...
288
                    throw ODataException::createSyntaxError(
289
                        Messages::keyDescriptorInCompatibleKeyType(
290
                            $segmentAsString,
291
                            $keyName,
292
                            $expectedType->getFullTypeName(),
0 ignored issues
show
Bug introduced by
The method getFullTypeName does only exist in POData\Providers\Metadata\Type\IType, but not in ReflectionClass.

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...
293
                            $typeProvided->getFullTypeName()
294
                        )
295
                    );
296
                }
297
298
                $this->validatedNamedValues[$keyName] = $this->namedValues[$keyName];
299
            }
300
        } else {
301 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...
302
                throw ODataException::createSyntaxError(
303
                    Messages::keyDescriptorKeyCountNotMatching(
304
                        $segmentAsString,
305
                        $keyPropertiesCount,
306
                        count($this->positionalValues)
307
                    )
308
                );
309
            }
310
311
            $i = 0;
312
            foreach ($keyProperties as $keyName => $keyResourceProperty) {
313
                $typeProvided = $this->positionalValues[$i][1];
314
                $expectedType = $keyResourceProperty->getInstanceType();
315
316 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...
317
                    throw ODataException::createSyntaxError(
318
                        Messages::keyDescriptorInCompatibleKeyTypeAtPosition(
319
                            $segmentAsString,
320
                            $keyResourceProperty->getName(),
321
                            $i,
322
                            $expectedType->getFullTypeName(),
323
                            $typeProvided->getFullTypeName()
324
                        )
325
                    );
326
                }
327
328
                $this->validatedNamedValues[$keyName]
329
                    = $this->positionalValues[$i];
330
                ++$i;
331
            }
332
        }
333
    }
334
335
    /**
336
     * Attempts to parse value(s) of resource key(s) from the key predicate and
337
     * creates instance of KeyDescription representing the same, Once parsing is
338
     * done, one should call validate function to validate the created KeyDescription.
339
     *
340
     * @param string        $keyPredicate     The key predicate to parse
341
     * @param bool          $allowNamedValues Set to true if parser should accept
342
     *                                        named values(Property = KeyValue),
343
     *                                        if false then parser will fail on
344
     *                                        such constructs
345
     * @param bool          $allowNull        Set to true if parser should accept
346
     *                                        null values for positional key
347
     *                                        values, if false then parser will
348
     *                                        fail on seeing null values
349
     * @param KeyDescriptor &$keyDescriptor   On return, Description of key after
350
     *                                        parsing
351
     *
352
     * @return bool True if the given values were parsed; false if there was a syntax error
353
     */
354
    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...
355
        $keyPredicate,
356
        $allowNamedValues,
357
        $allowNull,
358
        &$keyDescriptor
359
    ) {
360
        $expressionLexer = new ExpressionLexer($keyPredicate);
361
        $currentToken = $expressionLexer->getCurrentToken();
362
363
        //Check for empty predicate e.g. Customers(  )
364
        if ($currentToken->Id == ExpressionTokenId::END) {
365
            $keyDescriptor = new self([], []);
366
367
            return true;
368
        }
369
370
        $namedValues = [];
371
        $positionalValues = [];
372
373
        do {
374
            if (($currentToken->Id == ExpressionTokenId::IDENTIFIER)
375
                && $allowNamedValues
376
            ) {
377
                //named and positional values are mutually exclusive
378
                if (!empty($positionalValues)) {
379
                    return false;
380
                }
381
382
                //expecting keyName=keyValue, verify it
383
                $identifier = $currentToken->getIdentifier();
384
                $expressionLexer->nextToken();
385
                $currentToken = $expressionLexer->getCurrentToken();
386
                if ($currentToken->Id != ExpressionTokenId::EQUAL) {
387
                    return false;
388
                }
389
390
                $expressionLexer->nextToken();
391
                $currentToken = $expressionLexer->getCurrentToken();
392
                $value = null;
0 ignored issues
show
Unused Code introduced by
$value 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...
393
                if (!$currentToken->isKeyValueToken()) {
394
                    return false;
395
                }
396
397
                if (array_key_exists($identifier, $namedValues)) {
398
                    //Duplication of KeyName not allowed
399
                    return false;
400
                }
401
402
                //Get type of keyValue and validate keyValue
403
                $ouValue = $outType = null;
0 ignored issues
show
Unused Code introduced by
$ouValue 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...
404
                if (!self::getTypeAndValidateKeyValue(
405
                    $currentToken->Text,
406
                    $currentToken->Id,
407
                    $outValue,
408
                    $outType
409
                )
410
                ) {
411
                    return false;
412
                }
413
414
                $namedValues[$identifier] = [$outValue, $outType];
415
            } elseif ($currentToken->isKeyValueToken()
416
                || ($currentToken->Id == ExpressionTokenId::NULL_LITERAL && $allowNull)
417
            ) {
418
                //named and positional values are mutually exclusive
419
                if (!empty($namedValues)) {
420
                    return false;
421
                }
422
423
                //Get type of keyValue and validate keyValue
424
                $ouValue = $outType = null;
0 ignored issues
show
Unused Code introduced by
$ouValue 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...
425
                if (!self::getTypeAndValidateKeyValue(
426
                    $currentToken->Text,
427
                    $currentToken->Id,
428
                    $outValue,
429
                    $outType
430
                )
431
                ) {
432
                    return false;
433
                }
434
435
                $positionalValues[] = [$outValue, $outType];
436
            } else {
437
                return false;
438
            }
439
440
            $expressionLexer->nextToken();
441
            $currentToken = $expressionLexer->getCurrentToken();
442
            if ($currentToken->Id == ExpressionTokenId::COMMA) {
443
                $expressionLexer->nextToken();
444
                $currentToken = $expressionLexer->getCurrentToken();
445
                //end of text and comma, Trailing comma not allowed
446
                if ($currentToken->Id == ExpressionTokenId::END) {
447
                    return false;
448
                }
449
            }
450
        } while ($currentToken->Id != ExpressionTokenId::END);
451
452
        $keyDescriptor = new self($namedValues, $positionalValues);
453
454
        return true;
455
    }
456
457
    /**
458
     * Get the type of an Astoria URI key value, validate the value against the type. If valid, this function
459
     * provides the PHP value equivalent to the Astoria URI key value.
460
     *
461
     * @param string            $value     The Astoria URI key value
462
     * @param ExpressionTokenId $tokenId   The tokenId for $value literal
463
     * @param mixed|null        &$outValue After the invocation, this parameter holds the PHP equivalent to $value,
464
     *                                     if $value is not valid then this parameter will be null
465
     * @param IType|null        &$outType  After the invocation, this parameter holds the type of $value, if $value is
466
     *                                     not a valid key value type then this parameter will be null
467
     *
468
     * @return bool True if $value is a valid type, else false
469
     */
470
    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...
471
    {
472
        switch ($tokenId) {
473
            case ExpressionTokenId::BOOLEAN_LITERAL:
474
                $outType = new Boolean();
475
                break;
476
            case ExpressionTokenId::DATETIME_LITERAL:
477
                $outType = new DateTime();
478
                break;
479
            case ExpressionTokenId::GUID_LITERAL:
480
                $outType = new Guid();
481
                break;
482
            case ExpressionTokenId::STRING_LITERAL:
483
                $outType = new StringType();
484
                break;
485
            case ExpressionTokenId::INTEGER_LITERAL:
486
                $outType = new Int32();
487
                break;
488
            case ExpressionTokenId::DECIMAL_LITERAL:
489
                $outType = new Decimal();
490
                break;
491
            case ExpressionTokenId::DOUBLE_LITERAL:
492
                $outType = new Double();
493
                break;
494
            case ExpressionTokenId::INT64_LITERAL:
495
                $outType = new Int64();
496
                break;
497
            case ExpressionTokenId::SINGLE_LITERAL:
498
                $outType = new Single();
499
                break;
500
            case ExpressionTokenId::NULL_LITERAL:
501
                $outType = new Null1();
502
                break;
503
            default:
504
                $outType = null;
505
506
                return false;
507
        }
508
509
        if (!$outType->validate($value, $outValue)) {
510
            $outType = $outValue = null;
511
512
            return false;
513
        }
514
515
        return true;
516
    }
517
}
518