Passed
Branch master (f496ba)
by stéphane
02:11
created

Loader.php (4 issues)

1
<?php
2
declare(strict_types=1);
3
4
namespace Dallgoot\Yaml;
5
6
use Dallgoot\Yaml\{Node as Node, Types as T, Builder};
0 ignored issues
show
This use statement conflicts with another class in this namespace, Dallgoot\Yaml\Node. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
This use statement conflicts with another class in this namespace, Dallgoot\Yaml\Builder. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
7
8
class Loader
9
{
10
    public $errors = [];
11
    //options
12
    public const EXCLUDE_DIRECTIVES = 0001;//DONT include_directive
13
    public const IGNORE_COMMENTS    = 0010;//DONT include_comments
14
    public const EXCEPTIONS_PARSING = 0100;//THROW Exception on parsing Errors
15
    public const NO_OBJECT_FOR_DATE = 1000;//DONT import date strings as dateTime Object
16
    //
17
    private $_content;
18
    private $filePath;
19
    private $_debug   = 0;//TODO: determine levels
20
    private $_options = 0;
21
    //Exceptions
22
    const INVALID_VALUE        = self::class.": at line %d";
23
    const EXCEPTION_NO_FILE    = self::class.": file '%s' does not exists (or path is incorrect?)";
24
    const EXCEPTION_READ_ERROR = self::class.": file '%s' failed to be loaded (permission denied ?)";
25
    const EXCEPTION_LINE_SPLIT = self::class.": content is not a string(maybe a file error?)";
26
27
    public function __construct($absolutePath = null, $options = null, $debug = 0)
28
    {
29
        $this->_debug = is_int($debug) ? min($debug, 3) : 1;
30
        if (!is_null($options)) {
31
            $this->options = $options;
0 ignored issues
show
Bug Best Practice introduced by
The property options does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
32
        }
33
        if (!is_null($absolutePath)) {
34
            $this->load($absolutePath);
35
        }
36
    }
37
38
    public function load(String $absolutePath):Loader
39
    {
40
        $this->_debug && var_dump($absolutePath);
41
        $this->filePath = $absolutePath;
42
        if (!file_exists($absolutePath)) {
43
            throw new \Exception(sprintf(self::EXCEPTION_NO_FILE, $absolutePath));
44
        }
45
        $adle = "auto_detect_line_endings";
46
        $prevADLE = ini_get($adle);
47
        !$prevADLE && ini_set($adle, "true");
48
        $content = file($absolutePath, FILE_IGNORE_NEW_LINES);
49
        !$prevADLE && ini_set($adle, "false");
50
        if (is_bool($content)) {
51
            throw new \Exception(sprintf(self::EXCEPTION_READ_ERROR, $absolutePath));
52
        }
53
        $this->_content = $content;
54
        return $this;
55
    }
56
57
    /**
58
     * Parse Yaml lines into an hierarchy of Node
59
     *
60
     * @param      string       $strContent  The Yaml string or null to parse loaded content
61
     * @throws     \Exception    if content is not available as $strContent or as $this->content (from file)
62
     * @throws     \ParseError  if any error during parsing or building
63
     *
64
     * @return     array|YamlObject      the hierarchy built an array of YamlObject or just YamlObject
65
     */
66
    public function parse($strContent = null)
67
    {
68
        $source = $this->_content;
69
        if (is_null($source)) $source = preg_split("/([^\n\r]+)/um", $strContent, 0, PREG_SPLIT_DELIM_CAPTURE);
70
        //TODO : be more permissive on $strContent values
71
        if (!is_array($source)) throw new \Exception(self::EXCEPTION_LINE_SPLIT);
72
        $previous = $root = new Node();
73
        $emptyLines = [];
74
        $specialTypes = [T::LITTERAL, T::LITTERAL_FOLDED, T::EMPTY];
75
        try {
76
            foreach ($source as $lineNb => $lineString) {
77
                $n = new Node($lineString, $lineNb + 1);//TODO: useful???-> $this->_debug && var_dump($n);
78
                $parent  = $previous;
79
                $deepest = $previous->getDeepestNode();
80
                if ($deepest->type === T::PARTIAL) {
81
                    //TODO:verify this edge case
82
                    // if ($n->type === T::KEY && $n->indent === $previous->indent) {
83
                    //     throw new \ParseError(sprintf(self::INVALID_VALUE, $lineNb), 1);
84
                    // }
85
                    $deepest->parse($deepest->value.' '.ltrim($lineString));
86
                } else {
87
                    if (in_array($n->type, $specialTypes)) {
88
                        if ($this->_onSpecialType($n, $parent, $previous, $emptyLines)) continue;
89
                    }
90
                    foreach ($emptyLines as $key => $node) {
91
                        $node->getParent()->add($node);
92
                    }
93
                    $emptyLines = [];
94
                    if ($n->indent < $previous->indent) {
95
                        $parent = $previous->getParent($n->indent);
96
                    } elseif ($n->indent === $previous->indent) {
97
                        $parent = $previous->getParent();
98
                    } elseif ($n->indent > $previous->indent) {
99
                        if ($this->_onDeepestType($n, $parent, $previous, $lineString)) continue;
100
                    }
101
                    $parent->add($n);
102
                    $previous = $n;
103
                }
104
            }
105
            if ($this->_debug === 2) {
106
                var_dump("\033[33mParsed Structure\033[0m\n", $root);
107
                exit(0);
108
            }
109
            $out = Builder::buildContent($root, $this->_debug);
110
            return $out;
111
        } catch (\ParseError $pe) {
112
            $message = $pe->getMessage()." on line ".$pe->getLine();
113
            if ($this->_options & self::EXCEPTIONS_PARSING) {
114
                var_dump($root);
115
                throw new \Exception($message, 1);
116
            }
117
            $this->errors[] = $message;
118
        } catch (\Error|\Exception $e) {
119
            throw new \Exception($e->getMessage()." for '$this->filePath'", 1);
120
        }
121
    }
122
123
    private function _onSpecialType(&$n, &$parent, &$previous, &$emptyLines):bool
124
    {
125
        $deepest = $previous->getDeepestNode();
126
        switch ($n->type) {
127
            case T::EMPTY:
128
                if ($previous->type === T::SCALAR) $emptyLines[] = $n->setParent($previous->getParent());
129
                if (in_array($deepest->type, T::$LITTERALS)) $emptyLines[] = $n->setParent($deepest);
130
                return true;
131
                break;
0 ignored issues
show
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
132
            case T::LITTERAL://fall through
133
            case T::LITTERAL_FOLDED://var_dump($deepest);exit();
134
                if ($deepest->type === T::KEY && is_null($deepest->value)) {
135
                    $deepest->add($n);
136
                    $previous = $n;
137
                    return true;
138
                }
139
            default:
140
                return false;
141
        }
142
    }
143
144
    private function _onDeepestType(&$n, &$parent, &$previous, $lineString):bool
145
    {
146
        $deepest = $previous->getDeepestNode();
147
        switch ($deepest->type) {
148
            case T::LITTERAL:
149
            case T::LITTERAL_FOLDED:
150
                $n->value = trim($lineString);//fall through
151
            case T::REF_DEF://fall through
152
            case T::SET_VALUE://fall through
153
            case T::TAG:
154
                $parent = $deepest;
155
                break;
156
            case T::EMPTY:
157
            case T::SCALAR:
158
                if ($n->type === T::SCALAR &&
159
                    !in_array($deepest->getParent()->type, T::$LITTERALS) ) {
160
                    $deepest->type = T::SCALAR;
161
                    $deepest->value .= "\n".$n->value;
162
                    return true;
163
                } else {
164
                    if (!in_array($previous->type, [T::ITEM, T::SET_KEY])) {
165
                        $parent = $deepest->getParent();
166
                    }
167
                }
168
        }
169
        return false;
170
    }
171
}
172