Completed
Push — master ( ac6391...9b00f4 )
by Michael
12:56
created

Inline::getHexRegex()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 4
rs 10
c 1
b 0
f 0
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\ParseException;
15
use Symfony\Component\Yaml\Exception\DumpException;
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 array.
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 array A PHP array representing the YAML string
0 ignored issues
show
Documentation introduced by
Should the return type not be string|array?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
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($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 array
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\...esDoubleQuoting($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 preg_match(self::getTimestampRegex(), $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 preg_match(self::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...
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;
147
            case '' == $value:
148
                return "''";
149
            case Escaper::requiresDoubleQuoting($value):
0 ignored issues
show
Bug introduced by
It seems like $value defined by parameter $value on line 97 can also be of type boolean or resource; however, Symfony\Component\Yaml\E...requiresDoubleQuoting() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
150
                return Escaper::escapeWithDoubleQuotes($value);
0 ignored issues
show
Bug introduced by
It seems like $value defined by parameter $value on line 97 can also be of type boolean or resource; however, Symfony\Component\Yaml\E...scapeWithDoubleQuotes() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
151
            case Escaper::requiresSingleQuoting($value):
0 ignored issues
show
Bug introduced by
It seems like $value defined by parameter $value on line 97 can also be of type boolean or resource; however, Symfony\Component\Yaml\E...requiresSingleQuoting() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
152
            case preg_match(self::getHexRegex(), $value):
153
            case preg_match(self::getTimestampRegex(), $value):
154
                return Escaper::escapeWithSingleQuotes($value);
0 ignored issues
show
Bug introduced by
It seems like $value defined by parameter $value on line 97 can also be of type boolean or resource; however, Symfony\Component\Yaml\E...scapeWithSingleQuotes() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
155
            default:
156
                return $value;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $value; (string|boolean|resource) is incompatible with the return type documented by Symfony\Component\Yaml\Inline::dump of type string.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
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 scalar to a YAML string.
214
     *
215
     * @param string $scalar
216
     * @param string $delimiters
217
     * @param array  $stringDelimiters
218
     * @param int    &$i
219
     * @param bool   $evaluate
220
     * @param array  $references
221
     *
222
     * @return string A YAML 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 (!in_array($tmp[0], $delimiters)) {
237
                    throw new ParseException(sprintf('Unexpected characters (%s).', substr($scalar, $i)));
238
                }
239
            }
240
        } else {
241
            // "normal" string
242
            if (!$delimiters) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $delimiters of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
243
                $output = substr($scalar, $i);
244
                $i += strlen($output);
245
246
                // remove comments
247
                if (preg_match('/[ \t]+#/', $output, $match, PREG_OFFSET_CAPTURE)) {
248
                    $output = substr($output, 0, $match[0][1]);
249
                }
250
            } elseif (preg_match('/^(.+?)('.implode('|', $delimiters).')/', substr($scalar, $i), $match)) {
251
                $output = $match[1];
252
                $i += strlen($output);
253
            } else {
254
                throw new ParseException(sprintf('Malformed inline YAML string (%s).', $scalar));
255
            }
256
257
            // a non-quoted string cannot start with @ or ` (reserved) nor with a scalar indicator (| or >)
258
            if ($output && ('@' === $output[0] || '`' === $output[0] || '|' === $output[0] || '>' === $output[0])) {
259
                @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);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
260
261
                // to be thrown in 3.0
262
                // throw new ParseException(sprintf('The reserved indicator "%s" cannot start a plain scalar; you need to quote the scalar.', $output[0]));
263
            }
264
265
            if ($evaluate) {
266
                $output = self::evaluateScalar($output, $references);
267
            }
268
        }
269
270
        return $output;
271
    }
272
273
    /**
274
     * Parses a quoted scalar to YAML.
275
     *
276
     * @param string $scalar
277
     * @param int    &$i
278
     *
279
     * @return string A YAML string
280
     *
281
     * @throws ParseException When malformed inline YAML string is parsed
282
     */
283
    private static function parseQuotedScalar($scalar, &$i)
284
    {
285
        if (!preg_match('/'.self::REGEX_QUOTED_STRING.'/Au', substr($scalar, $i), $match)) {
286
            throw new ParseException(sprintf('Malformed inline YAML string (%s).', substr($scalar, $i)));
287
        }
288
289
        $output = substr($match[0], 1, strlen($match[0]) - 2);
290
291
        $unescaper = new Unescaper();
292
        if ('"' == $scalar[$i]) {
293
            $output = $unescaper->unescapeDoubleQuotedString($output);
294
        } else {
295
            $output = $unescaper->unescapeSingleQuotedString($output);
296
        }
297
298
        $i += strlen($match[0]);
299
300
        return $output;
301
    }
302
303
    /**
304
     * Parses a sequence to a YAML string.
305
     *
306
     * @param string $sequence
307
     * @param int    &$i
308
     * @param array  $references
309
     *
310
     * @return string A YAML string
0 ignored issues
show
Documentation introduced by
Should the return type not be array?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
311
     *
312
     * @throws ParseException When malformed inline YAML string is parsed
313
     */
