Passed
Push — master ( d03553...18ea48 )
by Sebastian
02:14
created

XMLHelper_Converter_Decorator::jsonSerialize()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 7
c 2
b 0
f 0
dl 0
loc 14
rs 10
cc 4
nc 2
nop 0
1
<?php
2
/**
3
 * File containing the {@see \AppUtils\XMLHelper_Converter_Decorator} class.
4
 * 
5
 * @package Application Utils
6
 * @subpackage XMLHelper
7
 * @see XMLHelper_Converter_Decorator
8
 */
9
10
declare(strict_types=1);
11
12
namespace AppUtils;
13
14
/**
15
 * Custom decorator for converting a SimpleXMLElement to
16
 * a meaningful JSON structure.
17
 *
18
 * @package Application Utils
19
 * @subpackage XMLHelper
20
 * @see https://hakre.wordpress.com/2013/07/10/simplexml-and-json-encode-in-php-part-iii-and-end/
21
 */
22
class XMLHelper_Converter_Decorator implements \JsonSerializable
23
{
24
   /**
25
    * @var \SimpleXMLElement
26
    */
27
    private $subject;
28
    
29
    const DEF_DEPTH = 512;
30
    
31
   /**
32
    * @var array
33
    */
34
    private $options = array(
35
        '@attributes' => true,
36
        '@text' => true,
37
        'depth' => self::DEF_DEPTH
38
    );
39
40
   /**
41
    * @var array
42
    */
43
    protected $result = array();
44
    
45
    public function __construct(\SimpleXMLElement $element)
46
    {
47
        $this->subject = $element;
48
    }
49
    
50
   /**
51
    * Whether to use the `@attributes` key to store element attributes.
52
    * 
53
    * @param bool $bool
54
    * @return XMLHelper_Converter_Decorator
55
    */
56
    public function useAttributes(bool $bool) : XMLHelper_Converter_Decorator 
57
    {
58
        $this->options['@attributes'] = (bool)$bool;
59
        return $this;
60
    }
61
    
62
   /**
63
    * Whether to use the `@text` key to store the node text.
64
    * 
65
    * @param bool $bool
66
    * @return XMLHelper_Converter_Decorator
67
    */
68
    public function useText(bool $bool) : XMLHelper_Converter_Decorator 
69
    {
70
        $this->options['@text'] = (bool)$bool;
71
        return $this;
72
    }
73
    
74
   /**
75
    * Set the maximum depth to parse in the document.
76
    * 
77
    * @param int $depth
78
    * @return XMLHelper_Converter_Decorator
79
    */
80
    public function setDepth(int $depth) : XMLHelper_Converter_Decorator 
81
    {
82
        $this->options['depth'] = (int)max(0, $depth);
83
        return $this;
84
    }
85
    
86
    /**
87
     * Specify data which should be serialized to JSON
88
     *
89
     * @return mixed data which can be serialized by json_encode.
90
     */
91
    public function jsonSerialize()
92
    {
93
        $this->result = array();
94
        
95
        $this->detectAttributes();
96
        $this->traverseChildren();
97
        $this->encodeTextElements();
98
        
99
        // return empty elements as NULL (self-closing or empty tags)
100
        if (empty($this->result) && !is_numeric($this->result) && !is_bool($this->result)) {
101
            $this->result = NULL;
102
        }
103
        
104
        return $this->result;
105
    }
106
    
107
    protected function detectAttributes()
108
    {
109
        // json encode attributes if any.
110
        if(!$this->options['@attributes']) {
111
            return;
112
        }
113
        
114
        $attributes = $this->subject->attributes();
115
        
116
        if($attributes instanceof \SimpleXMLElement) 
0 ignored issues
show
introduced by
$attributes is always a sub-type of SimpleXMLElement.
Loading history...
117
        {
118
            $this->result['@attributes'] = array_map('strval', iterator_to_array($attributes));
119
        }
120
    }
121
    
122
    protected function traverseChildren()
123
    {
124
        $children = $this->subject;
125
        $depth = $this->options['depth'] - 1;
126
        
127
        if($depth <= 0) 
128
        {
129
            $children = [];
130
        }
131
        
132
        // json encode child elements if any. group on duplicate names as an array.
133
        foreach ($children as $name => $element) 
134
        {
135
            /* @var \SimpleXMLElement $element */
136
            $decorator = new self($element);
137
            
138
            $decorator->options = ['depth' => $depth] + $this->options;
139
            
140
            if(isset($this->result[$name])) 
141
            {
142
                if(!is_array($this->result[$name])) 
143
                {
144
                    $this->result[$name] = [$this->result[$name]];
145
                }
146
                
147
                $this->result[$name][] = $decorator;
148
            } 
149
            else 
150
            {
151
                $this->result[$name] = $decorator;
152
            }
153
        }
154
    }
155
    
156
    protected function encodeTextElements()
157
    {
158
        // json encode non-whitespace element simplexml text values.
159
        $text = trim((string)$this->subject);
160
        
161
        if(strlen($text)) 
162
        {
163
            if($this->result && $this->options['@text']) 
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->result of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
164
            {
165
                $this->result['@text'] = $text;
166
            } 
167
            else 
168
            {
169
                $this->result = $text;
0 ignored issues
show
Documentation Bug introduced by
It seems like $text of type string is incompatible with the declared type array of property $result.

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...
170
            }
171
        }
172
    }
173
}
174