Completed
Push — master ( c7418d...8b4244 )
by stéphane
04:44
created

Loader::onSpecialType()   C

Complexity

Conditions 14
Paths 11

Size

Total Lines 27
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 14
eloc 19
nc 11
nop 4
dl 0
loc 27
rs 6.2666
c 0
b 0
f 0

How to fix   Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
declare(strict_types=1);
0 ignored issues
show
Coding Style introduced by
Missing file doc comment
Loading history...
3
4
namespace Dallgoot\Yaml;
5
6
use Dallgoot\Yaml\Yaml as Y;
7
8
/**
9
 * TODO
10
 *
11
 * @author  Stéphane Rebai <[email protected]>
12
 * @license Apache 2.0
13
 * @link    TODO : url to specific online doc
14
 */
0 ignored issues
show
Coding Style introduced by
Missing @category tag in class comment
Loading history...
Coding Style introduced by
Missing @package tag in class comment
Loading history...
15
final class Loader
16
{
17
    //public
18
19
    /* @var null|string */
20
    public static $error;
21
22
    public const EXCLUDE_DIRECTIVES = 1;//DONT include_directive
23
    public const IGNORE_COMMENTS    = 2;//DONT include_comments
24
    public const NO_PARSING_EXCEPTIONS = 4;//THROW Exception on parsing Errors
25
    public const NO_OBJECT_FOR_DATE = 8;//DONT import date strings as dateTime Object
26
    //privates
27
    /* @var null|string */
28
    private $content;
0 ignored issues
show
Coding Style introduced by
Private member variable "content" must be prefixed with an underscore
Loading history...
29
    /* @var null|string */
30
    private $filePath;
0 ignored issues
show
Coding Style introduced by
Private member variable "filePath" must be prefixed with an underscore
Loading history...
31
    /* @var integer */
32
    private $debug = 0;///TODO: determine levels
0 ignored issues
show
Coding Style introduced by
Private member variable "debug" must be prefixed with an underscore
Loading history...
33
    /* @var integer */
34
    private $options = 0;
0 ignored issues
show
Coding Style introduced by
Private member variable "options" must be prefixed with an underscore
Loading history...
35
    //Exceptions messages
36
    private const INVALID_VALUE        = self::class.": at line %d";
37
    private const EXCEPTION_NO_FILE    = self::class.": file '%s' does not exists (or path is incorrect?)";
38
    private const EXCEPTION_READ_ERROR = self::class.": file '%s' failed to be loaded (permission denied ?)";
39
    private const EXCEPTION_LINE_SPLIT = self::class.": content is not a string(maybe a file error?)";
40
41
    public function __construct($absolutePath = null, $options = null, $debug = 0)
0 ignored issues
show
Coding Style introduced by
Missing function doc comment
Loading history...
42
    {
43
        $this->debug   = is_int($debug) ? min($debug, 3) : 1;
44
        $this->options = is_int($options) ? $options : $this->options;
45
        if (is_string($absolutePath)) {
46
            $this->load($absolutePath);
47
        }
48
    }
49
50
    /**
51
     * Load a file and save its content as $content
52
     *
53
     * @param string $absolutePath The absolute path of a file
54
     *
55
     * @throws \Exception if file don't exist OR reading failed
56
     *
57
     * @return self  ( returns the same Loader  )
58
     */
59
    public function load(string $absolutePath):Loader
60
    {
61
        if (!file_exists($absolutePath)) {
62
            throw new \Exception(sprintf(self::EXCEPTION_NO_FILE, $absolutePath));
63
        }
64
        $this->filePath = $absolutePath;
65
        $adle = "auto_detect_line_endings";
66
        $prevADLE = ini_get($adle);
67
        !$prevADLE && ini_set($adle, "true");
68
        $content = file($absolutePath, FILE_IGNORE_NEW_LINES);
69
        !$prevADLE && ini_set($adle, "false");
70
        if (is_bool($content)) {
71
            throw new \Exception(sprintf(self::EXCEPTION_READ_ERROR, $absolutePath));
72
        }
73
        $this->content = $content;
0 ignored issues
show
Documentation Bug introduced by
It seems like $content of type array is incompatible with the declared type null|string of property $content.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
74
        return $this;
75
    }
76
77
    /**
78
     * Parse Yaml lines into a hierarchy of Node
79
     *
80
     * @param string $strContent The Yaml string or null to parse loaded content
81
     *
82
     * @throws \Exception    if content is not available as $strContent or as $this->content (from file)
83
     * @throws \ParseError  if any error during parsing or building
84
     *
85
     * @return array|YamlObject|null      null on errors if NO_PARSING_EXCEPTIONS is set, otherwise an array of YamlObject or just YamlObject
86
     */
87
    public function parse($strContent = null)
88
    {
89
        $source = $this->content ?? preg_split("/\n/m", preg_replace('/(\r\n|\r)/', "\n", $strContent), 0, PREG_SPLIT_DELIM_CAPTURE);
90
        //TODO : be more permissive on $strContent values
91
        if (!is_array($source) || !count($source)) throw new \Exception(self::EXCEPTION_LINE_SPLIT);
92
        $previous = $root = new Node();
93
        $emptyLines = [];
94
        try { //var_dump($source);
95
            $gen = function () use($source) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after USE keyword; found 0
Loading history...
96
                foreach ($source as $key => $value) {
97
                    yield ++$key => $value;
98
                }
99
            };
100
            foreach ($gen() as $lineNb => $lineString) {
101
                $n = new Node($lineString, $lineNb);
102
                $deepest = $previous->getDeepestNode();
103
                if ($n->type & (Y::LITTERALS|Y::BLANK|Y::COMMENT|Y::TAG) || $deepest->type & Y::PARTIAL) {
104
                    if ($this->onSpecialType($n, $previous, $emptyLines, $lineString)) continue;
105
                }
106
                //Note: 6.6 comments: Note that outside scalar content, a line containing only white space characters is taken to be a comment line.
107
                    foreach ($emptyLines as $blankNode) {
108
                        if ($blankNode !== $previous) {
109
                            $blankNode->getParent()->add($blankNode);
110
                        }
111
                    }
112
                $emptyLines = [];
113
                switch ($n->indent <=> $previous->indent) {
114
                    case -1: $target = $previous->getParent($n->indent);
115
                        if ($n->type & Y::ITEM) {
116
                            $target = $previous->getParent($n->indent, Y::KEY);
117
                        }
118
                        break;
119
                    case 0:
120
                        if ($n->type & Y::KEY && $n->indent === 0) {
121
                            $target = $root;
122
                        } elseif($n->type & Y::ITEM && $deepest->type & Y::KEY && is_null($deepest->value)) {
0 ignored issues
show
Coding Style introduced by
Expected "} elseif (...) \n"; found " elseif(...) {\n"
Loading history...
123
                            $target = $deepest;
124
                        } else {
125
                            $target = $previous->getParent();
126
                        }
127
                        break;
128
                    default:
129
                        $target = $previous;
130
                        if ($previous->type & Y::ITEM) {
131
                            if (($deepest->type & Y::KEY && is_null($deepest->value)) ||
132
                                ($deepest->type & Y::TAG && is_null($deepest->value))
0 ignored issues
show
Coding Style introduced by
Each line in a multi-line IF statement must begin with a boolean operator
Loading history...
133
                            ) {
134
                                $target = $deepest;
135
                            }
136
                        }
137
                }
138
                if ($this->onContextType($n, $target, $lineString)) continue;
139
                $target->add($n);
140
                $previous = $n;
141
            }
142
            if ($this->debug === 2) {
143
                print_r((new \ReflectionClass(Y::class))->getConstants());
144
                echo "\033[33mParsed Structure\033[0m\n";
145
                print_r($root);
146
                exit();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
147
            }
148
            $out = Builder::buildContent($root, $this->debug);
149
            return $out;
150
        } catch (\Error|\Exception|\ParseError $e) {
151
            $file = $this->filePath ? basename($this->filePath) : 'YAML STRING';
152
            $message = basename($e->getFile())."@".$e->getLine().": ".$e->getMessage()." for '$file' @".($lineNb)."\n";
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $lineNb seems to be defined by a foreach iteration on line 100. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
153
            if ($this->options & self::NO_PARSING_EXCEPTIONS) {
154
                // trigger_error($message, E_USER_WARNING);
155
                self::$error = $message;
156
                return null;
157
            }
158
            $this->debug && print_r($root);
159
            throw new \Exception($message, 3);
160
        }
161
    }
