These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | /** |
||
3 | * Load a DOM document from a json string or file |
||
4 | * |
||
5 | * @license http://www.opensource.org/licenses/mit-license.php The MIT License |
||
6 | * @copyright Copyright (c) 2009-2014 Bastian Feder, Thomas Weinert |
||
7 | */ |
||
8 | |||
9 | namespace FluentDOM\Loader\Json { |
||
10 | |||
11 | use FluentDOM\Document; |
||
12 | use FluentDOM\Element; |
||
13 | use FluentDOM\Loadable; |
||
14 | use FluentDOM\Loader\Result; |
||
15 | use FluentDOM\QualifiedName; |
||
16 | use FluentDOM\Loader\Supports\Json as SupportsJson; |
||
17 | |||
18 | /** |
||
19 | * Load a DOM document from a json string or file |
||
20 | */ |
||
21 | class JsonDOM implements Loadable { |
||
22 | |||
23 | use SupportsJson; |
||
24 | |||
25 | const ON_MAP_KEY = 'onMapKey'; |
||
26 | |||
27 | const XMLNS = 'urn:carica-json-dom.2013'; |
||
28 | const DEFAULT_QNAME = '_'; |
||
29 | |||
30 | const OPTION_VERBOSE = 1; |
||
31 | |||
32 | const TYPE_NULL = 'null'; |
||
33 | const TYPE_BOOLEAN = 'boolean'; |
||
34 | const TYPE_NUMBER = 'number'; |
||
35 | const TYPE_STRING = 'string'; |
||
36 | const TYPE_OBJECT = 'object'; |
||
37 | const TYPE_ARRAY = 'array'; |
||
38 | |||
39 | /** |
||
40 | * Maximum recursions |
||
41 | * |
||
42 | * @var int |
||
43 | */ |
||
44 | private $_recursions = 100; |
||
45 | |||
46 | /** |
||
47 | * Add json:type and json:name attributes to all elements, even if not necessary. |
||
48 | * |
||
49 | * @var bool |
||
50 | */ |
||
51 | private $_verbose = FALSE; |
||
52 | |||
53 | /** |
||
54 | * Called to map key names tag names |
||
55 | * @var null|callable |
||
56 | */ |
||
57 | private $_onMapKey = NULL; |
||
58 | |||
59 | /** |
||
60 | * Create the loader for a json string. |
||
61 | * |
||
62 | * The string will be decoded into a php variable structure and convert into a DOM document |
||
63 | * If options contains is self::OPTION_VERBOSE, the DOMNodes will all have |
||
64 | * json:type and json:name attributes. Even if the information could be read from the structure. |
||
65 | * |
||
66 | * @param int $options |
||
67 | * @param int $depth |
||
68 | */ |
||
69 | 16 | public function __construct($options = 0, $depth = 100) { |
|
70 | 16 | $this->_recursions = (int)$depth; |
|
71 | 16 | $this->_verbose = ($options & self::OPTION_VERBOSE) == self::OPTION_VERBOSE; |
|
72 | 16 | } |
|
73 | |||
74 | /** |
||
75 | * @return string[] |
||
76 | */ |
||
77 | 16 | public function getSupported() { |
|
78 | 16 | return ['json', 'application/json', 'text/json']; |
|
79 | } |
||
80 | |||
81 | |||
82 | /** |
||
83 | * Load the json string into an DOMDocument |
||
84 | * |
||
85 | * @param mixed $source |
||
86 | * @param string $contentType |
||
87 | * @param array|\Traversable|Options $options |
||
88 | * @return Document|Result|NULL |
||
89 | */ |
||
90 | 12 | public function load($source, $contentType, $options = []) { |
|
91 | 12 | if (FALSE !== ($json = $this->getJson($source, $contentType, $options))) { |
|
0 ignored issues
–
show
|
|||
92 | 9 | $dom = new Document('1.0', 'UTF-8'); |
|
93 | 9 | $dom->appendChild( |
|
94 | 9 | $root = $dom->createElementNS(self::XMLNS, 'json:json') |
|
95 | 9 | ); |
|
96 | 9 | $onMapKey = $this->_onMapKey; |
|
97 | 9 | View Code Duplication | if (isset($options[self::ON_MAP_KEY]) && is_callable($options[self::ON_MAP_KEY])) { |
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository.
Loading history...
|
|||
98 | 1 | $this->onMapKey($options[self::ON_MAP_KEY]); |
|
99 | 1 | } |
|
100 | 9 | $this->transferTo($root, $json, $this->_recursions); |
|
101 | 9 | $this->_onMapKey = $onMapKey; |
|
102 | 9 | return $dom; |
|
103 | } |
||
104 | 1 | return NULL; |
|
105 | } |
||
106 | |||
107 | /** |
||
108 | * @param string $source |
||
109 | * @param string $contentType |
||
110 | * @param array|\Traversable|Options $options |
||
111 | * @return \FluentDOM\DocumentFragment|null |
||
112 | */ |
||
113 | 2 | public function loadFragment($source, $contentType, $options = []) { |
|
114 | 2 | if ($this->supports($contentType)) { |
|
115 | 1 | $dom = new Document('1.0', 'UTF-8'); |
|
116 | 1 | $fragment = $dom->createDocumentFragment(); |
|
117 | 1 | $onMapKey = $this->_onMapKey; |
|
118 | 1 | View Code Duplication | if (isset($options[self::ON_MAP_KEY]) && is_callable($options[self::ON_MAP_KEY])) { |
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository.
Loading history...
|
|||
119 | 1 | $this->onMapKey($options[self::ON_MAP_KEY]); |
|
120 | 1 | } |
|
121 | 1 | $this->transferTo($fragment, json_decode($source), $this->_recursions); |
|
122 | 1 | $this->_onMapKey = $onMapKey; |
|
123 | 1 | return $fragment; |
|
124 | } |
||
125 | 1 | return NULL; |
|
126 | } |
||
127 | |||
128 | /** |
||
129 | * Get/Set a mapping callback for the tag names. If it is a callable |
||
130 | * it will be set. FALSE removes the callback. |
||
131 | * |
||
132 | * function callback(string $key, boolean $isArrayElement) {} |
||
133 | * |
||
134 | * @param NULL|FALSE|callable $callback |
||
135 | * @return callable|null |
||
136 | */ |
||
137 | 9 | public function onMapKey($callback = NULL) { |
|
138 | 9 | if (isset($callback)) { |
|
139 | 2 | $this->_onMapKey = is_callable($callback) ? $callback : NULL; |
|
140 | 2 | } |
|
141 | 9 | return $this->_onMapKey; |
|
142 | } |
||
143 | |||
144 | /** |
||
145 | * Transfer a value into a target xml element node. This sets attributes on the |
||
146 | * target node and creates child elements for object and array values. |
||
147 | * |
||
148 | * If the current element is an object or array the method is called recursive. |
||
149 | * The $recursions parameter is used to limit the recursion depth of this function. |
||
150 | * |
||
151 | * @param \DOMElement|\DOMNode $target |
||
152 | * @param mixed $value |
||
153 | * @param int $recursions |
||
154 | */ |
||
155 | 10 | protected function transferTo(\DOMNode $target, $value, $recursions = 100) { |
|
156 | 10 | if ($recursions < 1) { |
|
157 | 1 | return; |
|
158 | 10 | } elseif ($target instanceof \DOMElement || $target instanceOf \DOMDocumentFragment) { |
|
159 | 10 | $type = $this->getTypeFromValue($value); |
|
160 | switch ($type) { |
||
161 | 10 | case self::TYPE_ARRAY : |
|
162 | 4 | $this->transferArrayTo($target, $value, $this->_recursions - 1); |
|
163 | 4 | break; |
|
164 | 9 | case self::TYPE_OBJECT : |
|
165 | 9 | $this->transferObjectTo($target, $value, $this->_recursions - 1); |
|
166 | 9 | break; |
|
167 | 8 | default : |
|
168 | 8 | if ($this->_verbose || $type != self::TYPE_STRING) { |
|
169 | 4 | $target->setAttributeNS(self::XMLNS, 'json:type', $type); |
|
0 ignored issues
–
show
The method
setAttributeNS does only exist in DOMElement , but not in DOMDocumentFragment .
It seems like the method you are trying to call exists only in some of the possible types. Let’s take a look at an example: class A
{
public function foo() { }
}
class B extends A
{
public function bar() { }
}
/**
* @param A|B $x
*/
function someFunction($x)
{
$x->foo(); // This call is fine as the method exists in A and B.
$x->bar(); // This method only exists in B and might cause an error.
}
Available Fixes
Loading history...
|
|||
170 | 4 | } |
|
171 | 8 | $string = $this->getValueAsString($type, $value); |
|
172 | 8 | if (is_string($string)) { |
|
173 | 8 | $target->appendChild($target->ownerDocument->createTextNode($string)); |
|
174 | 8 | } |
|
175 | 8 | } |
|
176 | 10 | } |
|
177 | 10 | } |
|
178 | |||
179 | /** |
||
180 | * Get the type from a variable value. |
||
181 | * |
||
182 | * @param mixed $value |
||
183 | * @return string |
||
184 | */ |
||
185 | 10 | public function getTypeFromValue($value) { |
|
186 | 10 | if (is_array($value)) { |
|
187 | 5 | if (empty($value) || array_keys($value) === range(0, count($value) - 1)) { |
|
188 | 4 | return self::TYPE_ARRAY; |
|
189 | } |
||
190 | 2 | return self::TYPE_OBJECT; |
|
191 | 9 | } elseif (is_object($value)) { |
|
192 | 7 | return self::TYPE_OBJECT; |
|
193 | 8 | } elseif (NULL === $value) { |
|
194 | 1 | return self::TYPE_NULL; |
|
195 | 8 | } elseif (is_bool($value)) { |
|
196 | 1 | return self::TYPE_BOOLEAN; |
|
197 | 8 | } elseif (is_int($value) || is_float($value)) { |
|
198 | 3 | return self::TYPE_NUMBER; |
|
199 | } else { |
||
200 | 6 | return self::TYPE_STRING; |
|
201 | } |
||
202 | } |
||
203 | |||
204 | /** |
||
205 | * Get a valid qualified name (tag name) using the property name/key. |
||
206 | * |
||
207 | * @param string $key |
||
208 | * @param string $default |
||
209 | * @param bool $isArrayElement |
||
210 | * @return string |
||
211 | */ |
||
212 | 9 | private function getQualifiedName($key, $default, $isArrayElement = FALSE) { |
|
213 | 9 | if ($callback = $this->onMapKey()) { |
|
214 | 2 | $key = $callback($key, $isArrayElement); |
|
215 | 9 | } elseif ($isArrayElement) { |
|
216 | 1 | $key = $default; |
|
217 | 1 | } |
|
218 | 9 | return QualifiedName::normalizeString($key, self::DEFAULT_QNAME); |
|
219 | } |
||
220 | |||
221 | /** |
||
222 | * @param string $type |
||
223 | * @param mixed $value |
||
224 | * @return null|string |
||
225 | */ |
||
226 | 8 | public function getValueAsString($type, $value) { |
|
227 | switch ($type) { |
||
228 | 8 | case self::TYPE_NULL : |
|
229 | 1 | return NULL; |
|
230 | 8 | case self::TYPE_BOOLEAN : |
|
231 | 1 | return $value ? 'true' : 'false'; |
|
232 | 8 | default : |
|
233 | 8 | return (string)$value; |
|
234 | 8 | } |
|
235 | } |
||
236 | |||
237 | /** |
||
238 | * Transfer an array value into a target element node. Sets the json:type attribute to 'array' and |
||
239 | * creates child element nodes for each array element using the default QName. |
||
240 | * |
||
241 | * @param \DOMNode|\DOMElement|\DOMDocumentFragment $target |
||
242 | * @param array $value |
||
243 | * @param int $recursions |
||
244 | */ |
||
245 | 4 | private function transferArrayTo(\DOMNode $target, array $value, $recursions) { |
|
246 | 4 | $parentName = ''; |
|
247 | 4 | if ($target instanceof Element) { |
|
248 | 4 | $target->setAttributeNS(self::XMLNS, 'json:type', 'array'); |
|
249 | 4 | $parentName = $target->getAttributeNS(self::XMLNS, 'name') ?: $target->localName; |
|
250 | 4 | } |
|
251 | 4 | foreach ($value as $item) { |
|
252 | 3 | $target->appendChild( |
|
253 | 3 | $child = $target->ownerDocument->createElement( |
|
254 | 3 | $this->getQualifiedName($parentName, self::DEFAULT_QNAME, TRUE |
|
255 | 3 | ) |
|
256 | 3 | ) |
|
257 | 3 | ); |
|
258 | 3 | $this->transferTo($child, $item, $recursions); |
|
259 | 4 | } |
|
260 | 4 | } |
|
261 | |||
262 | /** |
||
263 | * Transfer an object value into a target element node. If the object has no properties, |
||
264 | * the json:type attribute is always set to 'object'. If verbose is not set the json:type attribute will |
||
265 | * be omitted if the object value has properties. |
||
266 | * |
||
267 | * The method creates child nodes for each property. The property name will be normalized to a valid NCName. |
||
268 | * If the normalized NCName is different from the property name or verbose is TRUE, a json:name attribute |
||
269 | * with the property name will be added. |
||
270 | * |
||
271 | * @param \DOMNode|\DOMElement|\DOMDocumentFragment $target |
||
272 | * @param object $value |
||
273 | * @param int $recursions |
||
274 | */ |
||
275 | 9 | private function transferObjectTo(\DOMNode $target, $value, $recursions) { |
|
276 | 9 | $properties = is_array($value) ? $value : get_object_vars($value); |
|
277 | 9 | if ($this->_verbose || empty($properties)) { |
|
278 | 2 | $target->setAttributeNS(self::XMLNS, 'json:type', 'object'); |
|
279 | 2 | } |
|
280 | 9 | foreach ($properties as $property => $item) { |
|
281 | 9 | $qname = $this->getQualifiedName($property, self::DEFAULT_QNAME); |
|
282 | 9 | $target->appendChild( |
|
283 | 9 | $child = $target->ownerDocument->createElement($qname) |
|
284 | 9 | ); |
|
285 | 9 | if ($this->_verbose || $qname != $property) { |
|
286 | 1 | $child->setAttributeNS(self::XMLNS, 'json:name', $property); |
|
287 | 1 | } |
|
288 | 9 | $this->transferTo($child, $item, $recursions); |
|
289 | 9 | } |
|
290 | 9 | } |
|
291 | } |
||
292 | } |
This check looks at variables that have been passed in as parameters and are passed out again to other methods.
If the outgoing method call has stricter type requirements than the method itself, an issue is raised.
An additional type check may prevent trouble.