XMLWriter   B
last analyzed

Complexity

Total Complexity 46

Size/Duplication

Total Lines 226
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Test Coverage

Coverage 100%

Importance

Changes 4
Bugs 0 Features 1
Metric Value
dl 0
loc 226
ccs 98
cts 98
cp 1
rs 8.3999
c 4
b 0
f 1
wmc 46
lcom 1
cbo 3

14 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A registerNamespace() 0 3 1
A startElement() 0 5 1
A writeElement() 0 5 1
A applyNamespaces() 0 7 4
A startElementNS() 0 10 4
A writeElementNS() 0 6 4
A endElement() 0 4 1
A startAttribute() 0 6 1
A writeAttribute() 0 6 1
A startAttributeNS() 0 9 3
B writeAttributeNS() 0 19 9
B collapse() 0 12 6
D collapseNode() 0 36 9

How to fix   Complexity   

Complex Class

Complex classes like XMLWriter 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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 XMLWriter, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace FluentDOM {
4
5
  use FluentDOM\Utility\Namespaces;
6
  use FluentDOM\Utility\QualifiedName;
7
8
  class XMLWriter extends \XMLWriter {
9
10
    /**
11
     * @var Namespaces
12
     */
13
    private $_namespaces;
14
15
    /**
16
     * @var XMLWriter\NamespaceStack
17
     */
18
    private $_xmlnsStack;
19
20 11
    public function __construct() {
21 11
      $this->_namespaces = new Namespaces();
22 11
      $this->_xmlnsStack = new XMLWriter\NamespaceStack();
23 11
    }
24
25
    /**
26
     * register a namespace prefix for the xml reader, it will be used in
27
     * next() and other methods with a tag name argument
28
     *
29
     * @param string $prefix
30
     * @param string $namespaceURI
31
     * @throws \LogicException
32
     */
33 7
    public function registerNamespace(string $prefix, string $namespaceURI) {
34 7
      $this->_namespaces[$prefix] = $namespaceURI;
35 7
    }
36
37
    /**
38
     * Add the current namespace configuration as xmlns* attributes to the element node.
39
     */
40 1
    public function applyNamespaces() {
41 1
      foreach ($this->_namespaces as $prefix => $namespaceURI) {
42 1
        $this->writeAttribute(
43 1
          empty($prefix) || $prefix === '#default' ? 'xmlns' : 'xmlns:'.$prefix, $namespaceURI
44
        );
45
      }
46 1
    }
47
48
    /**
49
     * @param string $name
50
     * @return bool
51
     */
52 7
    public function startElement($name) {
53 7
      list($prefix, $localName) = QualifiedName::split($name);
54 7
      $namespaceURI = $this->_namespaces->resolveNamespace((string)$prefix);
55 7
      return $this->startElementNS((string)$prefix, $localName, $namespaceURI);
56
    }
57
58
    /**
59
     * @param string $name
60
     * @param NULL|string $content
61
     * @return bool
62
     */
63 3
    public function writeElement($name, $content = NULL) {
64 3
      list($prefix, $localName) = QualifiedName::split($name);
65 3
      $namespaceURI = $this->_namespaces->resolveNamespace((string)$prefix);
66 3
      return $this->writeElementNS((string)$prefix, $localName, $namespaceURI, $content);
67
    }
68
69
    /**
70
     * @param string $prefix
71
     * @param string $name
72
     * @param string $namespaceURI
73
     * @return bool
74
     */
75 10
    public function startElementNS($prefix, $name, $namespaceURI) {
76 10
      $this->_xmlnsStack->push();
77 10
      if ($this->_xmlnsStack->isDefined($prefix, $namespaceURI)) {
78 9
        $result = parent::startElement(empty($prefix) ? $name : $prefix.':'.$name);
79
      } else {
80 6
        $result = parent::startElementNS(empty($prefix) ? NULL : $prefix, $name, $namespaceURI);
81 6
        $this->_xmlnsStack->add($prefix, $namespaceURI);
82
      }
83 10
      return $result;
84
    }
85
86
    /**
87
     * @param string $prefix
88
     * @param string $name
89
     * @param string $uri
90
     * @param NULL|string $content
91
     * @return bool
92
     */
93 3
    public function writeElementNS($prefix, $name, $uri, $content = NULL) {
94 3
      if ($this->_xmlnsStack->isDefined($prefix, $uri)) {
95 2
        return parent::writeElement(empty($prefix) ? $name : $prefix.':'.$name, $content);
96
      }
97 1
      return parent::writeElementNS(empty($prefix) ? NULL : $prefix, $name, $uri, $content);
98
    }
99
100
    /**
101
     * @return bool
102
     */
103 10
    public function endElement() {
104 10
      $this->_xmlnsStack->pop();
105 10
      return parent::endElement();
106
    }
107
108
    /**
109
     * @param string $name
110
     * @return bool
111
     */
112 3
    public function startAttribute($name) {
113 3
      list($prefix, $localName) = QualifiedName::split($name);
114 3
      return $this->startAttributeNS(
115 3
        (string)$prefix, $localName, $this->_namespaces->resolveNamespace((string)$prefix)
116
      );
117
    }
118
119
    /**
120
     * @param string $name
121
     * @param string $value
122
     * @return bool
123
     */
124 3
    public function writeAttribute($name, $value) {
125 3
      list($prefix, $localName) = QualifiedName::split($name);
126 3
      return $this->writeAttributeNS(
127 3
        (string)$prefix, $localName, $this->_namespaces->resolveNamespace((string)$prefix), $value
128
      );
129
    }
130
131
    /**
132
     * @param string $prefix
133
     * @param string $name
134
     * @param string $uri
135
     * @return bool
136
     */
137 3
    public function startAttributeNS($prefix, $name, $uri) {
138 3
      if (empty($prefix)) {
139 1
        return parent::startAttribute($name);
140
      }
141 2
      if ($this->_xmlnsStack->isDefined($prefix, $uri)) {
142 1
        return parent::startAttribute($prefix.':'.$name);
143
      }
144 1
      return parent::startAttributeNS($prefix, $name, $uri);
145
    }
146
147
    /**
148
     * @param string $prefix
149
     * @param string $localName
150
     * @param string $uri
151
     * @param string $content
152
     * @return bool
153
     */
154 5
    public function writeAttributeNS($prefix, $localName, $uri, $content) {
155 5
      if ((empty($prefix) && $localName === 'xmlns') || $prefix === 'xmlns') {
156 1
        $namespacePrefix = empty($prefix) ? '' : $localName;
157 1
        $namespaceURI = $content;
158 1
        if (!$this->_xmlnsStack->isDefined($namespacePrefix, $namespaceURI)) {
159 1
          $result = parent::writeAttribute(empty($prefix) ? 'xmlns' : 'xmlns:'.$localName, $namespaceURI);
160 1
          $this->_xmlnsStack->add($namespacePrefix, $namespaceURI);
161 1
          return $result;
162
        }
163 1
        return FALSE;
164
      }
165 5
      if (empty($prefix)) {
166 4
        return parent::writeAttribute($localName, $content);
167
      }
168 2
      if ($this->_xmlnsStack->isDefined($prefix, $uri)) {
169 1
        return parent::writeAttribute($prefix.':'.$localName, $content);
170
      }
171 1
      return parent::writeAttributeNS($prefix, $localName, $uri, $content);
172
    }
173
174
    /**
175
     * Write a DOM node
176
     *
177
     * @param \DOMNode|\DOMNode[]|\Traversable $nodes
178
     * @param int $maximumDepth
179
     */
180 3
    public function collapse($nodes, int $maximumDepth = 1000) {
181 3
      if ($maximumDepth <= 0) {
182 1
        return;
183
      }
184 3
      if ($nodes instanceof \DOMNode) {
185 3
        $this->collapseNode($nodes, $maximumDepth);
186 3
      } elseif ($nodes instanceof \Traversable || is_array($nodes)) {
187 3
        foreach ($nodes as $childNode) {
188 3
          $this->collapse($childNode, $maximumDepth);
189
        }
190
      }
191 3
    }
192
193
    /**
194
     * @param \DOMNode $node
195
     * @param int $maximumDepth
196
     */
197 3
    private function collapseNode(\DOMNode $node, int $maximumDepth) {
198 3
      switch ($node->nodeType) {
199 3
      case XML_ELEMENT_NODE :
200 3
        $this->startElementNS($node->prefix, $node->localName, $node->namespaceURI);
201 3
        $this->collapse($node->attributes, $maximumDepth - 1);
202 3
        $this->collapse($node->childNodes, $maximumDepth - 1);
203 3
        $this->endElement();
204 3
        return;
205 3
      case XML_TEXT_NODE :
206
        /** @var \DOMText $node */
207 2
        if (!$node->isWhitespaceInElementContent()) {
208 2
          $this->text($node->textContent);
209
        }
210 2
        return;
211 3
      case XML_CDATA_SECTION_NODE :
212 2
        $this->writeCData($node->textContent);
213 2
        return;
214 3
      case XML_COMMENT_NODE :
215 2
        $this->writeComment($node->textContent);
216 2
        return;
217 3
      case XML_PI_NODE :
218
        /** @var \DOMProcessingInstruction $node */
219 2
        $this->writePI($node->target, $node->textContent);
220 2
        return;
221 3
      case XML_ATTRIBUTE_NODE :
222
        /** @var \DOMAttr $node */
223 2
        $this->writeAttributeNS($node->prefix, $node->localName, $node->namespaceURI, $node->value);
224 2
        return;
225
      default :
226
        /** @noinspection UnSafeIsSetOverArrayInspection */
227 3
        if (isset($node->childNodes)) {
228 3
          $this->collapse($node->childNodes, $maximumDepth);
229
        }
230 3
        return;
231
      }
232
    }
233
  }
234
}
235