Inline::evaluateScalar()   D
last analyzed

Complexity

Conditions 40
Paths 39

Size

Total Lines 93
Code Lines 67

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 67
dl 0
loc 93
rs 4.1666
c 0
b 0
f 0
cc 40
nc 39
nop 2

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/*
4
 * This file is part of the Symfony package.
5
 *
6
 * (c) Fabien Potencier <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Symfony\Component\Yaml;
13
14
use Symfony\Component\Yaml\Exception\DumpException;
15
use Symfony\Component\Yaml\Exception\ParseException;
16
17
/**
18
 * Inline implements a YAML parser/dumper for the YAML inline syntax.
19
 *
20
 * @author Fabien Potencier <[email protected]>
21
 */
22
class Inline
23
{
24
    const REGEX_QUOTED_STRING = '(?:"([^"\\\\]*+(?:\\\\.[^"\\\\]*+)*+)"|\'([^\']*+(?:\'\'[^\']*+)*+)\')';
25
26
    private static $exceptionOnInvalidType = false;
27
    private static $objectSupport = false;
28
    private static $objectForMap = false;
29
30
    /**
31
     * Converts a YAML string to a PHP value.
32
     *
33
     * @param string $value                  A YAML string
34
     * @param bool   $exceptionOnInvalidType True if an exception must be thrown on invalid types (a PHP resource or object), false otherwise
35
     * @param bool   $objectSupport          True if object support is enabled, false otherwise
36
     * @param bool   $objectForMap           True if maps should return a stdClass instead of array()
37
     * @param array  $references             Mapping of variable names to values
38
     *
39
     * @return mixed A PHP value
40
     *
41
     * @throws ParseException
42
     */
43
    public static function parse($value, $exceptionOnInvalidType = false, $objectSupport = false, $objectForMap = false, $references = array())
44
    {
45
        self::$exceptionOnInvalidType = $exceptionOnInvalidType;
46
        self::$objectSupport = $objectSupport;
47
        self::$objectForMap = $objectForMap;
48
49
        $value = trim((string)$value);
50
51
        if ('' === $value) {
52
            return '';
53
        }
54
55
        if (2 /* MB_OVERLOAD_STRING */ & (int) ini_get('mbstring.func_overload')) {
56
            $mbEncoding = mb_internal_encoding();
57
            mb_internal_encoding('ASCII');
58
        }
59
60
        $i = 0;
61
        switch ($value[0]) {
62
            case '[':
63
                $result = self::parseSequence($value, $i, $references);
64
                ++$i;
65
                break;
66
            case '{':
67
                $result = self::parseMapping($value, $i, $references);
68
                ++$i;
69
                break;
70
            default:
71
                $result = self::parseScalar($value, null, array('"', "'"), $i, true, $references);
72
        }
73
74
        // some comments are allowed at the end
75
        if (preg_replace('/\s+#.*$/A', '', substr($value, $i))) {
76
            throw new ParseException(sprintf('Unexpected characters near "%s".', substr($value, $i)));
77
        }
78
79
        if (isset($mbEncoding)) {
80
            mb_internal_encoding($mbEncoding);
81
        }
82
83
        return $result;
84
    }
85
86
    /**
87
     * Dumps a given PHP variable to a YAML string.
88
     *
89
     * @param mixed $value                  The PHP variable to convert
90
     * @param bool  $exceptionOnInvalidType True if an exception must be thrown on invalid types (a PHP resource or object), false otherwise
91
     * @param bool  $objectSupport          True if object support is enabled, false otherwise
92
     *
93
     * @return string The YAML string representing the PHP value
94
     *
95
     * @throws DumpException When trying to dump PHP resource
96
     */
97
    public static function dump($value, $exceptionOnInvalidType = false, $objectSupport = false)
98
    {
99
        switch (true) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing Symfony\Component\Yaml\P...:getHexRegex(), $value) of type integer to the boolean true. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
Bug Best Practice introduced by
It seems like you are loosely comparing Symfony\Component\Yaml\P...mestampRegex(), $value) of type integer to the boolean true. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
100
            case \is_resource($value):
101
                if ($exceptionOnInvalidType) {
102
                    throw new DumpException(sprintf('Unable to dump PHP resources in a YAML file ("%s").', get_resource_type($value)));
103
                }
104
105
                return 'null';
106
            case \is_object($value):
107
                if ($objectSupport) {
108
                    return '!php/object:'.serialize($value);
109
                }
110
111
                if ($exceptionOnInvalidType) {
112
                    throw new DumpException('Object support when dumping a YAML file has been disabled.');
113
                }
114
115
                return 'null';
116
            case \is_array($value):
