Completed
Push — master ( 23b4c3...cb8b18 )
by stéphane
02:37
created

Loader   D

Complexity

Total Complexity 58

Size/Duplication

Total Lines 209
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 114
dl 0
loc 209
rs 4.5599
c 0
b 0
f 0
wmc 58

10 Methods

Rating   Name   Duplication   Size   Complexity  
A load() 0 16 5
A __construct() 0 6 4
A getSourceIterator() 0 8 4
B parse() 0 43 11
A attachBlankLines() 0 5 3
A onEqualIndent() 0 9 6
A onMoreIndent() 0 6 4
B onSpecialType() 0 23 11
A onSpecialBlank() 0 4 3
B onContextType() 0 17 7

How to fix   Complexity   

Complex Class

Complex classes like Loader often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Loader, and based on these observations, apply Extract Interface, too.

1
<?php
2
declare(strict_types=1);
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
 */
15
final class Loader
16
{
17
    //public
18
    /* @var null|string */
19
    public static $error;
20
    public const IGNORE_DIRECTIVES = 1;//DONT include_directive
21
    public const IGNORE_COMMENTS    = 2;//DONT include_comments
22
    public const NO_PARSING_EXCEPTIONS = 4;//DONT throw Exception on parsing errors
23
    public const NO_OBJECT_FOR_DATE = 8;//DONT import date strings as dateTime Object
24
25
    //privates
26
    /* @var null|false|array */
27
    private $content;
28
    /* @var null|string */
29
    private $filePath;
30
    /* @var integer */
31
    private $debug = 0;///TODO: determine levels
32
    /* @var integer */
33
    private $options = 0;
34
    //Exceptions messages
35
    private const INVALID_VALUE        = self::class.": at line %d";
36
    private const EXCEPTION_NO_FILE    = self::class.": file '%s' does not exists (or path is incorrect?)";
37
    private const EXCEPTION_READ_ERROR = self::class.": file '%s' failed to be loaded (permission denied ?)";
38
    private const EXCEPTION_LINE_SPLIT = self::class.": content is not a string(maybe a file error?)";
39
40
    public function __construct($absolutePath = null, $options = null, $debug = 0)
41
    {
42
        $this->debug   = is_int($debug) ? min($debug, 3) : 1;
43
        $this->options = is_int($options) ? $options : $this->options;
44
        if (is_string($absolutePath)) {
45
            $this->load($absolutePath);
46
        }
47
    }
48
49
    /**
50
     * Load a file and save its content as $content
51
     *
52
     * @param string $absolutePath The absolute path of a file
53
     *
54
     * @throws \Exception if file don't exist OR reading failed
55
     *
56
     * @return self  ( returns the same Loader  )
57
     */
58
    public function load(string $absolutePath):Loader
59
    {
60
        if (!file_exists($absolutePath)) {
61
            throw new \Exception(sprintf(self::EXCEPTION_NO_FILE, $absolutePath));
62
        }
63
        $this->filePath = $absolutePath;
64
        $adle = "auto_detect_line_endings";
65
        $prevADLE = ini_get($adle);
66
        !$prevADLE && ini_set($adle, "true");
67
        $content = file($absolutePath, FILE_IGNORE_NEW_LINES);
68
        !$prevADLE && ini_set($adle, "false");
69
        if (is_bool($content)) {
70
            throw new \Exception(sprintf(self::EXCEPTION_READ_ERROR, $absolutePath));
71
        }
72
        $this->content = $content;
73
        return $this;
74
    }
75
76
    private function getSourceIterator($strContent = null)
77
    {
78
        $source = $this->content ?? preg_split("/\n/m", preg_replace('/(\r\n|\r)/', "\n", $strContent), 0, PREG_SPLIT_DELIM_CAPTURE);
79
        //TODO : be more permissive on $strContent values
80
        if (!is_array($source) || !count($source)) throw new \Exception(self::EXCEPTION_LINE_SPLIT);
81
        return function () use($source) {
82
            foreach ($source as $key => $value) {
83
                yield ++$key => $value;
84
            }
85
        };
86
    }
87
88
    /**
89
     * Parse Yaml lines into a hierarchy of Node
90
     *
91
     * @param string $strContent The Yaml string or null to parse loaded content
92
     *
93
     * @throws \Exception    if content is not available as $strContent or as $this->content (from file)
94
     * @throws \ParseError  if any error during parsing or building
95
     *
96
     * @return array|YamlObject|null      null on errors if NO_PARSING_EXCEPTIONS is set, otherwise an array of YamlObject or just YamlObject
97
     */
98
    public function parse($strContent = null)
99
    {
100
        $sourceIterator = $this->getSourceIterator($strContent);
101
        $root = new Node();
102
        $root->value = new NodeList;
103
        $previous = $root;
104
        $emptyLines = [];
105
        try {
106
            foreach ($sourceIterator() as $lineNb => $lineString) {
107
                $n = new Node($lineString, $lineNb);
108
                if ($this->onSpecialType($n, $previous, $emptyLines, $lineString)) continue;
109
                $this->attachBlankLines($emptyLines, $previous);
110
                $emptyLines = [];
111
                $target = $previous;
112
                switch ($n->indent <=> $previous->indent) {
113
                    case -1:
114
                        $target = $previous->getParent($n->indent);break;//, $n->type & Y::ITEM ? Y::KEY : null);
115
                    case 0:
116
                        $this->onEqualIndent($n, $previous, $target);break;
117
                    default:
118
                        $this->onMoreIndent($previous, $target);
119
                }
120
                if ($this->onContextType($n, $target, $lineString)) continue;
121
                $target->add($n);
122
                $previous = $n;
123
            }
124
            if ($this->debug === 2) {
125
                print_r((new \ReflectionClass(Y::class))->getConstants());
126
                print_r($root);
127
            } else {
128
                $out = Builder::buildContent($root, $this->debug);
129
                return $out;
130
            }
131
        } catch (\Error|\Exception|\ParseError $e) {
132
            $file = $this->filePath ? basename($this->filePath) : '#YAML STRING#';
133
            $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 106. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
134
            if ($this->options & self::NO_PARSING_EXCEPTIONS) {
135
                // trigger_error($message, E_USER_WARNING);
136
                self::$error = $message;
137
                return null;
138
            }
139
            $this->debug && print_r($root);
140
            throw new \Exception($message, 3);
141
        }
142
    }
143
144
    public function onMoreIndent(Node &$previous, Node &$target)
145
    {
146
        $deepest = $previous->getDeepestNode();
147
        if ($previous->type & Y::ITEM) {
148
            if ($deepest->type & (Y::KEY|Y::TAG) && is_null($deepest->value)) {
149
                $target = $deepest;
150
            }
151
        }
152
    }
153
154
155
    private function onEqualIndent(Node &$n, Node &$previous, Node &$target)
156
    {
157
        $deepest = $previous->getDeepestNode();
158
        if ($n->type & Y::KEY && $n->indent === 0) {
159
            $target = $previous->getParent(-1);//get root
160
        } elseif ($n->type & Y::ITEM && $deepest->type & Y::KEY && is_null($deepest->value)) {
161
            $target = $deepest;
162
        } else {
163
            $target = $previous->getParent();
164
        }
165
    }
166
167
    public function attachBlankLines(array &$emptyLines, Node &$previous)
168
    {
169
        foreach ($emptyLines as $blankNode) {
170
            if ($blankNode !== $previous) {
171
                $blankNode->getParent()->add($blankNode);
172
            }
173
        }
174
    }
175
176
    private function onSpecialType(Node &$n, Node &$previous, &$emptyLines, $lineString):bool
177
    {
178
        $deepest = $previous->getDeepestNode();
179
        if ($deepest->type & Y::PARTIAL) {
180
            $add = empty($lineString) ? "\n" : ltrim($lineString);
181
            if ($add !== "\n" && $deepest->value[-1] !== "\n") {
182
                $add = ' '.$add;
183
            }
184
            $deepest->parse($deepest->value.$add);
185
            return true;
186
        }
187
        if ($n->type & Y::BLANK) {
188
            $this->onSpecialBlank($emptyLines, $n, $previous, $deepest);
189
            return true;
190
        } elseif ($n->type & Y::COMMENT
191
                  && !($previous->getParent()->value->type & Y::LITTERALS)
192
                  && !($deepest->type & Y::LITTERALS)) {
193
            // $previous->getParent(0)->add($n);
194
            return true;
195
        } elseif ($n->type & Y::TAG && is_null($n->value) ){//&& $previous->type & (Y::ROOT|Y::DOC_START|Y::DOC_END)) {
196
            $n->value = '';
197
        }
198
        return false;
199
    }
200
201
    private function onSpecialBlank(array &$emptyLines, Node $n, Node $previous, Node $deepest)
202
    {
203
        if ($previous->type & Y::SCALAR)   $emptyLines[] = $n->setParent($previous->getParent());
204
        if ($deepest->type & Y::LITTERALS) $emptyLines[] = $n->setParent($deepest);
205
    }
206
207
    private function onContextType(Node &$n, Node &$previous, $lineString):bool
208
    {
209
        $deepest = $previous->getDeepestNode();
210
        if (($previous->type & Y::LITTERALS && $n->indent >= $previous->indent) || (($deepest->type & Y::LITTERALS) && is_null($deepest->value))) {
211
            $n->type = Y::SCALAR;
212
            $n->identifier = null;
213
            $n->value = trim($lineString);
214
            $previous = $deepest->getParent();
215
            return false;
216
        }
217
        if (is_null($deepest->value) && $deepest->type & (Y::LITTERALS|Y::REF_DEF|Y::SET_VALUE|Y::TAG)) {
218
            $previous = $deepest;
219
        }
220
        // if ($n->type & Y::SCALAR && $previous->type & Y::SCALAR) {
221
        //     $previous = $previous->getParent();
222
        // }
223
        return false;
224
    }
225
}
226