Inline::parseQuotedScalar()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 19
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 19
rs 9.4285
cc 3
eloc 11
nc 3
nop 2
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
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 (0 == strlen($value)) {
52
            return '';
53
        }
54
55 View Code Duplication
        if (function_exists('mb_internal_encoding') && ((int) ini_get('mbstring.func_overload')) & 2) {
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...
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::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 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...
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
     * Dumps a PHP array to a YAML string.
162
     *
163
     * @param array $value                  The PHP array to dump
164
     * @param bool  $exceptionOnInvalidType true if an exception must be thrown on invalid types (a PHP resource or object), false otherwise
165
     * @param bool  $objectSupport          true if object support is enabled, false otherwise
166
     *
167
     * @return string The YAML string representing the PHP array
168
     */
169
    private static function dumpArray($value, $exceptionOnInvalidType, $objectSupport)
170
    {
171
        // array
172
        $keys = array_keys($value);
173
        $keysCount = count($keys);
174
        if ((1 === $keysCount && '0' == $keys[0])
175
            || ($keysCount > 1 && array_reduce($keys, function ($v, $w) { return (int) $v + $w; }, 0) === $keysCount * ($keysCount - 1) / 2)
176
        ) {
177
            $output = array();
178
            foreach ($value as $val) {
179
                $output[] = self::dump($val, $exceptionOnInvalidType, $objectSupport);
180
            }
181
182
            return sprintf('[%s]', implode(', ', $output));
183
        }
184
185
        // mapping
186
        $output = array();
187
        foreach ($value as $key => $val) {
188
            $output[] = sprintf('%s: %s', self::dump($key, $exceptionOnInvalidType, $objectSupport), self::dump($val, $exceptionOnInvalidType, $objectSupport));
189
        }
190
191
        return sprintf('{ %s }', implode(', ', $output));
192
    }
193
194
    /**
195
     * Parses a scalar to a YAML string.
196
     *
197
     * @param string $scalar
198
     * @param string $delimiters
199
     * @param array  $stringDelimiters
200
     * @param int    &$i
201
     * @param bool   $evaluate
202
     * @param array  $references
203
     *
204
     * @return string A YAML string
205
     *
206
     * @throws ParseException When malformed inline YAML string is parsed
207
     */
208
    public static function parseScalar($scalar, $delimiters = null, $stringDelimiters = array('"', "'"), &$i = 0, $evaluate = true, $references = array())
209
    {
210
        if (in_array($scalar[$i], $stringDelimiters)) {
211
            // quoted scalar
212
            $output = self::parseQuotedScalar($scalar, $i);
213
214
            if (null !== $delimiters) {
215
                $tmp = ltrim(substr($scalar, $i), ' ');
216
                if (!in_array($tmp[0], $delimiters)) {
217
                    throw new ParseException(sprintf('Unexpected characters (%s).', substr($scalar, $i)));
218
                }
219
            }
220
        } else {
221
            // "normal" string
222
            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...
223
                $output = substr($scalar, $i);
224
                $i += strlen($output);
225
226
                // remove comments
227
                if (false !== $strpos = strpos($output, ' #')) {
228
                    $output = rtrim(substr($output, 0, $strpos));
229
                }
230
            } elseif (preg_match('/^(.+?)('.implode('|', $delimiters).')/', substr($scalar, $i), $match)) {
231
                $output = $match[1];
232
                $i += strlen($output);
233
            } else {
234
                throw new ParseException(sprintf('Malformed inline YAML string (%s).', $scalar));
235
            }
236
237
            if ($evaluate) {
238
                $output = self::evaluateScalar($output, $references);
239
            }
240
        }
241
242
        return $output;
243
    }
244
245
    /**
246
     * Parses a quoted scalar to YAML.
247
     *
248
     * @param string $scalar
249
     * @param int    &$i
250
     *
251
     * @return string A YAML string
252
     *
253
     * @throws ParseException When malformed inline YAML string is parsed
254
     */
255
    private static function parseQuotedScalar($scalar, &$i)
256
    {
257
        if (!preg_match('/'.self::REGEX_QUOTED_STRING.'/Au', substr($scalar, $i), $match)) {
258
            throw new ParseException(sprintf('Malformed inline YAML string (%s).', substr($scalar, $i)));
259
        }
260
261
        $output = substr($match[0], 1, strlen($match[0]) - 2);
262
263
        $unescaper = new Unescaper();
264
        if ('"' == $scalar[$i]) {
265
            $output = $unescaper->unescapeDoubleQuotedString($output);
266
        } else {
267
            $output = $unescaper->unescapeSingleQuotedString($output);
268
        }
269
270
        $i += strlen($match[0]);
271
272
        return $output;
273
    }
274
275
    /**
276
     * Parses a sequence to a YAML string.
277
     *
278
     * @param string $sequence
279
     * @param int    &$i
280
     * @param array  $references
281
     *
282
     * @return string A YAML string
283
     *
284
     * @throws ParseException When malformed inline YAML string is parsed
285
     */
