| Total Complexity | 51 |
| Total Lines | 454 |
| Duplicated Lines | 0 % |
| Coverage | 100% |
| Changes | 1 | ||
| Bugs | 0 | Features | 0 |
Complex classes like Serializer 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 Serializer, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 16 | class Serializer |
||
| 17 | { |
||
| 18 | /** |
||
| 19 | * @var XPathConvertor XPath-to-PHP convertor |
||
| 20 | */ |
||
| 21 | public $convertor; |
||
| 22 | |||
| 23 | /** |
||
| 24 | * @var array Value of the "void" attribute of all elements, using the element's "id" as key |
||
| 25 | */ |
||
| 26 | protected $isVoid; |
||
| 27 | |||
| 28 | /** |
||
| 29 | * @var DOMXPath |
||
| 30 | */ |
||
| 31 | protected $xpath; |
||
| 32 | |||
| 33 | /** |
||
| 34 | * Constructor |
||
| 35 | */ |
||
| 36 | 820 | public function __construct() |
|
| 37 | { |
||
| 38 | 820 | $this->convertor = new XPathConvertor; |
|
| 39 | } |
||
| 40 | |||
| 41 | /** |
||
| 42 | * Convert an XPath expression (used in a condition) into PHP code |
||
| 43 | * |
||
| 44 | * This method is similar to convertXPath() but it selectively replaces some simple conditions |
||
| 45 | * with the corresponding DOM method for performance reasons |
||
| 46 | * |
||
| 47 | * @param string $expr XPath expression |
||
| 48 | * @return string PHP code |
||
| 49 | */ |
||
| 50 | 331 | public function convertCondition($expr) |
|
| 51 | { |
||
| 52 | 331 | return $this->convertor->convertCondition($expr); |
|
| 53 | } |
||
| 54 | |||
| 55 | /** |
||
| 56 | * Convert an XPath expression (used as value) into PHP code |
||
| 57 | * |
||
| 58 | * @param string $expr XPath expression |
||
| 59 | * @return string PHP code |
||
| 60 | */ |
||
| 61 | 632 | public function convertXPath($expr) |
|
| 62 | { |
||
| 63 | 632 | $php = $this->convertor->convertXPath($expr); |
|
| 64 | 632 | if (is_numeric($php)) |
|
| 65 | { |
||
| 66 | 7 | $php = "'" . $php . "'"; |
|
| 67 | } |
||
| 68 | |||
| 69 | 632 | return $php; |
|
| 70 | } |
||
| 71 | |||
| 72 | /** |
||
| 73 | * Serialize the internal representation of a template into PHP |
||
| 74 | * |
||
| 75 | * @param DOMElement $ir Internal representation |
||
| 76 | * @return string |
||
| 77 | */ |
||
| 78 | 815 | public function serialize(DOMElement $ir) |
|
| 79 | { |
||
| 80 | 815 | $this->xpath = new DOMXPath($ir->ownerDocument); |
|
|
1 ignored issue
–
show
|
|||
| 81 | 815 | $this->isVoid = []; |
|
| 82 | 815 | foreach ($this->xpath->query('//element') as $element) |
|
| 83 | { |
||
| 84 | 809 | $this->isVoid[$element->getAttribute('id')] = $element->getAttribute('void'); |
|
| 85 | } |
||
| 86 | |||
| 87 | 815 | return $this->serializeChildren($ir); |
|
| 88 | } |
||
| 89 | |||
| 90 | /** |
||
| 91 | * Convert an attribute value template into PHP |
||
| 92 | * |
||
| 93 | * NOTE: escaping must be performed by the caller |
||
| 94 | * |
||
| 95 | * @link https://www.w3.org/TR/1999/REC-xslt-19991116#dt-attribute-value-template |
||
| 96 | * |
||
| 97 | * @param string $attrValue Attribute value template |
||
| 98 | * @return string |
||
| 99 | */ |
||
| 100 | 809 | protected function convertAttributeValueTemplate($attrValue) |
|
| 116 | } |
||
| 117 | |||
| 118 | /** |
||
| 119 | * Convert a dynamic xsl:attribute/xsl:element name into PHP |
||
| 120 | * |
||
| 121 | * @param string $attrValue Attribute value template |
||
| 122 | * @return string |
||
| 123 | */ |
||
| 124 | protected function convertDynamicNodeName(string $attrValue): string |
||
| 125 | 466 | { |
|
| 126 | if (strpos($attrValue, '{') === false) |
||
| 127 | 466 | { |
|
| 128 | return var_export(htmlspecialchars($attrValue, ENT_QUOTES), true); |
||
| 129 | 3 | } |
|
| 130 | |||
| 131 | return 'htmlspecialchars(' . $this->convertAttributeValueTemplate($attrValue) . ',' . ENT_QUOTES . ')'; |
||
| 132 | 463 | } |
|
| 133 | |||
| 134 | 463 | /** |
|
| 135 | * Escape given literal |
||
| 136 | * |
||
| 137 | * @param string $text Literal |
||
| 138 | * @param string $context Either "raw", "text" or "attribute" |
||
| 139 | * @return string Escaped literal |
||
| 140 | */ |
||
| 141 | protected function escapeLiteral($text, $context) |
||
| 142 | { |
||
| 143 | if ($context === 'raw') |
||
| 144 | 618 | { |
|
| 145 | return $text; |
||
| 146 | 618 | } |
|
| 147 | |||
| 148 | 3 | $escapeMode = ($context === 'attribute') ? ENT_COMPAT : ENT_NOQUOTES; |
|
| 149 | |||
| 150 | return htmlspecialchars($text, $escapeMode); |
||
| 151 | 615 | } |
|
| 152 | |||
| 153 | 615 | /** |
|
| 154 | * Escape the output of given PHP expression |
||
| 155 | * |
||
| 156 | * @param string $php PHP expression |
||
| 157 | * @param string $context Either "raw", "text" or "attribute" |
||
| 158 | * @return string PHP expression, including escaping mechanism |
||
| 159 | */ |
||
| 160 | protected function escapePHPOutput($php, $context) |
||
| 161 | { |
||
| 162 | 33 | if ($context === 'raw') |
|
| 163 | { |
||
| 164 | 33 | return $php; |
|
| 165 | } |
||
| 166 | |||
| 167 | $escapeMode = ($context === 'attribute') ? ENT_COMPAT : ENT_NOQUOTES; |
||
| 168 | |||
| 169 | return 'htmlspecialchars(' . $php . ',' . $escapeMode . ')'; |
||
| 170 | } |
||
| 171 | |||
| 172 | /** |
||
| 173 | * Test whether given switch has more than one non-default case |
||
| 174 | * |
||
| 175 | * @param DOMElement $switch <switch/> node |
||
| 176 | 486 | * @return bool |
|
| 177 | */ |
||
| 178 | 486 | protected function hasMultipleCases(DOMElement $switch) |
|
| 179 | 486 | { |
|
| 180 | return $this->xpath->evaluate('count(case[@test]) > 1', $switch); |
||
| 181 | 486 | } |
|
| 182 | |||
| 183 | /** |
||
| 184 | 8 | * Test whether given attribute declaration is a minimizable boolean attribute |
|
| 185 | * |
||
| 186 | * The test is case-sensitive and only covers attribute that are minimized by libxslt |
||
| 187 | * |
||
| 188 | * @param string $attrName Attribute name |
||
| 189 | * @param string $php Attribute content, in PHP |
||
| 190 | * @return boolean |
||
| 191 | */ |
||
| 192 | protected function isBooleanAttribute(string $attrName, string $php): bool |
||
| 193 | 808 | { |
|
| 194 | $attrNames = ['checked', 'compact', 'declare', 'defer', 'disabled', 'ismap', 'multiple', 'nohref', 'noresize', 'noshade', 'nowrap', 'readonly', 'selected']; |
||
| 195 | 808 | if (!in_array($attrName, $attrNames, true)) |
|
| 196 | 808 | { |
|
| 197 | return false; |
||
| 198 | 2 | } |
|
| 199 | |||
| 200 | 808 | return ($php === '' || $php === "\$this->out.='" . $attrName . "';"); |
|
| 201 | } |
||
| 202 | 808 | ||
| 203 | /** |
||
| 204 | * Serialize an <applyTemplates/> node |
||
| 205 | * |
||
| 206 | * @param DOMElement $applyTemplates <applyTemplates/> node |
||
| 207 | * @return string |
||
| 208 | */ |
||
| 209 | protected function serializeApplyTemplates(DOMElement $applyTemplates) |
||
| 210 | { |
||
| 211 | 486 | $php = '$this->at($node'; |
|
| 212 | if ($applyTemplates->hasAttribute('select')) |
||
| 213 | 486 | { |
|
| 214 | $php .= ',' . var_export($applyTemplates->getAttribute('select'), true); |
||
| 215 | } |
||
| 216 | 486 | $php .= ');'; |
|
| 217 | |||
| 218 | return $php; |
||
| 219 | 486 | } |
|
| 220 | |||
| 221 | 486 | /** |
|
| 222 | 486 | * Serialize an <attribute/> node |
|
| 223 | 486 | * |
|
| 224 | * @param DOMElement $attribute <attribute/> node |
||
| 225 | 486 | * @return string |
|
| 226 | */ |
||
| 227 | 486 | protected function serializeAttribute(DOMElement $attribute) |
|
| 228 | { |
||
| 229 | 486 | $attrName = $attribute->getAttribute('name'); |
|
| 230 | |||
| 231 | // PHP representation of this attribute's name |
||
| 232 | $phpAttrName = $this->convertDynamicNodeName($attrName); |
||
| 233 | |||
| 234 | $php = "\$this->out.=' '." . $phpAttrName; |
||
| 235 | $content = $this->serializeChildren($attribute); |
||
| 236 | if (!$this->isBooleanAttribute($attrName, $content)) |
||
| 237 | { |
||
| 238 | 815 | $php .= ".'=\"';" . $content . "\$this->out.='\"'"; |
|
| 239 | } |
||
| 240 | 815 | $php .= ';'; |
|
| 241 | 815 | ||
| 242 | return $php; |
||
| 243 | 815 | } |
|
| 244 | |||
| 245 | 815 | /** |
|
| 246 | 815 | * Serialize all the children of given node into PHP |
|
| 247 | * |
||
| 248 | * @param DOMElement $ir Internal representation |
||
| 249 | * @return string |
||
| 250 | 814 | */ |
|
| 251 | protected function serializeChildren(DOMElement $ir) |
||
| 252 | { |
||
| 253 | $php = ''; |
||
| 254 | foreach ($ir->childNodes as $node) |
||
| 255 | { |
||
| 256 | if ($node instanceof DOMElement) |
||
| 257 | { |
||
| 258 | $methodName = 'serialize' . ucfirst($node->localName); |
||
| 259 | 809 | $php .= $this->$methodName($node); |
|
| 260 | } |
||
| 261 | 809 | } |
|
| 262 | 809 | ||
| 263 | return $php; |
||
| 264 | 809 | } |
|
| 265 | |||
| 266 | 1 | /** |
|
| 267 | * Serialize a <closeTag/> node |
||
| 268 | * |
||
| 269 | 809 | * @param DOMElement $closeTag <closeTag/> node |
|
| 270 | * @return string |
||
| 271 | 1 | */ |
|
| 272 | protected function serializeCloseTag(DOMElement $closeTag) |
||
| 273 | { |
||
| 274 | 809 | $php = "\$this->out.='>';"; |
|
| 275 | $id = $closeTag->getAttribute('id'); |
||
| 276 | |||
| 277 | 12 | if ($closeTag->hasAttribute('set')) |
|
| 278 | { |
||
| 279 | $php .= '$t' . $id . '=1;'; |
||
| 280 | 809 | } |
|
| 281 | |||
| 282 | if ($closeTag->hasAttribute('check')) |
||
| 283 | { |
||
| 284 | $php = 'if(!isset($t' . $id . ')){' . $php . '}'; |
||
| 285 | } |
||
| 286 | |||
| 287 | if ($this->isVoid[$id] === 'maybe') |
||
| 288 | { |
||
| 289 | 17 | // Check at runtime whether this element is not void |
|
| 290 | $php .= 'if(!$v' . $id . '){'; |
||
| 291 | } |
||
| 292 | 17 | ||
| 293 | 17 | return $php; |
|
| 294 | } |
||
| 295 | |||
| 296 | /** |
||
| 297 | * Serialize a <comment/> node |
||
| 298 | * |
||
| 299 | * @param DOMElement $comment <comment/> node |
||
| 300 | * @return string |
||
| 301 | */ |
||
| 302 | 2 | protected function serializeComment(DOMElement $comment) |
|
| 303 | { |
||
| 304 | return "\$this->out.='<!--';" |
||
| 305 | . $this->serializeChildren($comment) |
||
| 306 | . "\$this->out.='-->';"; |
||
| 307 | } |
||
| 308 | |||
| 309 | 2 | /** |
|
| 310 | 2 | * Serialize a <copyOfAttributes/> node |
|
| 311 | 2 | * |
|
| 312 | * @param DOMElement $copyOfAttributes <copyOfAttributes/> node |
||
| 313 | * @return string |
||
| 314 | */ |
||
| 315 | protected function serializeCopyOfAttributes(DOMElement $copyOfAttributes) |
||
| 325 | 809 | } |
|
| 326 | |||
| 327 | /** |
||
| 328 | 809 | * Serialize an <element/> node |
|
| 329 | * |
||
| 330 | * @param DOMElement $element <element/> node |
||
| 331 | 809 | * @return string |
|
| 332 | */ |
||
| 333 | protected function serializeElement(DOMElement $element) |
||
| 334 | 809 | { |
|
| 335 | $php = ''; |
||
| 336 | $elName = $element->getAttribute('name'); |
||
| 337 | 809 | $id = $element->getAttribute('id'); |
|
| 338 | $isVoid = $element->getAttribute('void'); |
||
| 339 | 12 | ||
| 340 | // Test whether this element name is dynamic |
||
| 341 | $isDynamic = (bool) (strpos($elName, '{') !== false); |
||
| 342 | 12 | ||
| 343 | // PHP representation of this element's name |
||
| 344 | $phpElName = $this->convertDynamicNodeName($elName); |
||
| 345 | 12 | ||
| 346 | // If the element name is dynamic, we cache its name for convenience and performance |
||
| 347 | if ($isDynamic) |
||
| 348 | { |
||
| 349 | 809 | $varName = '$e' . $id; |
|
| 350 | |||
| 351 | 12 | // Add the var declaration to the source |
|
| 352 | $php .= $varName . '=' . $phpElName . ';'; |
||
| 353 | |||
| 354 | // Replace the element name with the var |
||
| 355 | 809 | $phpElName = $varName; |
|
| 356 | } |
||
| 357 | |||
| 358 | 809 | // Test whether this element is void if we need this information |
|
| 359 | if ($isVoid === 'maybe') |
||
| 360 | { |
||
| 361 | 809 | $php .= '$v' . $id . '=preg_match(' . var_export(TemplateParser::$voidRegexp, true) . ',' . $phpElName . ');'; |
|
| 362 | } |
||
| 363 | 808 | ||
| 364 | // Open the start tag |
||
| 365 | $php .= "\$this->out.='<'." . $phpElName . ';'; |
||
| 366 | |||
| 367 | // Serialize this element's content |
||
| 368 | 809 | $php .= $this->serializeChildren($element); |
|
| 369 | |||
| 370 | 12 | // Close that element unless we know it's void |
|
| 371 | if ($isVoid !== 'yes') |
||
| 372 | { |
||
| 373 | 809 | $php .= "\$this->out.='</'." . $phpElName . ".'>';"; |
|
| 374 | } |
||
| 375 | |||
| 376 | // If this element was maybe void, serializeCloseTag() has put its content within an if |
||
| 377 | // block. We need to close that block |
||
| 378 | if ($isVoid === 'maybe') |
||
| 379 | { |
||
| 380 | $php .= '}'; |
||
| 381 | } |
||
| 382 | 33 | ||
| 383 | return $php; |
||
| 384 | 33 | } |
|
| 385 | 33 | ||
| 386 | /** |
||
| 387 | 32 | * Serialize a <switch/> node that has a branch-key attribute |
|
| 388 | * |
||
| 389 | 32 | * @param DOMElement $switch <switch/> node |
|
| 390 | * @return string |
||
| 391 | */ |
||
| 392 | 33 | protected function serializeHash(DOMElement $switch) |
|
| 412 | 703 | } |
|
| 413 | |||
| 414 | 703 | /** |
|
| 415 | 703 | * Serialize an <output/> node |
|
| 416 | * |
||
| 417 | 618 | * @param DOMElement $output <output/> node |
|
| 418 | * @return string |
||
| 419 | */ |
||
| 420 | protected function serializeOutput(DOMElement $output) |
||
| 436 | } |
||
| 437 | 339 | ||
| 438 | /** |
||
| 439 | 32 | * Serialize a <switch/> node |
|
| 440 | * |
||
| 441 | * @param DOMElement $switch <switch/> node |
||
| 442 | 331 | * @return string |
|
| 443 | 331 | */ |
|
| 444 | 331 | protected function serializeSwitch(DOMElement $switch) |
|
| 470 | } |
||
| 471 | } |