314
    private static function parseSequence($sequence, &$i = 0, $references = array())
315
    {
316
        $output = array();
317
        $len = strlen($sequence);
318
        ++$i;
319
320
        // [foo, bar, ...]
321
        while ($i < $len) {
322
            switch ($sequence[$i]) {
323
                case '[':
324
                    // nested sequence
325
                    $output[] = self::parseSequence($sequence, $i, $references);
326
                    break;
327
                case '{':
328
                    // nested mapping
329
                    $output[] = self::parseMapping($sequence, $i, $references);
330
                    break;
331
                case ']':
332
                    return $output;
333
                case ',':
334
                case ' ':
335
                    break;
336
                default:
337
                    $isQuoted = in_array($sequence[$i], array('"', "'"));
338
                    $value = self::parseScalar($sequence, array(',', ']'), array('"', "'"), $i, true, $references);
339
340
                    // the value can be an array if a reference has been resolved to an array var
341
                    if (!is_array($value) && !$isQuoted && false !== strpos($value, ': ')) {
342
                        // embedded mapping?
343
                        try {
344
                            $pos = 0;
345
                            $value = self::parseMapping('{'.$value.'}', $pos, $references);
346
                        } catch (\InvalidArgumentException $e) {
347
                            // no, it's not
348
                        }
349
                    }
350
351
                    $output[] = $value;
352
353
                    --$i;
354
            }
355
356
            ++$i;
357
        }
358
359
        throw new ParseException(sprintf('Malformed inline YAML string %s', $sequence));
360
    }
361
362
    /**
363
     * Parses a mapping to a YAML string.
364
     *
365
     * @param string $mapping
366
     * @param int    &$i
367
     * @param array  $references
368
     *
369
     * @return string A YAML string
370
     *
371
     * @throws ParseException When malformed inline YAML string is parsed
372
     */
373
    private static function parseMapping($mapping, &$i = 0, $references = array())
374
    {
375
        $output = array();
376
        $len = strlen($mapping);
377
        ++$i;
378
379
        // {foo: bar, bar:foo, ...}
380
        while ($i < $len) {
381
            switch ($mapping[$i]) {
382
                case ' ':
383
                case ',':
384
                    ++$i;
385
                    continue 2;
386
                case '}':
387
                    if (self::$objectForMap) {
388
                        return (object) $output;
389
                    }
390
391
                    return $output;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $output; (array) is incompatible with the return type documented by Symfony\Component\Yaml\Inline::parseMapping of type string.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
392
            }
393
394
            // key
395
            $key = self::parseScalar($mapping, array(':', ' '), array('"', "'"), $i, false);
396
397
            // value
398
            $done = false;
399
400
            while ($i < $len) {
401
                switch ($mapping[$i]) {
402 View Code Duplication
                    case '[':
403
                        // nested sequence
404
                        $value = self::parseSequence($mapping, $i, $references);
405
                        // Spec: Keys MUST be unique; first one wins.
406
                        // Parser cannot abort this mapping earlier, since lines
407
                        // are processed sequentially.
408
                        if (!isset($output[$key])) {
409
                            $output[$key] = $value;
410
                        }
411
                        $done = true;
412
                        break;
413 View Code Duplication
                    case '{':
414
                        // nested mapping
415
                        $value = self::parseMapping($mapping, $i, $references);
416
                        // Spec: Keys MUST be unique; first one wins.
417
                        // Parser cannot abort this mapping earlier, since lines
418
                        // are processed sequentially.
419
                        if (!isset($output[$key])) {
420
                            $output[$key] = $value;
421
                        }
422
                        $done = true;
423
                        break;
424
                    case ':':
425
                    case ' ':
426
                        break;
427
                    default:
428
                        $value = self::parseScalar($mapping, array(',', '}'), array('"', "'"), $i, true, $references);
429
                        // Spec: Keys MUST be unique; first one wins.
430
                        // Parser cannot abort this mapping earlier, since lines
431
                        // are processed sequentially.
432
                        if (!isset($output[$key])) {
433
                            $output[$key] = $value;
434
                        }
435
                        $done = true;
436
                        --$i;
437
                }
438
439
                ++$i;
440
441
                if ($done) {
442
                    continue 2;
443
                }
444
            }
445
        }
446
447
        throw new ParseException(sprintf('Malformed inline YAML string %s', $mapping));
448
    }
449
450
    /**
451
     * Evaluates scalars and replaces magic values.
452
     *
453
     * @param string $scalar
454
     * @param array  $references
455
     *
456
     * @return string A YAML string
457
     *
458
     * @throws ParseException when object parsing support was disabled and the parser detected a PHP object or when a reference could not be resolved
459
     */
460
    private static function evaluateScalar($scalar, $references = array())