117
                return self::dumpArray($value, $exceptionOnInvalidType, $objectSupport);
118
            case null === $value:
119
                return 'null';
120
            case true === $value:
121
                return 'true';
122
            case false === $value:
123
                return 'false';
124
            case ctype_digit($value):
125
                return \is_string($value) ? "'$value'" : (int) $value;
126
            case is_numeric($value):
127
                $locale = setlocale(LC_NUMERIC, 0);
128
                if (false !== $locale) {
129
                    setlocale(LC_NUMERIC, 'C');
130
                }
131
                if (\is_float($value)) {
132
                    $repr = (string) $value;
133
                    if (is_infinite($value)) {
134
                        $repr = str_ireplace('INF', '.Inf', $repr);
135
                    } elseif (floor($value) == $value && $repr == $value) {
136
                        // Preserve float data type since storing a whole number will result in integer value.
137
                        $repr = '!!float '.$repr;
138
                    }
139
                } else {
140
                    $repr = \is_string($value) ? "'$value'" : (string) $value;
141
                }
142
                if (false !== $locale) {
143
                    setlocale(LC_NUMERIC, $locale);
144
                }
145
146
                return $repr;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $repr also could return the type array which is incompatible with the documented return type string.
Loading history...
147
            case '' == $value:
148
                return "''";
149
            case Escaper::requiresDoubleQuoting($value):
150
                return Escaper::escapeWithDoubleQuotes($value);
151
            case Escaper::requiresSingleQuoting($value):
152
            case Parser::preg_match(self::getHexRegex(), $value):
153
            case Parser::preg_match(self::getTimestampRegex(), $value):
154
                return Escaper::escapeWithSingleQuotes($value);
155
            default:
156
                return $value;
157
        }
158
    }
159
160
    /**
161
     * Check if given array is hash or just normal indexed array.
162
     *
163
     * @internal
164
     *
165
     * @param array $value The PHP array to check
166
     *
167
     * @return bool true if value is hash array, false otherwise
168
     */
169
    public static function isHash(array $value)
170
    {
171
        $expectedKey = 0;
172
173
        foreach ($value as $key => $val) {
174
            if ($key !== $expectedKey++) {
175
                return true;
176
            }
177
        }
178
179
        return false;
180
    }
181
182
    /**
183
     * Dumps a PHP array to a YAML string.
184
     *
185
     * @param array $value                  The PHP array to dump
186
     * @param bool  $exceptionOnInvalidType True if an exception must be thrown on invalid types (a PHP resource or object), false otherwise
187
     * @param bool  $objectSupport          True if object support is enabled, false otherwise
188
     *
189
     * @return string The YAML string representing the PHP array
190
     */
191
    private static function dumpArray($value, $exceptionOnInvalidType, $objectSupport)
192
    {
193
        // array
194
        if ($value && !self::isHash($value)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $value of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
195
            $output = array();
196
            foreach ($value as $val) {
197
                $output[] = self::dump($val, $exceptionOnInvalidType, $objectSupport);
198
            }
199
200
            return sprintf('[%s]', implode(', ', $output));
201
        }
202
203
        // hash
204
        $output = array();
205
        foreach ($value as $key => $val) {
206
            $output[] = sprintf('%s: %s', self::dump($key, $exceptionOnInvalidType, $objectSupport), self::dump($val, $exceptionOnInvalidType, $objectSupport));
207
        }
208
209
        return sprintf('{ %s }', implode(', ', $output));
210
    }
211
212
    /**
213
     * Parses a YAML scalar.
214
     *
215
     * @param string   $scalar
216
     * @param string[] $delimiters
217
     * @param string[] $stringDelimiters
218
     * @param int      &$i
219
     * @param bool     $evaluate
220
     * @param array    $references
221
     *
222
     * @return string
223
     *
224
     * @throws ParseException When malformed inline YAML string is parsed
225
     *
226
     * @internal
227
     */
228
    public static function parseScalar($scalar, $delimiters = null, $stringDelimiters = array('"', "'"), &$i = 0, $evaluate = true, $references = array())
