| Total Complexity | 40 |
| Total Lines | 242 |
| Duplicated Lines | 0 % |
| Changes | 0 | ||
Complex classes like ExtensionXmlPushParser 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 ExtensionXmlPushParser, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 31 | class ExtensionXmlPushParser extends AbstractExtensionXmlParser |
||
| 32 | { |
||
| 33 | /** |
||
| 34 | * Property to store the xml parser resource in when run with PHP <= 7.4 |
||
| 35 | * |
||
| 36 | * @var resource|null |
||
| 37 | * @deprecated will be removed as soon as the minimum version of TYPO3 is 8.0 |
||
| 38 | */ |
||
| 39 | protected $legacyXmlParserResource; |
||
| 40 | |||
| 41 | /** |
||
| 42 | * Property to store the xml parser resource in when run with PHP >= 8.0 |
||
| 43 | */ |
||
| 44 | protected ?\XMLParser $xmlParser = null; |
||
| 45 | |||
| 46 | /** |
||
| 47 | * Keeps current data of element to process. |
||
| 48 | * |
||
| 49 | * @var string |
||
| 50 | */ |
||
| 51 | protected $elementData = ''; |
||
| 52 | |||
| 53 | /** |
||
| 54 | * Class constructor. |
||
| 55 | */ |
||
| 56 | public function __construct() |
||
| 57 | { |
||
| 58 | $this->requiredPhpExtensions = 'xml'; |
||
| 59 | $this->createParser(); |
||
| 60 | } |
||
| 61 | |||
| 62 | /** |
||
| 63 | * Create required parser |
||
| 64 | */ |
||
| 65 | protected function createParser() |
||
| 66 | { |
||
| 67 | if (PHP_MAJOR_VERSION >= 8) { |
||
| 68 | $this->xmlParser = xml_parser_create(); |
||
| 69 | xml_set_object($this->xmlParser, $this); |
||
| 70 | } else { |
||
| 71 | $this->legacyXmlParserResource = xml_parser_create(); |
||
|
|
|||
| 72 | xml_set_object($this->legacyXmlParserResource, $this); |
||
| 73 | } |
||
| 74 | } |
||
| 75 | |||
| 76 | /** |
||
| 77 | * Method parses an extensions.xml file. |
||
| 78 | * |
||
| 79 | * @param string $file GZIP stream resource |
||
| 80 | * @throws \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException in case of parse errors |
||
| 81 | */ |
||
| 82 | public function parseXml($file) |
||
| 83 | { |
||
| 84 | $this->createParser(); |
||
| 85 | if (PHP_MAJOR_VERSION < 8) { |
||
| 86 | $this->parseWithLegacyResource($file); |
||
| 87 | return; |
||
| 88 | } |
||
| 89 | |||
| 90 | if ($this->xmlParser === null) { |
||
| 91 | throw $this->createUnableToCreateXmlParseException(); |
||
| 92 | } |
||
| 93 | |||
| 94 | /** @var \XMLParser $parser */ |
||
| 95 | $parser = $this->xmlParser; |
||
| 96 | |||
| 97 | // keep original character case of XML document |
||
| 98 | xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0); |
||
| 99 | xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 0); |
||
| 100 | xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, 'utf-8'); |
||
| 101 | xml_set_element_handler($parser, [$this, 'startElement'], [$this, 'endElement']); |
||
| 102 | xml_set_character_data_handler($parser, [$this, 'characterData']); |
||
| 103 | if (!($fp = fopen($file, 'r'))) { |
||
| 104 | throw $this->createUnableToOpenFileResourceException($file); |
||
| 105 | } |
||
| 106 | while ($data = fread($fp, 4096)) { |
||
| 107 | if (!xml_parse($parser, $data, feof($fp))) { |
||
| 108 | throw $this->createXmlErrorException($parser, $file); |
||
| 109 | } |
||
| 110 | } |
||
| 111 | xml_parser_free($parser); |
||
| 112 | } |
||
| 113 | |||
| 114 | /** |
||
| 115 | * @throws ExtensionManagerException |
||
| 116 | * @internal |
||
| 117 | */ |
||
| 118 | private function parseWithLegacyResource(string $file) |
||
| 148 | } |
||
| 149 | |||
| 150 | private function createUnableToCreateXmlParseException(): ExtensionManagerException |
||
| 151 | { |
||
| 152 | return new ExtensionManagerException('Unable to create XML parser.', 1342640663); |
||
| 153 | } |
||
| 154 | |||
| 155 | private function createUnableToOpenFileResourceException(string $file): ExtensionManagerException |
||
| 158 | } |
||
| 159 | |||
| 160 | private function createXmlErrorException($parser, string $file): ExtensionManagerException |
||
| 161 | { |
||
| 162 | return new ExtensionManagerException( |
||
| 163 | sprintf( |
||
| 164 | 'XML error %s in line %u of file resource %s.', |
||
| 165 | xml_error_string(xml_get_error_code($parser)), |
||
| 166 | xml_get_current_line_number($parser), |
||
| 167 | $file |
||
| 168 | ), |
||
| 169 | 1342640703 |
||
| 170 | ); |
||
| 171 | } |
||
| 172 | |||
| 173 | /** |
||
| 174 | * Method is invoked when parser accesses start tag of an element. |
||
| 175 | * |
||
| 176 | * @param resource $parser parser resource |
||
| 177 | * @param string $elementName element name at parser's current position |
||
| 178 | * @param array $attrs array of an element's attributes if available |
||
| 179 | */ |
||
| 180 | protected function startElement($parser, $elementName, $attrs) |
||
| 191 | } |
||
| 192 | } |
||
| 193 | |||
| 194 | /** |
||
| 195 | * Method is invoked when parser accesses end tag of an element. |
||
| 196 | * |
||
| 197 | * @param resource $parser parser resource |
||
| 198 | * @param string $elementName Element name at parser's current position |
||
| 199 | */ |
||
| 200 | protected function endElement($parser, $elementName) |
||
| 201 | { |
||
| 202 | switch ($elementName) { |
||
| 203 | case 'extension': |
||
| 204 | $this->resetProperties(true); |
||
| 205 | break; |
||
| 206 | case 'version': |
||
| 207 | $this->notify(); |
||
| 208 | $this->resetProperties(); |
||
| 209 | break; |
||
| 210 | case 'downloadcounter': |
||
| 211 | // downloadcounter could be a child node of |
||
| 212 | // extension or version |
||
| 213 | if ($this->version == null) { |
||
| 214 | $this->extensionDownloadCounter = $this->elementData; |
||
| 215 | } else { |
||
| 216 | $this->versionDownloadCounter = $this->elementData; |
||
| 217 | } |
||
| 218 | break; |
||
| 219 | case 'title': |
||
| 220 | $this->title = $this->elementData; |
||
| 221 | break; |
||
| 222 | case 'description': |
||
| 223 | $this->description = $this->elementData; |
||
| 224 | break; |
||
| 225 | case 'state': |
||
| 226 | $this->state = $this->elementData; |
||
| 227 | break; |
||
| 228 | case 'reviewstate': |
||
| 229 | $this->reviewstate = $this->elementData; |
||
| 230 | break; |
||
| 231 | case 'category': |
||
| 232 | $this->category = $this->elementData; |
||
| 233 | break; |
||
| 234 | case 'lastuploaddate': |
||
| 235 | $this->lastuploaddate = $this->elementData; |
||
| 236 | break; |
||
| 237 | case 'uploadcomment': |
||
| 238 | $this->uploadcomment = $this->elementData; |
||
| 239 | break; |
||
| 240 | case 'dependencies': |
||
| 241 | $this->dependencies = $this->convertDependencies($this->elementData); |
||
| 242 | break; |
||
| 243 | case 'authorname': |
||
| 244 | $this->authorname = $this->elementData; |
||
| 245 | break; |
||
| 246 | case 'authoremail': |
||
| 247 | $this->authoremail = $this->elementData; |
||
| 248 | break; |
||
| 249 | case 'authorcompany': |
||
| 250 | $this->authorcompany = $this->elementData; |
||
| 251 | break; |
||
| 252 | case 'ownerusername': |
||
| 253 | $this->ownerusername = $this->elementData; |
||
| 254 | break; |
||
| 255 | case 't3xfilemd5': |
||
| 256 | $this->t3xfilemd5 = $this->elementData; |
||
| 257 | break; |
||
| 258 | case 'documentation_link': |
||
| 259 | $this->documentationLink = $this->elementData; |
||
| 260 | break; |
||
| 261 | } |
||
| 262 | } |
||
| 263 | |||
| 264 | /** |
||
| 265 | * Method is invoked when parser accesses any character other than elements. |
||
| 266 | * |
||
| 267 | * @param resource $parser parser resource |
||
| 268 | * @param string $data An element's value |
||
| 269 | */ |
||
| 270 | protected function characterData($parser, $data) |
||
| 273 | } |
||
| 274 | } |
||
| 275 |
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountIdthat can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theidproperty of an instance of theAccountclass. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.