286
    private static function parseSequence($sequence, &$i = 0, $references = array())
287
    {
288
        $output = array();
289
        $len = strlen($sequence);
290
        ++$i;
291
292
        // [foo, bar, ...]
293
        while ($i < $len) {
294
            switch ($sequence[$i]) {
295
                case '[':
296
                    // nested sequence
297
                    $output[] = self::parseSequence($sequence, $i, $references);
298
                    break;
299
                case '{':
300
                    // nested mapping
301
                    $output[] = self::parseMapping($sequence, $i, $references);
302
                    break;
303
                case ']':
304
                    return $output;
305
                case ',':
306
                case ' ':
307
                    break;
308
                default:
309
                    $isQuoted = in_array($sequence[$i], array('"', "'"));
310
                    $value = self::parseScalar($sequence, array(',', ']'), array('"', "'"), $i, true, $references);
0 ignored issues
show
Documentation introduced by
array(',', ']') is of type array<integer,string,{"0":"string","1":"string"}>, but the function expects a string|null.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
311
312
                    // the value can be an array if a reference has been resolved to an array var
313
                    if (!is_array($value) && !$isQuoted && false !== strpos($value, ': ')) {
314
                        // embedded mapping?
315
                        try {
316
                            $pos = 0;
317
                            $value = self::parseMapping('{'.$value.'}', $pos, $references);
318
                        } catch (\InvalidArgumentException $e) {
319
                            // no, it's not
320
                        }
321
                    }
322
323
                    $output[] = $value;
324
325
                    --$i;
326
            }
327
328
            ++$i;
329
        }
330
331
        throw new ParseException(sprintf('Malformed inline YAML string %s', $sequence));
332
    }
333
334
    /**
335
     * Parses a mapping to a YAML string.
336
     *
337
     * @param string $mapping
338
     * @param int    &$i
339
     * @param array  $references
340
     *
341
     * @return string A YAML string
342
     *
343
     * @throws ParseException When malformed inline YAML string is parsed
344
     */
345
    private static function parseMapping($mapping, &$i = 0, $references = array())
346
    {
347
        $output = array();
348
        $len = strlen($mapping);
349
        ++$i;
350
351
        // {foo: bar, bar:foo, ...}
352
        while ($i < $len) {
353
            switch ($mapping[$i]) {
354
                case ' ':
355
                case ',':
356
                    ++$i;
357
                    continue 2;
358
                case '}':
359
                    if (self::$objectForMap) {
360
                        return (object) $output;
361
                    }
362
363
                    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...
364
            }
365
366
            // key
367
            $key = self::parseScalar($mapping, array(':', ' '), array('"', "'"), $i, false);
0 ignored issues
show
Documentation introduced by
array(':', ' ') is of type array<integer,string,{"0":"string","1":"string"}>, but the function expects a string|null.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
368
369
            // value
370
            $done = false;
371
372
            while ($i < $len) {
373
                switch ($mapping[$i]) {
374 View Code Duplication
                    case '[':
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...
375
                        // nested sequence
376
                        $value = self::parseSequence($mapping, $i, $references);
377
                        // Spec: Keys MUST be unique; first one wins.
378
                        // Parser cannot abort this mapping earlier, since lines
379
                        // are processed sequentially.
380
                        if (!isset($output[$key])) {
381
                            $output[$key] = $value;
382
                        }
383
                        $done = true;
384
                        break;
385 View Code Duplication
                    case '{':
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...
386
                        // nested mapping
387
                        $value = self::parseMapping($mapping, $i, $references);
388
                        // Spec: Keys MUST be unique; first one wins.
389
                        // Parser cannot abort this mapping earlier, since lines
390
                        // are processed sequentially.
391
                        if (!isset($output[$key])) {
392
                            $output[$key] = $value;
393
                        }
394
                        $done = true;
395
                        break;
396
                    case ':':
397
                    case ' ':
398
                        break;
399
                    default:
400
                        $value = self::parseScalar($mapping, array(',', '}'), array('"', "'"), $i, true, $references);
0 ignored issues
show
Documentation introduced by
array(',', '}') is of type array<integer,string,{"0":"string","1":"string"}>, but the function expects a string|null.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
401
                        // Spec: Keys MUST be unique; first one wins.
402
                        // Parser cannot abort this mapping earlier, since lines
403
                        // are processed sequentially.
404
                        if (!isset($output[$key])) {
405
                            $output[$key] = $value;
406
                        }
407
                        $done = true;
408
                        --$i;
409
                }
410
411
                ++$i;
412
413
                if ($done) {
414
                    continue 2;
415
                }
416
            }
417
        }
418
419
        throw new ParseException(sprintf('Malformed inline YAML string %s', $mapping));
420
    }