229
    {
230
        if (\in_array($scalar[$i], $stringDelimiters)) {
231
            // quoted scalar
232
            $output = self::parseQuotedScalar($scalar, $i);
233
234
            if (null !== $delimiters) {
235
                $tmp = ltrim(substr($scalar, $i), ' ');
236
                if ('' === $tmp) {
237
                    throw new ParseException(sprintf('Unexpected end of line, expected one of "%s".', implode('', $delimiters)));
238
                }
239
                if (!\in_array($tmp[0], $delimiters)) {
240
                    throw new ParseException(sprintf('Unexpected characters (%s).', substr($scalar, $i)));
241
                }
242
            }
243
        } else {
244
            // "normal" string
245
            if (!$delimiters) {
246
                $output = substr($scalar, $i);
247
                $i += \strlen($output);
248
249
                // remove comments
250
                if (Parser::preg_match('/[ \t]+#/', $output, $match, PREG_OFFSET_CAPTURE)) {
251
                    $output = substr($output, 0, $match[0][1]);
252
                }
253
            } elseif (Parser::preg_match('/^(.+?)('.implode('|', $delimiters).')/', substr($scalar, $i), $match)) {
254
                $output = $match[1];
255
                $i += \strlen($output);
256
            } else {
257
                throw new ParseException(sprintf('Malformed inline YAML string: %s.', $scalar));
258
            }
259
260
            // a non-quoted string cannot start with @ or ` (reserved) nor with a scalar indicator (| or >)
261
            if ($output && ('@' === $output[0] || '`' === $output[0] || '|' === $output[0] || '>' === $output[0])) {
262
                @trigger_error(sprintf('Not quoting the scalar "%s" starting with "%s" is deprecated since Symfony 2.8 and will throw a ParseException in 3.0.', $output, $output[0]), E_USER_DEPRECATED);
263
264
                // to be thrown in 3.0
265
                // throw new ParseException(sprintf('The reserved indicator "%s" cannot start a plain scalar; you need to quote the scalar.', $output[0]));
266
            }
267
268
            if ($evaluate) {
269
                $output = self::evaluateScalar($output, $references);
270
            }
271
        }
272
273
        return $output;
274
    }
275
276
    /**
277
     * Parses a YAML quoted scalar.
278
     *
279
     * @param string $scalar
280
     * @param int    &$i
281
     *
282
     * @return string
283
     *
284
     * @throws ParseException When malformed inline YAML string is parsed
285
     */
286
    private static function parseQuotedScalar($scalar, &$i)
287
    {
288
        if (!Parser::preg_match('/'.self::REGEX_QUOTED_STRING.'/Au', substr($scalar, $i), $match)) {
289
            throw new ParseException(sprintf('Malformed inline YAML string: %s.', substr($scalar, $i)));
290
        }
291
292
        $output = substr($match[0], 1, \strlen($match[0]) - 2);
293
294
        $unescaper = new Unescaper();
295
        if ('"' == $scalar[$i]) {
296
            $output = $unescaper->unescapeDoubleQuotedString($output);
297
        } else {
298
            $output = $unescaper->unescapeSingleQuotedString($output);
299
        }
300
301
        $i += \strlen($match[0]);
302
303
        return $output;
304
    }
305
306
    /**
307
     * Parses a YAML sequence.
308
     *
309
     * @param string $sequence
310
     * @param int    &$i
311
     * @param array  $references
312
     *
313
     * @return array
314
     *
315
     * @throws ParseException When malformed inline YAML string is parsed
316
     */
317
    private static function parseSequence($sequence, &$i = 0, $references = array())
318
    {
319
        $output = array();
320
        $len = \strlen($sequence);
321
        ++$i;
322
323
        // [foo, bar, ...]
324
        while ($i < $len) {
325
            switch ($sequence[$i]) {
326
                case '[':
327
                    // nested sequence
328
                    $output[] = self::parseSequence($sequence, $i, $references);
329
                    break;
330
                case '{':
331
                    // nested mapping
332
                    $output[] = self::parseMapping($sequence, $i, $references);
333
                    break;
334
                case ']':
335
                    return $output;
336
                case ',':
337
                case ' ':
338
                    break;
339
                default:
340
                    $isQuoted = \in_array($sequence[$i], array('"', "'"));
341
                    $value = self::parseScalar($sequence, array(',', ']'), array('"', "'"), $i, true, $references);
342
343
                    // the value can be an array if a reference has been resolved to an array var
344
                    if (!\is_array($value) && !$isQuoted && false !== strpos($value, ': ')) {
345
                        // embedded mapping?
346
                        try {
347
                            $pos = 0;
348
                            $value = self::parseMapping('{'.$value.'}', $pos, $references);
349
                        } catch (\InvalidArgumentException $e) {
350
                            // no, it's not
351
                        }
352
                    }
353
354
                    $output[] = $value;
355
356
                    --$i;
357
            }
358
359
            ++$i;
360
        }
361
362
        throw new ParseException(sprintf('Malformed inline YAML string: %s.', $sequence));
363
    }