162
163
    private function onSpecialType(Node &$n, Node &$previous, &$emptyLines, $lineString):bool
0 ignored issues
show
Coding Style introduced by
Private method name "Loader::onSpecialType" must be prefixed with an underscore
Loading history...
Coding Style introduced by
Missing function doc comment
Loading history...
164
    {
165
        $deepest = $previous->getDeepestNode();
166
        if ($deepest->type & Y::PARTIAL) {
167
            $add = trim($lineString) === '' ? "\n" : trim($lineString);
168
            if ($add !== "\n" && $deepest->value[-1] !== "\n") {
169
                $add = ' '.$add;
170
            }
171
            $deepest->parse($deepest->value.$add);
172
            return true;
173
        }
174
        if ($n->type & Y::BLANK) {
175
            if ($previous->type & Y::SCALAR) $emptyLines[] = $n->setParent($previous->getParent());
176
            if ($deepest->type & Y::LITTERALS) $emptyLines[] = $n->setParent($deepest);
177
            return true;
178
        }
179
        //comment is fullline : forces 'root' as parent IF NOT inside a LITTERAL
180
        if ($n->type & Y::COMMENT &&
181
            !($previous->getParent()->value->type & Y::LITTERALS) &&
0 ignored issues
show
Coding Style introduced by
Each line in a multi-line IF statement must begin with a boolean operator
Loading history...
182
            !($deepest->type & Y::LITTERALS)) {
0 ignored issues
show
Coding Style introduced by
Each line in a multi-line IF statement must begin with a boolean operator
Loading history...
Coding Style introduced by
Closing parenthesis of a multi-line IF statement must be on a new line
Loading history...
183
                $previous->getParent(0)->add($n);
184
                return true;
185
        }
186
        if($n->type & Y::TAG && is_null($n->value) && $previous->type & (Y::ROOT|Y::DOC_START|Y::DOC_END)) {
0 ignored issues
show
Coding Style introduced by
Expected "if (...) {\n"; found "if(...) {\n"
Loading history...
187
            $n->value = '';
188
        }
189
        return false;
190
    }