421
422
    /**
423
     * Evaluates scalars and replaces magic values.
424
     *
425
     * @param string $scalar
426
     * @param array  $references
427
     *
428
     * @return string A YAML string
429
     *
430
     * @throws ParseException when object parsing support was disabled and the parser detected a PHP object or when a reference could not be resolved
431
     */
432
    private static function evaluateScalar($scalar, $references = array())
433
    {
434
        $scalar = trim($scalar);
435
        $scalarLower = strtolower($scalar);
436
437
        if (0 === strpos($scalar, '*')) {
438 View Code Duplication
            if (false !== $pos = strpos($scalar, '#')) {
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...
439
                $value = substr($scalar, 1, $pos - 2);
440
            } else {
441
                $value = substr($scalar, 1);
442
            }
443
444
            // an unquoted *
445
            if (false === $value || '' === $value) {
446
                throw new ParseException('A reference must contain at least one character.');
447
            }
448
449
            if (!array_key_exists($value, $references)) {
450
                throw new ParseException(sprintf('Reference "%s" does not exist.', $value));
451
            }
452
453
            return $references[$value];
454
        }
455
456
        switch (true) {
457
            case 'null' === $scalarLower:
458
            case '' === $scalar:
459
            case '~' === $scalar:
460
                return;
461
            case 'true' === $scalarLower:
462
                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...
463
            case 'false' === $scalarLower:
464
                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...
465
            // Optimise for returning strings.
466
            case $scalar[0] === '+' || $scalar[0] === '-' || $scalar[0] === '.' || $scalar[0] === '!' || is_numeric($scalar[0]):
467
                switch (true) {
0 ignored issues
show
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...
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...
468
                    case 0 === strpos($scalar, '!str'):
469
                        return (string) substr($scalar, 5);
470
                    case 0 === strpos($scalar, '! '):
471
                        return (int) self::parseScalar(substr($scalar, 2));
472
                    case 0 === strpos($scalar, '!!php/object:'):
473
                        if (self::$objectSupport) {
474
                            return unserialize(substr($scalar, 13));
475
                        }
476
477
                        if (self::$exceptionOnInvalidType) {
478
                            throw new ParseException('Object support when parsing a YAML file has been disabled.');
479
                        }
480
481
                        return;
482
                    case 0 === strpos($scalar, '!!float '):
483
                        return (float) substr($scalar, 8);
484
                    case ctype_digit($scalar):
485
                        $raw = $scalar;
486
                        $cast = (int) $scalar;
487
488
                        return '0' == $scalar[0] ? octdec($scalar) : (((string) $raw == (string) $cast) ? $cast : $raw);
489
                    case '-' === $scalar[0] && ctype_digit(substr($scalar, 1)):
490
                        $raw = $scalar;
491
                        $cast = (int) $scalar;
492
493
                        return '0' == $scalar[1] ? octdec($scalar) : (((string) $raw === (string) $cast) ? $cast : $raw);
494
                    case is_numeric($scalar):
495
                    case preg_match(self::getHexRegex(), $scalar):
496
                        return '0x' === $scalar[0].$scalar[1] ? hexdec($scalar) : (float) $scalar;
497
                    case '.inf' === $scalarLower:
498
                    case '.nan' === $scalarLower:
499
                        return -log(0);
500
                    case '-.inf' === $scalarLower:
501
                        return log(0);
502
                    case preg_match('/^(-|\+)?[0-9,]+(\.[0-9]+)?$/', $scalar):
503
                        return (float) str_replace(',', '', $scalar);
504
                    case preg_match(self::getTimestampRegex(), $scalar):
505
                        return strtotime($scalar);
506
                }
507
            default:
508
                return (string) $scalar;
509
        }
510
    }
511
512
    /**
513
     * Gets a regex that matches a YAML date.
514
     *
515
     * @return string The regular expression
516
     *
517
     * @see http://www.yaml.org/spec/1.2/spec.html#id2761573
518
     */
519
    private static function getTimestampRegex()
520
    {
521
        return <<<EOF
522
        ~^
523
        (?P<year>[0-9][0-9][0-9][0-9])
524
        -(?P<month>[0-9][0-9]?)
525
        -(?P<day>[0-9][0-9]?)
526
        (?:(?:[Tt]|[ \t]+)
527
        (?P<hour>[0-9][0-9]?)
528
        :(?P<minute>[0-9][0-9])
529
        :(?P<second>[0-9][0-9])
530
        (?:\.(?P<fraction>[0-9]*))?
531
        (?:[ \t]*(?P<tz>Z|(?P<tz_sign>[-+])(?P<tz_hour>[0-9][0-9]?)
532
        (?::(?P<tz_minute>[0-9][0-9]))?))?)?
533
        $~x
534
EOF;
535
    }
536
537
    /**
538
     * Gets a regex that matches a YAML number in hexadecimal notation.
539
     *
540
     * @return string
541
     */
542
    private static function getHexRegex()
543
    {
544
        return '~^0x[0-9a-f]++$~i';
545
    }
546
}
547