364
365
    /**
366
     * Parses a YAML mapping.
367
     *
368
     * @param string $mapping
369
     * @param int    &$i
370
     * @param array  $references
371
     *
372
     * @return array|\stdClass
373
     *
374
     * @throws ParseException When malformed inline YAML string is parsed
375
     */
376
    private static function parseMapping($mapping, &$i = 0, $references = array())
377
    {
378
        $output = array();
379
        $len = \strlen($mapping);
380
        ++$i;
381
        $allowOverwrite = false;
382
383
        // {foo: bar, bar:foo, ...}
384
        while ($i < $len) {
385
            switch ($mapping[$i]) {
386
                case ' ':
387
                case ',':
388
                    ++$i;
389
                    continue 2;
390
                case '}':
391
                    if (self::$objectForMap) {
392
                        return (object) $output;
393
                    }
394
395
                    return $output;
396
            }
397
398
            // key
399
            $key = self::parseScalar($mapping, array(':', ' '), array('"', "'"), $i, false);
400
401
            if ('<<' === $key) {
402
                $allowOverwrite = true;
403
            }
404
405
            // value
406
            $done = false;
407
408
            while ($i < $len) {
409
                switch ($mapping[$i]) {
410
                    case '[':
411
                        // nested sequence
412
                        $value = self::parseSequence($mapping, $i, $references);
413
                        // Spec: Keys MUST be unique; first one wins.
414
                        // Parser cannot abort this mapping earlier, since lines
415
                        // are processed sequentially.
416
                        // But overwriting is allowed when a merge node is used in current block.
417
                        if ('<<' === $key) {
418
                            foreach ($value as $parsedValue) {
419
                                $output += $parsedValue;
420
                            }
421
                        } elseif ($allowOverwrite || !isset($output[$key])) {
422
                            $output[$key] = $value;
423
                        }
424
                        $done = true;
425
                        break;
426
                    case '{':
427
                        // nested mapping
428
                        $value = self::parseMapping($mapping, $i, $references);
429
                        // Spec: Keys MUST be unique; first one wins.
430
                        // Parser cannot abort this mapping earlier, since lines
431
                        // are processed sequentially.
432
                        // But overwriting is allowed when a merge node is used in current block.
433
                        if ('<<' === $key) {
434
                            $output += $value;
435
                        } elseif ($allowOverwrite || !isset($output[$key])) {
436
                            $output[$key] = $value;
437
                        }
438
                        $done = true;
439
                        break;
440
                    case ':':
441
                    case ' ':
442
                        break;
443
                    default:
444
                        $value = self::parseScalar($mapping, array(',', '}'), array('"', "'"), $i, true, $references);
445
                        // Spec: Keys MUST be unique; first one wins.
446
                        // Parser cannot abort this mapping earlier, since lines
447
                        // are processed sequentially.
448
                        // But overwriting is allowed when a merge node is used in current block.
449
                        if ('<<' === $key) {
450
                            $output += $value;
451
                        } elseif ($allowOverwrite || !isset($output[$key])) {
452
                            $output[$key] = $value;
453
                        }
454
                        $done = true;
455
                        --$i;
456
                }
457
458
                ++$i;
459
460
                if ($done) {
461
                    continue 2;
462
                }
463
            }
464
        }
465
466
        throw new ParseException(sprintf('Malformed inline YAML string: %s.', $mapping));
467
    }
468
469
    /**
470
     * Evaluates scalars and replaces magic values.
471
     *
472
     * @param string $scalar
473
     * @param array  $references
474
     *
475
     * @return mixed The evaluated YAML string
476
     *
477
     * @throws ParseException when object parsing support was disabled and the parser detected a PHP object or when a reference could not be resolved
478
     */
479
    private static function evaluateScalar($scalar, $references = array())
480
    {
481
        $scalar = trim($scalar);
482
        $scalarLower = strtolower($scalar);
483
484
        if (0 === strpos($scalar, '*')) {
485
            if (false !== $pos = strpos($scalar, '#')) {
486
                $value = substr($scalar, 1, $pos - 2);
487
            } else {
488
                $value = substr($scalar, 1);
489
            }
490
491
            // an unquoted *
492
            if (false === $value || '' === $value) {
493
                throw new ParseException('A reference must contain at least one character.');
494
            }
495
496
            if (!array_key_exists($value, $references)) {
497
                throw new ParseException(sprintf('Reference "%s" does not exist.', $value));
498
            }
499
500
            return $references[$value];
501
        }
502
503
        switch (true) {
504
            case 'null' === $scalarLower:
505
            case '' === $scalar:
506
            case '~' === $scalar:
507
                return;
508
            case 'true' === $scalarLower:
509
                return true;
510
            case 'false' === $scalarLower:
511
                return false;
512
            // Optimise for returning strings.
513
            case '+' === $scalar[0] || '-' === $scalar[0] || '.' === $scalar[0] || '!' === $scalar[0] || is_numeric($scalar[0]):
514
                switch (true) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing Symfony\Component\Yaml\P...\.[0-9]+)?$/', $scalar) of type integer to the boolean true. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
Bug Best Practice introduced by
It seems like you are loosely comparing Symfony\Component\Yaml\P...getHexRegex(), $scalar) of type integer to the boolean true. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
Bug Best Practice introduced by
It seems like you are loosely comparing Symfony\Component\Yaml\P...estampRegex(), $scalar) of type integer to the boolean true. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
515
                    case 0 === strpos($scalar, '!str'):