191
192
    private function onContextType(Node &$n, Node &$previous, $lineString):bool
0 ignored issues
show
Coding Style introduced by
Private method name "Loader::onContextType" must be prefixed with an underscore
Loading history...
Coding Style introduced by
Missing function doc comment
Loading history...
193
    {
194
        $deepest = $previous->getDeepestNode();
195
        if (($previous->type & Y::LITTERALS && $n->indent >= $previous->indent) || (($deepest->type & Y::LITTERALS) && is_null($deepest->value))) {
196
            $n->type = Y::SCALAR;
197
            $n->identifier = null;
198
            $n->value = trim($lineString);
199
            $previous = $deepest->getParent();
200
            return false;
201
        }
202
        // var_dump(Y::getName($n->type).Y::getName($deepest->type).Y::getName($previous->type));
203
        if ((($deepest->type & (Y::LITTERALS|Y::REF_DEF|Y::SET_VALUE)) &&
204
            is_null($deepest->value)) //&&
0 ignored issues
show
Coding Style introduced by
Each line in a multi-line IF statement must begin with a boolean operator
Loading history...
205
            /*!($previous->type & Y::ROOT)*/ ||
206
            ($deepest->type & Y::TAG && is_null($deepest->value)) ) {
0 ignored issues
show
Coding Style introduced by
Each line in a multi-line IF statement must begin with a boolean operator
Loading history...
Coding Style introduced by
Closing parenthesis of a multi-line IF statement must be on a new line
Loading history...
207
            // var_dump(Y::getName($previous->type));
208
            $previous = $deepest;
209
        }
210
        if ($n->type & Y::SCALAR && $previous->type & Y::SCALAR) {
211
            $previous = $previous->getParent();
212
        }
213
        return false;
214
    }
215
}
216