Completed
Pull Request — master (#133)
by Goffy
16:45
created

Parser::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 4
rs 10
c 0
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
16
/**
17
 * Parser parses YAML strings to convert them to PHP arrays.
18
 *
19
 * @author Fabien Potencier <[email protected]>
20
 */
21
class Parser
22
{
23
    const FOLDED_SCALAR_PATTERN = '(?P<separator>\||>)(?P<modifiers>\+|\-|\d+|\+\d+|\-\d+|\d+\+|\d+\-)?(?P<comments> +#.*)?';
24
25
    private $offset         = 0;
26
    private $lines          = array();
27
    private $currentLineNb  = -1;
28
    private $currentLine    = '';
29
    private $refs           = array();
30
31
    /**
32
     * Constructor
33
     *
34
     * @param int     $offset The offset of YAML document (used for line numbers in error messages)
35
     */
36
    public function __construct($offset = 0)
37
    {
38
        $this->offset = $offset;
39
    }
40
41
    /**
42
     * Parses a YAML string to a PHP value.
43
     *
44
     * @param string  $value                  A YAML string
45
     * @param bool    $exceptionOnInvalidType true if an exception must be thrown on invalid types (a PHP resource or object), false otherwise
46
     * @param bool    $objectSupport          true if object support is enabled, false otherwise
47
     *
48
     * @return mixed  A PHP value
49
     *
50
     * @throws ParseException If the YAML is not valid
51
     */
52
    public function parse($value, $exceptionOnInvalidType = false, $objectSupport = false)
53
    {
54
        $this->currentLineNb = -1;
55
        $this->currentLine = '';
56
        $this->lines = explode("\n", $this->cleanup($value));
57
58
        if (!preg_match('//u', $value)) {
59
            throw new ParseException('The YAML value does not appear to be valid UTF-8.');
60
        }
61
62 View Code Duplication
        if (function_exists('mb_internal_encoding') && ((int) ini_get('mbstring.func_overload')) & 2) {
63
            $mbEncoding = mb_internal_encoding();
64
            mb_internal_encoding('UTF-8');
65
        }
66
67
        $data = array();
68
        $context = null;
69
        while ($this->moveToNextLine()) {
70
            if ($this->isCurrentLineEmpty()) {
71
                continue;
72
            }
73
74
            // tab?
75
            if ("\t" === $this->currentLine[0]) {
76
                throw new ParseException('A YAML file cannot contain tabs as indentation.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
77
            }
78
79
            $isRef = $isInPlace = $isProcessed = false;
80
            if (preg_match('#^\-((?P<leadspaces>\s+)(?P<value>.+?))?\s*$#u', $this->currentLine, $values)) {
81
                if ($context && 'mapping' == $context) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $context of type null|string is loosely compared to true; 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...
82
                    throw new ParseException('You cannot define a sequence item when in a mapping');
83
                }
84
                $context = 'sequence';
85
86 View Code Duplication
                if (isset($values['value']) && preg_match('#^&(?P<ref>[^ ]+) *(?P<value>.*)#u', $values['value'], $matches)) {
87
                    $isRef = $matches['ref'];
88
                    $values['value'] = $matches['value'];
89
                }
90
91
                // array
92
                if (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) {
93
                    $c = $this->getRealCurrentLineNb() + 1;
94
                    $parser = new Parser($c);
95
                    $parser->refs = & $this->refs;
96
                    $data[] = $parser->parse($this->getNextEmbedBlock(null, true), $exceptionOnInvalidType, $objectSupport);
97
                } else {
98
                    if (isset($values['leadspaces'])
99
                        && ' ' == $values['leadspaces']
100
                        && preg_match('#^(?P<key>'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\{\[].*?) *\:(\s+(?P<value>.+?))?\s*$#u', $values['value'], $matches)
101
                    ) {
102
                        // this is a compact notation element, add to next block and parse
103
                        $c = $this->getRealCurrentLineNb();
104
                        $parser = new Parser($c);
105
                        $parser->refs = & $this->refs;
106
107
                        $block = $values['value'];
108
                        if ($this->isNextLineIndented()) {
109
                            $block .= "\n".$this->getNextEmbedBlock($this->getCurrentLineIndentation() + 2);
110
                        }
111
112
                        $data[] = $parser->parse($block, $exceptionOnInvalidType, $objectSupport);
113
                    } else {
114
                        $data[] = $this->parseValue($values['value'], $exceptionOnInvalidType, $objectSupport);
115
                    }
116
                }
117
            } elseif (preg_match('#^(?P<key>'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\[\{].*?) *\:(\s+(?P<value>.+?))?\s*$#u', $this->currentLine, $values) && (false === strpos($values['key'],' #') || in_array($values['key'][0], array('"', "'")))) {
118
                if ($context && 'sequence' == $context) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $context of type null|string is loosely compared to true; 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...
119
                    throw new ParseException('You cannot define a mapping item when in a sequence');
120
                }
121
                $context = 'mapping';
122
123
                // force correct settings
124
                Inline::parse(null, $exceptionOnInvalidType, $objectSupport, $this->refs);
125
                try {
126
                    $key = Inline::parseScalar($values['key']);
127
                } catch (ParseException $e) {
128
                    $e->setParsedLine($this->getRealCurrentLineNb() + 1);
129
                    $e->setSnippet($this->currentLine);
130
131
                    throw $e;
132
                }
133
134
                if ('<<' === $key) {
135
                    if (isset($values['value']) && 0 === strpos($values['value'], '*')) {
136
                        $isInPlace = substr($values['value'], 1);
137
                        if (!array_key_exists($isInPlace, $this->refs)) {
138
                            throw new ParseException(sprintf('Reference "%s" does not exist.', $isInPlace), $this->getRealCurrentLineNb() + 1, $this->currentLine);
139
                        }
140
                    } else {
141
                        if (isset($values['value']) && $values['value'] !== '') {
142
                            $value = $values['value'];
143
                        } else {
144
                            $value = $this->getNextEmbedBlock();
145
                        }
146
                        $c = $this->getRealCurrentLineNb() + 1;
147
                        $parser = new Parser($c);
148
                        $parser->refs = & $this->refs;
149
                        $parsed = $parser->parse($value, $exceptionOnInvalidType, $objectSupport);
150
151
                        $merged = array();
152
                        if (!is_array($parsed)) {
153
                            throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
154
                        } elseif (isset($parsed[0])) {
155
                            // Numeric array, merge individual elements
156
                            foreach (array_reverse($parsed) as $parsedItem) {
157
                                if (!is_array($parsedItem)) {
158
                                    throw new ParseException('Merge items must be arrays.', $this->getRealCurrentLineNb() + 1, $parsedItem);
159
                                }
160
                                $merged = array_merge($parsedItem, $merged);
161
                            }
162
                        } else {
163
                            // Associative array, merge
164
                            $merged = array_merge($merged, $parsed);
165
                        }
166
167
                        $isProcessed = $merged;
168
                    }
169 View Code Duplication
                } elseif (isset($values['value']) && preg_match('#^&(?P<ref>[^ ]+) *(?P<value>.*)#u', $values['value'], $matches)) {
170
                    $isRef = $matches['ref'];
171
                    $values['value'] = $matches['value'];
172
                }
173
174
                if ($isProcessed) {
175
                    // Merge keys
176
                    $data = $isProcessed;
177
                // hash
178
                } elseif (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) {
179
                    // if next line is less indented or equal, then it means that the current value is null
180
                    if (!$this->isNextLineIndented() && !$this->isNextLineUnIndentedCollection()) {
181
                        $data[$key] = null;
182
                    } else {
183
                        $c = $this->getRealCurrentLineNb() + 1;
184
                        $parser = new Parser($c);
185
                        $parser->refs = & $this->refs;
186
                        $data[$key] = $parser->parse($this->getNextEmbedBlock(), $exceptionOnInvalidType, $objectSupport);
187
                    }
188
                } else {
189
                    if ($isInPlace) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $isInPlace of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false 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...
190
                        $data = $this->refs[$isInPlace];
191
                    } else {
192
                        $data[$key] = $this->parseValue($values['value'], $exceptionOnInvalidType, $objectSupport);
193
                    }
194
                }
195
            } else {
196
                // 1-liner optionally followed by newline
197
                $lineCount = count($this->lines);
198
                if (1 === $lineCount || (2 === $lineCount && empty($this->lines[1]))) {
199
                    try {
200
                        $value = Inline::parse($this->lines[0], $exceptionOnInvalidType, $objectSupport, $this->refs);
201
                    } catch (ParseException $e) {
202
                        $e->setParsedLine($this->getRealCurrentLineNb() + 1);
203
                        $e->setSnippet($this->currentLine);
204
205
                        throw $e;
206
                    }
207
208
                    if (is_array($value)) {
209
                        $first = reset($value);
210
                        if (is_string($first) && 0 === strpos($first, '*')) {
211
                            $data = array();
212
                            foreach ($value as $alias) {
213
                                $data[] = $this->refs[substr($alias, 1)];
214
                            }
215
                            $value = $data;
216
                        }
217
                    }
218
219
                    if (isset($mbEncoding)) {
220
                        mb_internal_encoding($mbEncoding);
221
                    }
222
223
                    return $value;
224
                }
225
226
                switch (preg_last_error()) {
227
                    case PREG_INTERNAL_ERROR:
228
                        $error = 'Internal PCRE error.';
229
                        break;
230
                    case PREG_BACKTRACK_LIMIT_ERROR:
231
                        $error = 'pcre.backtrack_limit reached.';
232
                        break;
233
                    case PREG_RECURSION_LIMIT_ERROR:
234
                        $error = 'pcre.recursion_limit reached.';
235
                        break;
236
                    case PREG_BAD_UTF8_ERROR:
237
                        $error = 'Malformed UTF-8 data.';
238
                        break;
239
                    case PREG_BAD_UTF8_OFFSET_ERROR:
240
                        $error = 'Offset doesn\'t correspond to the begin of a valid UTF-8 code point.';
241
                        break;
242
                    default:
243
                        $error = 'Unable to parse.';
244
                }
245
246
                throw new ParseException($error, $this->getRealCurrentLineNb() + 1, $this->currentLine);
247
            }
248
249
            if ($isRef) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $isRef of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false 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...
250
                $this->refs[$isRef] = end($data);
251
            }
252
        }
253
254
        if (isset($mbEncoding)) {
255
            mb_internal_encoding($mbEncoding);
256
        }
257
258
        return empty($data) ? null : $data;
259
    }
260
261
    /**
262
     * Returns the current line number (takes the offset into account).
263
     *
264
     * @return int     The current line number
265
     */
266
    private function getRealCurrentLineNb()
267
    {
268
        return $this->currentLineNb + $this->offset;
269
    }
270
271
    /**
272
     * Returns the current line indentation.
273
     *
274
     * @return int     The current line indentation
275
     */
276
    private function getCurrentLineIndentation()
277
    {
278
        return strlen($this->currentLine) - strlen(ltrim($this->currentLine, ' '));
279
    }
280
281
    /**
282
     * Returns the next embed block of YAML.
283
     *
284
     * @param int  $indentation The indent level at which the block is to be read, or null for default
285
     * @param bool $inSequence  True if the enclosing data structure is a sequence
286
     *
287
     * @return string A YAML string
0 ignored issues
show
Documentation introduced by
Should the return type not be null|string?

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...
288
     *
289
     * @throws ParseException When indentation problem are detected
290
     */
291
    private function getNextEmbedBlock($indentation = null, $inSequence = false)
292
    {
293
        $oldLineIndentation = $this->getCurrentLineIndentation();
294
295
        if (!$this->moveToNextLine()) {
296
            return;
297
        }
298
299
        if (null === $indentation) {
300
            $newIndent = $this->getCurrentLineIndentation();
301
302
            $unindentedEmbedBlock = $this->isStringUnIndentedCollectionItem($this->currentLine);
0 ignored issues
show
Unused Code introduced by
The call to Parser::isStringUnIndentedCollectionItem() has too many arguments starting with $this->currentLine.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
303
304
            if (!$this->isCurrentLineEmpty() && 0 === $newIndent && !$unindentedEmbedBlock) {
305
                throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
306
            }
307
        } else {
308
            $newIndent = $indentation;
309
        }
310
311
        $data = array(substr($this->currentLine, $newIndent));
312
313
        if ($inSequence && $oldLineIndentation === $newIndent && '-' === $data[0][0]) {
314
            // the previous line contained a dash but no item content, this line is a sequence item with the same indentation
315
            // and therefore no nested list or mapping
316
            $this->moveToPreviousLine();
317
318
            return;
319
        }
320
321
        $isItUnindentedCollection = $this->isStringUnIndentedCollectionItem($this->currentLine);
0 ignored issues
show
Unused Code introduced by
The call to Parser::isStringUnIndentedCollectionItem() has too many arguments starting with $this->currentLine.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
322
323
        // Comments must not be removed inside a string block (ie. after a line ending with "|")
324
        $removeCommentsPattern = '~'.self::FOLDED_SCALAR_PATTERN.'$~';
325
        $removeComments = !preg_match($removeCommentsPattern, $this->currentLine);
326
327
        while ($this->moveToNextLine()) {
328
            $indent = $this->getCurrentLineIndentation();
329
330
            if ($indent === $newIndent) {
331
                $removeComments = !preg_match($removeCommentsPattern, $this->currentLine);
332
            }
333
334
            if ($isItUnindentedCollection && !$this->isStringUnIndentedCollectionItem($this->currentLine)) {
0 ignored issues
show
Unused Code introduced by
The call to Parser::isStringUnIndentedCollectionItem() has too many arguments starting with $this->currentLine.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
335
                $this->moveToPreviousLine();
336
                break;
337
            }
338
339
            if ($this->isCurrentLineBlank()) {
340
                $data[] = substr($this->currentLine, $newIndent);
341
                continue;
342
            }
343
344
            if ($removeComments && $this->isCurrentLineComment()) {
345
                continue;
346
            }
347
348
            if ($indent >= $newIndent) {
349
                $data[] = substr($this->currentLine, $newIndent);
350
            } elseif (0 == $indent) {
351
                $this->moveToPreviousLine();
352
353
                break;
354
            } else {
355
                throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
356
            }
357
        }
358
359
        return implode("\n", $data);
360
    }
361
362
    /**
363
     * Moves the parser to the next line.
364
     *
365
     * @return bool
366
     */
367
    private function moveToNextLine()
368
    {
369
        if ($this->currentLineNb >= count($this->lines) - 1) {
370
            return false;
371
        }
372
373
        $this->currentLine = $this->lines[++$this->currentLineNb];
374
375
        return true;
376
    }
377
378
    /**
379
     * Moves the parser to the previous line.
380
     */
381
    private function moveToPreviousLine()
382
    {
383
        $this->currentLine = $this->lines[--$this->currentLineNb];
384
    }
385
386
    /**
387
     * Parses a YAML value.
388
     *
389
     * @param string  $value                  A YAML value
390
     * @param bool    $exceptionOnInvalidType True if an exception must be thrown on invalid types false otherwise
391
     * @param bool    $objectSupport          True if object support is enabled, false otherwise
392
     *
393
     * @return mixed  A PHP value
394
     *
395
     * @throws ParseException When reference does not exist
396
     */
397
    private function parseValue($value, $exceptionOnInvalidType, $objectSupport)
398
    {
399
        if (0 === strpos($value, '*')) {
400 View Code Duplication
            if (false !== $pos = strpos($value, '#')) {
401
                $value = substr($value, 1, $pos - 2);
402
            } else {
403
                $value = substr($value, 1);
404
            }
405
406
            if (!array_key_exists($value, $this->refs)) {
407
                throw new ParseException(sprintf('Reference "%s" does not exist.', $value), $this->currentLine);
408
            }
409
410
            return $this->refs[$value];
411
        }
412
413
        if (preg_match('/^'.self::FOLDED_SCALAR_PATTERN.'$/', $value, $matches)) {
414
            $modifiers = isset($matches['modifiers']) ? $matches['modifiers'] : '';
415
416
            return $this->parseFoldedScalar($matches['separator'], preg_replace('#\d+#', '', $modifiers), intval(abs($modifiers)));
417
        }
418
419
        try {
420
            return Inline::parse($value, $exceptionOnInvalidType, $objectSupport, $this->refs);
421
        } catch (ParseException $e) {
422
            $e->setParsedLine($this->getRealCurrentLineNb() + 1);
423
            $e->setSnippet($this->currentLine);
424
425
            throw $e;
426
        }
427
    }
428
429
    /**
430
     * Parses a folded scalar.
431
     *
432
     * @param string  $separator   The separator that was used to begin this folded scalar (| or >)
433
     * @param string  $indicator   The indicator that was used to begin this folded scalar (+ or -)
434
     * @param int     $indentation The indentation that was used to begin this folded scalar
435
     *
436
     * @return string  The text value
437
     */
438
    private function parseFoldedScalar($separator, $indicator = '', $indentation = 0)
439
    {
440
        $notEOF = $this->moveToNextLine();
441
        if (!$notEOF) {
442
            return '';
443
        }
444
445
        $isCurrentLineBlank = $this->isCurrentLineBlank();
446
        $text = '';
447
448
        // leading blank lines are consumed before determining indentation
449
        while ($notEOF && $isCurrentLineBlank) {
450
            // newline only if not EOF
451
            if ($notEOF = $this->moveToNextLine()) {
452
                $text .= "\n";
453
                $isCurrentLineBlank = $this->isCurrentLineBlank();
454
            }
455
        }
456
457
        // determine indentation if not specified
458
        if (0 === $indentation) {
459
            if (preg_match('/^ +/', $this->currentLine, $matches)) {
460
                $indentation = strlen($matches[0]);
461
            }
462
        }
463
464
        if ($indentation > 0) {
465
            $pattern = sprintf('/^ {%d}(.*)$/', $indentation);
466
467
            while (
468
                $notEOF && (
469
                    $isCurrentLineBlank ||
470
                    preg_match($pattern, $this->currentLine, $matches)
471
                )
472
            ) {
473
                if ($isCurrentLineBlank) {
474
                    $text .= substr($this->currentLine, $indentation);
475
                } else {
476
                    $text .= $matches[1];
0 ignored issues
show
Bug introduced by
The variable $matches does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
477
                }
478
479
                // newline only if not EOF
480
                if ($notEOF = $this->moveToNextLine()) {
481
                    $text .= "\n";
482
                    $isCurrentLineBlank = $this->isCurrentLineBlank();
483
                }
484
            }
485
        } elseif ($notEOF) {
486
            $text .= "\n";
487
        }
488
489
        if ($notEOF) {
490
            $this->moveToPreviousLine();
491
        }
492
493
        // replace all non-trailing single newlines with spaces in folded blocks
494
        if ('>' === $separator) {
495
            preg_match('/(\n*)$/', $text, $matches);
496
            $text = preg_replace('/(?<!\n)\n(?!\n)/', ' ', rtrim($text, "\n"));
497
            $text .= $matches[1];
498
        }
499
500
        // deal with trailing newlines as indicated
501
        if ('' === $indicator) {
502
            $text = preg_replace('/\n+$/s', "\n", $text);
503
        } elseif ('-' === $indicator) {
504
            $text = preg_replace('/\n+$/s', '', $text);
505
        }
506
507
        return $text;
508
    }
509
510
    /**
511
     * Returns true if the next line is indented.
512
     *
513
     * @return bool    Returns true if the next line is indented, false otherwise
514
     */
515
    private function isNextLineIndented()
516
    {
517
        $currentIndentation = $this->getCurrentLineIndentation();
518
        $EOF = !$this->moveToNextLine();
519
520
        while (!$EOF && $this->isCurrentLineEmpty()) {
521
            $EOF = !$this->moveToNextLine();
522
        }
523
524
        if ($EOF) {
525
            return false;
526
        }
527
528
        $ret = false;
529
        if ($this->getCurrentLineIndentation() > $currentIndentation) {
530
            $ret = true;
531
        }
532
533
        $this->moveToPreviousLine();
534
535
        return $ret;
536
    }
537
538
    /**
539
     * Returns true if the current line is blank or if it is a comment line.
540
     *
541
     * @return bool    Returns true if the current line is empty or if it is a comment line, false otherwise
542
     */
543
    private function isCurrentLineEmpty()
544
    {
545
        return $this->isCurrentLineBlank() || $this->isCurrentLineComment();
546
    }
547
548
    /**
549
     * Returns true if the current line is blank.
550
     *
551
     * @return bool    Returns true if the current line is blank, false otherwise
552
     */
553
    private function isCurrentLineBlank()
554
    {
555
        return '' == trim($this->currentLine, ' ');
556
    }
557
558
    /**
559
     * Returns true if the current line is a comment line.
560
     *
561
     * @return bool    Returns true if the current line is a comment line, false otherwise
562
     */
563
    private function isCurrentLineComment()
564
    {
565
        //checking explicitly the first char of the trim is faster than loops or strpos
566
        $ltrimmedLine = ltrim($this->currentLine, ' ');
567
568
        return $ltrimmedLine[0] === '#';
569
    }
570
571
    /**
572
     * Cleanups a YAML string to be parsed.
573
     *
574
     * @param string $value The input YAML string
575
     *
576
     * @return string A cleaned up YAML string
577
     */
578
    private function cleanup($value)
579
    {
580
        $value = str_replace(array("\r\n", "\r"), "\n", $value);
581
582
        // strip YAML header
583
        $count = 0;
584
        $value = preg_replace('#^\%YAML[: ][\d\.]+.*\n#su', '', $value, -1, $count);
585
        $this->offset += $count;
586
587
        // remove leading comments
588
        $trimmedValue = preg_replace('#^(\#.*?\n)+#s', '', $value, -1, $count);
589
        if ($count == 1) {
590
            // items have been removed, update the offset
591
            $this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n");
592
            $value = $trimmedValue;
593
        }
594
595
        // remove start of the document marker (---)
596
        $trimmedValue = preg_replace('#^\-\-\-.*?\n#s', '', $value, -1, $count);
597
        if ($count == 1) {
598
            // items have been removed, update the offset
599
            $this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n");
600
            $value = $trimmedValue;
601
602
            // remove end of the document marker (...)
603
            $value = preg_replace('#\.\.\.\s*$#s', '', $value);
604
        }
605
606
        return $value;
607
    }
608
609
    /**
610
     * Returns true if the next line starts unindented collection
611
     *
612
     * @return bool    Returns true if the next line starts unindented collection, false otherwise
613
     */
614
    private function isNextLineUnIndentedCollection()
615
    {
616
        $currentIndentation = $this->getCurrentLineIndentation();
617
        $notEOF = $this->moveToNextLine();
618
619
        while ($notEOF && $this->isCurrentLineEmpty()) {
620
            $notEOF = $this->moveToNextLine();
621
        }
622
623
        if (false === $notEOF) {
624
            return false;
625
        }
626
627
        $ret = false;
628
        if (
629
            $this->getCurrentLineIndentation() == $currentIndentation
630
            &&
631
            $this->isStringUnIndentedCollectionItem($this->currentLine)
0 ignored issues
show
Unused Code introduced by
The call to Parser::isStringUnIndentedCollectionItem() has too many arguments starting with $this->currentLine.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
632
        ) {
633
            $ret = true;
634
        }
635
636
        $this->moveToPreviousLine();
637
638
        return $ret;
639
    }
640
641
    /**
642
     * Returns true if the string is un-indented collection item
643
     *
644
     * @return bool    Returns true if the string is un-indented collection item, false otherwise
645
     */
646
    private function isStringUnIndentedCollectionItem()
647
    {
648
        return (0 === strpos($this->currentLine, '- '));
649
    }
650
}
651