516
                        return (string) substr($scalar, 5);
517
                    case 0 === strpos($scalar, '! '):
518
                        return (int) self::parseScalar(substr($scalar, 2));
519
                    case 0 === strpos($scalar, '!php/object:'):
520
                        if (self::$objectSupport) {
521
                            return unserialize(substr($scalar, 12));
522
                        }
523
524
                        if (self::$exceptionOnInvalidType) {
525
                            throw new ParseException('Object support when parsing a YAML file has been disabled.');
526
                        }
527
528
                        return;
529
                    case 0 === strpos($scalar, '!!php/object:'):
530
                        if (self::$objectSupport) {
531
                            return unserialize(substr($scalar, 13));
532
                        }
533
534
                        if (self::$exceptionOnInvalidType) {
535
                            throw new ParseException('Object support when parsing a YAML file has been disabled.');
536
                        }
537
538
                        return;
539
                    case 0 === strpos($scalar, '!!float '):
540
                        return (float) substr($scalar, 8);
541
                    case ctype_digit($scalar):
542
                        $raw = $scalar;
543
                        $cast = (int) $scalar;
544
545
                        return '0' == $scalar[0] ? octdec($scalar) : (((string) $raw == (string) $cast) ? $cast : $raw);
546
                    case '-' === $scalar[0] && ctype_digit(substr($scalar, 1)):
547
                        $raw = $scalar;
548
                        $cast = (int) $scalar;
549
550
                        return '0' == $scalar[1] ? octdec($scalar) : (((string) $raw === (string) $cast) ? $cast : $raw);
551
                    case is_numeric($scalar):
552
                    case Parser::preg_match(self::getHexRegex(), $scalar):
553
                        return '0x' === $scalar[0].$scalar[1] ? hexdec($scalar) : (float) $scalar;
554
                    case '.inf' === $scalarLower:
555
                    case '.nan' === $scalarLower:
556
                        return -log(0);
557
                    case '-.inf' === $scalarLower:
558
                        return log(0);
559
                    case Parser::preg_match('/^(-|\+)?[0-9,]+(\.[0-9]+)?$/', $scalar):
560
                        return (float) str_replace(',', '', $scalar);
561
                    case Parser::preg_match(self::getTimestampRegex(), $scalar):
562
                        $timeZone = date_default_timezone_get();
563
                        date_default_timezone_set('UTC');
564
                        $time = strtotime($scalar);
565
                        date_default_timezone_set($timeZone);
566
567
                        return $time;
568
                }
569
                // no break
570
            default:
571
                return (string) $scalar;
572
        }
573
    }
574
575
    /**
576
     * Gets a regex that matches a YAML date.
577
     *
578
     * @return string The regular expression
579
     *
580
     * @see http://www.yaml.org/spec/1.2/spec.html#id2761573
581
     */
582
    private static function getTimestampRegex()
583
    {
584
        return <<<EOF
585
        ~^
586
        (?P<year>[0-9][0-9][0-9][0-9])
587
        -(?P<month>[0-9][0-9]?)
588
        -(?P<day>[0-9][0-9]?)
589
        (?:(?:[Tt]|[ \t]+)
590
        (?P<hour>[0-9][0-9]?)
591
        :(?P<minute>[0-9][0-9])
592
        :(?P<second>[0-9][0-9])
593
        (?:\.(?P<fraction>[0-9]*))?
594
        (?:[ \t]*(?P<tz>Z|(?P<tz_sign>[-+])(?P<tz_hour>[0-9][0-9]?)
595
        (?::(?P<tz_minute>[0-9][0-9]))?))?)?
596
        $~x
597
EOF;
598
    }
599
600
    /**
601
     * Gets a regex that matches a YAML number in hexadecimal notation.
602
     *
603
     * @return string
604
     */
605
    private static function getHexRegex()
606
    {
607
        return '~^0x[0-9a-f]++$~i';
608
    }
609
}
610