461
    {
462
        $scalar = trim($scalar);
463
        $scalarLower = strtolower($scalar);
464
465
        if (0 === strpos($scalar, '*')) {
466 View Code Duplication
            if (false !== $pos = strpos($scalar, '#')) {
467
                $value = substr($scalar, 1, $pos - 2);
468
            } else {
469
                $value = substr($scalar, 1);
470
            }
471
472
            // an unquoted *
473
            if (false === $value || '' === $value) {
474
                throw new ParseException('A reference must contain at least one character.');
475
            }
476
477
            if (!array_key_exists($value, $references)) {
478
                throw new ParseException(sprintf('Reference "%s" does not exist.', $value));
479
            }
480
481
            return $references[$value];
482
        }
483
484
        switch (true) {
485
            case 'null' === $scalarLower:
486
            case '' === $scalar:
487
            case '~' === $scalar:
488
                return;
489
            case 'true' === $scalarLower:
490
                return true;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return true; (boolean) is incompatible with the return type documented by Symfony\Component\Yaml\Inline::evaluateScalar of type string.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
491
            case 'false' === $scalarLower:
492
                return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by Symfony\Component\Yaml\Inline::evaluateScalar of type string.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
493
            // Optimise for returning strings.
494
            case $scalar[0] === '+' || $scalar[0] === '-' || $scalar[0] === '.' || $scalar[0] === '!' || is_numeric($scalar[0]):
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
495
                switch (true) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing preg_match('/^(-|\\+)?[0...\.[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 preg_match(self::getTimestampRegex(), $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 preg_match(self::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...
496
                    case 0 === strpos($scalar, '!str'):
497
                        return (string) substr($scalar, 5);
498
                    case 0 === strpos($scalar, '! '):
499
                        return (int) self::parseScalar(substr($scalar, 2));
500 View Code Duplication
                    case 0 === strpos($scalar, '!php/object:'):
501
                        if (self::$objectSupport) {
502
                            return unserialize(substr($scalar, 12));
503
                        }
504
505
                        if (self::$exceptionOnInvalidType) {
506
                            throw new ParseException('Object support when parsing a YAML file has been disabled.');
507
                        }
508
509
                        return;
510 View Code Duplication
                    case 0 === strpos($scalar, '!!php/object:'):
511
                        if (self::$objectSupport) {
512
                            return unserialize(substr($scalar, 13));
513
                        }
514
515
                        if (self::$exceptionOnInvalidType) {
516
                            throw new ParseException('Object support when parsing a YAML file has been disabled.');
517
                        }
518
519
                        return;
520
                    case 0 === strpos($scalar, '!!float '):
521
                        return (float) substr($scalar, 8);
522
                    case ctype_digit($scalar):
523
                        $raw = $scalar;
524
                        $cast = (int) $scalar;
525
526
                        return '0' == $scalar[0] ? octdec($scalar) : (((string) $raw == (string) $cast) ? $cast : $raw);
527
                    case '-' === $scalar[0] && ctype_digit(substr($scalar, 1)):
528
                        $raw = $scalar;
529
                        $cast = (int) $scalar;
530
531
                        return '0' == $scalar[1] ? octdec($scalar) : (((string) $raw === (string) $cast) ? $cast : $raw);
532
                    case is_numeric($scalar):
533
                    case preg_match(self::getHexRegex(), $scalar):
534
                        return '0x' === $scalar[0].$scalar[1] ? hexdec($scalar) : (float) $scalar;
535
                    case '.inf' === $scalarLower:
536
                    case '.nan' === $scalarLower:
537
                        return -log(0);
538
                    case '-.inf' === $scalarLower:
539
                        return log(0);
540
                    case preg_match('/^(-|\+)?[0-9,]+(\.[0-9]+)?$/', $scalar):
541
                        return (float) str_replace(',', '', $scalar);
542
                    case preg_match(self::getTimestampRegex(), $scalar):
543
                        $timeZone = date_default_timezone_get();
544
                        date_default_timezone_set('UTC');
545
                        $time = strtotime($scalar);
546
                        date_default_timezone_set($timeZone);
547
548
                        return $time;
549
                }
550
            default:
551
                return (string) $scalar;
552
        }
553
    }
554
555
    /**
556
     * Gets a regex that matches a YAML date.
557
     *
558
     * @return string The regular expression
559
     *
560
     * @see http://www.yaml.org/spec/1.2/spec.html#id2761573
561
     */
562
    private static function getTimestampRegex()
563
    {
564
        return <<<EOF
565
        ~^
566
        (?P<year>[0-9][0-9][0-9][0-9])
567
        -(?P<month>[0-9][0-9]?)
568
        -(?P<day>[0-9][0-9]?)
569
        (?:(?:[Tt]|[ \t]+)
570
        (?P<hour>[0-9][0-9]?)
571
        :(?P<minute>[0-9][0-9])
572
        :(?P<second>[0-9][0-9])
573
        (?:\.(?P<fraction>[0-9]*))?
574
        (?:[ \t]*(?P<tz>Z|(?P<tz_sign>[-+])(?P<tz_hour>[0-9][0-9]?)
575
        (?::(?P<tz_minute>[0-9][0-9]))?))?)?
576
        $~x
577
EOF;
578
    }
579
580
    /**
581
     * Gets a regex that matches a YAML number in hexadecimal notation.
582
     *
583
     * @return string
584
     */
585
    private static function getHexRegex()
586
    {
587
        return '~^0x[0-9a-f]++$~i';
588
    }
589
}
590