Completed
Push — master ( c45521...844759 )
by stéphane
08:22
created

Tag   A

Complexity

Total Complexity 34

Size/Duplication

Total Lines 257
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 68
dl 0
loc 257
rs 9.68
c 0
b 0
f 0
wmc 34

13 Methods

Rating   Name   Duplication   Size   Complexity  
A binaryHandler() 0 10 3
A inlineHandler() 0 3 1
A __construct() 0 8 3
A checkHandlerArgument() 0 4 3
A registerLegacyTags() 0 10 3
A strHandler() 0 5 3
A setHandler() 0 5 2
A mapHandler() 0 5 2
A symfonyPHPobjectHandler() 0 11 4
A addTagHandler() 0 6 2
A isKnown() 0 3 1
A build() 0 13 6
A longHandler() 0 3 1
1
<?php
2
namespace Dallgoot\Yaml;
3
4
use \ReflectionMethod as RM;
5
/**
6
 * TODO
7
 *
8
 * @author  Stéphane Rebai <[email protected]>
9
 * @license Apache 2.0
10
 * @link    TODO : url to specific online doc
11
 */
12
class Tag
13
{
14
    /** @var string */
15
    public $tagName;
16
    /** @var Node|null|string */
17
    public $value;
18
    /** @var null|Node|NodeList */
19
    private $raw;
20
21
    private const NO_NAME = '%s Error: a tag MUST have a name';
22
    private const WRONG_VALUE = "Error : cannot transform tag '%s' for type '%s'";
23
    private const LEGACY_TAGS_HANDLERS = ['!str'       => 'strHandler',
24
                                          '!binary'    => 'binaryHandler',
25
                                          '!set'       => 'setHandler',
26
                                          '!omap'      => 'mapHandler',
27
                                          'php/object' => 'symfonyPHPobjectHandler',
28
                                          '!inline'    => 'inlineHandler',
29
                                          '!long'      => 'longHandler'];
30
31
    public static $registeredHandlers = [];
32
33
    /**
34
     * Tag constructor.
35
     *
36
     * @param string $tagName the name of the tag like '!!str' (WITHOUT the first "!")
37
     * @param mixed  $raw     any PHP variable type
38
     *
39
     * @throws \Exception if $tagName is an invalid string or absent
40
     */
41
    public function __construct(string $tagName, $raw)
42
    {
43
        if (empty($tagName)) {
44
            throw new \Exception(sprintf(self::NO_NAME, __METHOD__));
45
        }
46
        $this->tagName = $tagName;
47
        $this->raw = $raw;
48
        if (empty(self::$registeredHandlers)) $this->registerLegacyTags();
49
    }
50
51
    /**
52
     * Add Handlers for legacy Yaml tags
53
     *
54
     * @see self::LEGACY_TAGS_HANDLERS
55
     */
56
    private function registerLegacyTags()
57
    {
58
        $reflectAPI = new \ReflectionClass(self::class);
59
        $methodsList = [];
60
        $list = $reflectAPI->getMethods(RM::IS_FINAL | RM::IS_STATIC & RM::IS_PRIVATE);
61
        foreach ($list as $method) {
62
            $methodsList[$method->name] = $method->getClosure($this);
63
        }
64
        foreach (self::LEGACY_TAGS_HANDLERS as $tagName => $methodName) {
65
            self::$registeredHandlers[$tagName] = $methodsList[$methodName];
66
        }
67
    }
68
69
    /**
70
     * Checked that handler argument is either Node or NodeList
71
     *
72
     * @param string $tagName  The tag name
73
     * @param $node  $node     The candidate Node|NodeList which will be processed by handler
0 ignored issues
show
Documentation Bug introduced by
The doc comment $node at position 0 could not be parsed: Unknown type name '$node' at position 0 in $node.
Loading history...
74
     *
75
     * @throws \Exception  if $node is not Node or NodeList
76
     * @return null
77
     */
78
    private function checkHandlerArgument($tagName, $node)
79
    {
80
        if (!($node instanceof Node) && !($node instanceof NodeList) ) {
81
            throw new \Exception(sprintf(self::WRONG_VALUE, $tagName, gettype($node)));
82
        }
83
    }
84
85
    /**
86
     * Specific Handler for Symfony custom tag : 'php/object'
87
     *
88
     * @param object             $node   The node
89
     * @param object|array|null  $parent The parent
90
     *
91
     * @throws Exception if unserialize fails OR if its a NodeList (no support of multiple values for this tag)
92
     * @return object    the unserialized object according to Node value
93
     */
94
    private final static function symfonyPHPobjectHandler(object $node, &$parent = null)
0 ignored issues
show
Unused Code introduced by
The method symfonyPHPobjectHandler() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
Unused Code introduced by
The parameter $parent is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

94
    private final static function symfonyPHPobjectHandler(object $node, /** @scrutinizer ignore-unused */ &$parent = null)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
95
    {
96
        if ($node instanceof NodeScalar) {
97
            $phpObject = unserialize($node->value);
0 ignored issues
show
Bug introduced by
It seems like $node->value can also be of type Dallgoot\Yaml\Node and Dallgoot\Yaml\NodeList; however, parameter $str of unserialize() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

97
            $phpObject = unserialize(/** @scrutinizer ignore-type */ $node->value);
Loading history...
98
            // NOTE : we assume this is only used for Object types (if a boolean false is serialized this will FAIL)
99
            if (is_bool($phpObject)) {
100
                throw new \Exception("value for tag 'php/object' could NOT be unserialized");
101
            }
102
            return $phpObject;
103
        } elseif ($node instanceof NodeList) {
104
            throw new \Exception("tag 'php/object' can NOT be a NodeList");
105
        }
106
    }
107
    /**
108
     * Specific handler for 'inline' tag
109
     *
110
     * @param object $node
111
     * @param object|array|null  $parent The parent
112
     *
113
     * @todo implements
114
     */
115
    private final static function inlineHandler(object $node, object &$parent = null)
0 ignored issues
show
Unused Code introduced by
The method inlineHandler() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
116
    {
117
        return self::strHandler($node, $parent);
118
    }
119
120
121
    /**
122
     * Specific handler for 'long' tag
123
     *
124
     * @param object $node
125
     * @param object|array|null  $parent The parent
126
     *
127
     * @todo implements
128
     */
129
    private final static function longHandler(object $node, object &$parent = null)
0 ignored issues
show
Unused Code introduced by
The method longHandler() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
130
    {
131
        return self::strHandler($node, $parent);
132
    }
133
134
135
    /**
136
     * Specific Handler for 'str' tag
137
     *
138
     * @param object $node    The Node or NodeList
139
     * @param object|array|null  $parent The parent
140
     *
141
     * @return string the value of Node converted to string if needed
142
     */
143
    private final static function strHandler(object $node, object &$parent = null)
144
    {
145
        if ($node instanceof Node) {
146
            if ($node instanceof NodeKey) $node->build($parent);
147
            return ltrim($node->raw);
148
        // } elseif ($node instanceof NodeList) {
149
        //     return Builder::buildLitteral($node);
150
        }
151
    }
152
153
    /**
154
     * Specific Handler for 'binary' tag
155
     *
156
     * @param object $node   The node or NodeList
157
     * @param object|array|null  $parent The parent
158
     *
159
     * @return string  The value considered as 'binary' Note: the difference with strHandler is that multiline have not separation
160
     */
161
    private final static function binaryHandler(object $node, Node &$parent = null)
0 ignored issues
show
Unused Code introduced by
The parameter $parent is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

161
    private final static function binaryHandler(object $node, /** @scrutinizer ignore-unused */ Node &$parent = null)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The method binaryHandler() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
162
    {
163
        if ($node instanceof Node) {
164
            $out = '';
165
            $cursor = $node->value;
166
            while ($cursor instanceof Node) {
167
                $out .= $cursor->value;
168
                $cursor = $cursor->value;
169
            }
170
            return ltrim($out);
171
        // } elseif ($node instanceof NodeList) {
172
        //     return Builder::buildLitteral($node, Y::RAW);
173
        }
174
    }
175
176
    /**
177
     * Specific Handler for the '!set' tag
178
     *
179
     * @param      object     $node    The node
180
     * @param object|array|null  $parent The parent
181
     *
182
     * @throws     \Exception  if theres a set but no children (set keys or set values)
183
     * @return     YamlObject|object  process the Set, ie. an object construction with properties as serialized JSON values
184
     */
185
    private final static function setHandler(object $node, Node &$parent = null)
0 ignored issues
show
Unused Code introduced by
The parameter $parent is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

185
    private final static function setHandler(object $node, /** @scrutinizer ignore-unused */ Node &$parent = null)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The method setHandler() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
186
    {
187
        if (!($node instanceof NodeList)) {
188
            throw new \Exception("tag '!!set' can NOT be a single Node");
189
        } else {
190
            // if ($parent instanceof YamlObject) {
191
            //     Builder::buildNodeList($node, $parent);
192
            // } else {
193
            //     return Builder::buildNodeList($node, $parent);
194
            // }
195
        }
196
    }
197
198
    /**
199
     * Specifi Handler for the 'omap' tag
200
     *
201
     * @param object $node   The node
202
     * @param object|array|null  $parent The parent
203
     *
204
     * @throws \Exception  if theres an omap but no map items
205
     * @return YamlObject|array process the omap
206
     */
207
    private final static function mapHandler(object $node, Node &$parent = null)
0 ignored issues
show
Unused Code introduced by
The method mapHandler() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
Unused Code introduced by
The parameter $parent is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

207
    private final static function mapHandler(object $node, /** @scrutinizer ignore-unused */ Node &$parent = null)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
208
    {
209
        if (!($node instanceof NodeList)) {
210
            throw new \Exception("tag '!!omap' can NOT be a single Node");
211
        } else {
212
            // $node->type = Y::SEQUENCE;
213
            // if ($parent instanceof YamlObject) {
214
            //     Builder::buildNodeList($node, $parent);
215
            // } else {
216
            //     return Builder::buildNodeList($node, $parent);
217
            // }
218
        }
219
    }
220
221
    /**
222
     * Return the tagged value according to Tag type
223
     *
224
     * @param mixed $parent The parent (object|array) IF any.
225
     *
226
     * @return mixed
227
     */
228
    public function build(&$parent = null)
229
    {
230
        if (is_scalar($this->raw) || is_null($this->raw)) {
231
            $this->value = $this->raw;
232
        } elseif ($this->isKnown()) {
233
            $this->checkHandlerArgument($this->tagName, $this->raw);
234
            $this->value = self::$registeredHandlers[$this->tagName]($this->raw, $parent);
235
        } elseif ($this->value instanceof NodeKey) {
236
            $this->value->build($parent);
237
        } elseif ($this->value instanceof NodeItem) {
238
            $this->value->build($parent);
239
        } else {
240
            $this->value->build($this->raw, $parent);
0 ignored issues
show
Unused Code introduced by
The call to Dallgoot\Yaml\Node::build() has too many arguments starting with $parent. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

240
            $this->value->/** @scrutinizer ignore-call */ 
241
                          build($this->raw, $parent);

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. Please note the @ignore annotation hint above.

Loading history...
Bug introduced by
The method build() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

240
            $this->value->/** @scrutinizer ignore-call */ 
241
                          build($this->raw, $parent);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
241
        }
242
    }
243
244
    /**
245
     * Determines if current is known : either YAML legacy or user added
246
     *
247
     * @return     boolean  True if known, False otherwise.
248
     */
249
    public function isKnown():bool
250
    {
251
        return in_array($this->tagName, array_keys(self::$registeredHandlers));
252
    }
253
254
    /**
255
     * Allow the user to add a custome tag handler.
256
     * Note: That allows to replace handlers for legacy tags also.
257
     *
258
     * @param      string      $name   The name
259
     * @param      Closure     $func   The function
0 ignored issues
show
Bug introduced by
The type Dallgoot\Yaml\Closure was not found. Did you mean Closure? If so, make sure to prefix the type with \.
Loading history...
260
     *
261
     * @throws     \Exception  Can NOT add handler without a name for the tag
262
     */
263
    public static function addTagHandler(string $name, \Closure $func)
264
    {
265
        if (empty($name)) {
266
            throw new \Exception(sprintf(self::NO_NAME, __METHOD__));
267
        }
268
        self::$registeredHandlers[$name] = $func;
269
    }
270
271
272
273
    /**
274
     * Should verify if the tag is correct
275
     *
276
     * @param string $providedName The provided name
277
     * @todo  is this required ???
278
     */
279
    // private function checkNameValidity(string $providedName)
280
    // {
281
        /* TODO  implement and throw Exception if invalid (setName method ???)
282
         *The suffix must not contain any “!” character. This would cause the tag shorthand to be interpreted as having a named tag handle. In addition, the suffix must not contain the “[”, “]”, “{”, “}” and “,” characters. These characters would cause ambiguity with flow collection structures. If the suffix needs to specify any of the above restricted characters, they must be escaped using the “%” character. This behavior is consistent with the URI character escaping rules (specifically, section 2.3 of RFC2396).
283
        */
284
    // }
285
}
286