| Total Complexity | 54 |
| Total Lines | 323 |
| Duplicated Lines | 0 % |
| Changes | 1 | ||
| Bugs | 0 | Features | 0 |
Complex classes like SclDirectory 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 SclDirectory, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 28 | class SclDirectory |
||
| 29 | { |
||
| 30 | /** the url where the scl directory can be accessed */ |
||
| 31 | protected const DATA_PROVIDER_URL = 'https://www.bundesbank.de/scl-directory'; |
||
| 32 | /** the data file */ |
||
| 33 | protected const DATAFILE = 'scl-directory.xml'; |
||
| 34 | |||
| 35 | /** @var string path to the data file */ |
||
| 36 | protected string $strDataPath = ''; |
||
| 37 | /** @var \DOMDocument */ |
||
| 38 | protected ?\DOMDocument $oDoc = null; |
||
| 39 | /** @var string last error */ |
||
| 40 | protected string $strLastError; |
||
| 41 | /** @var integer unix timestamp of last data update */ |
||
| 42 | protected int $uxtsLastUpdated = 0; |
||
| 43 | |||
| 44 | /** |
||
| 45 | * Create SCL directory. |
||
| 46 | * @param string $strDataPath path to the XML data file |
||
| 47 | */ |
||
| 48 | public function __construct(string $strDataPath = '') |
||
| 49 | { |
||
| 50 | $this->strDataPath = rtrim($strDataPath, DIRECTORY_SEPARATOR); |
||
| 51 | } |
||
| 52 | |||
| 53 | /** |
||
| 54 | * Init the object. |
||
| 55 | * If the XML data file already exist, it is checked if writable. |
||
| 56 | * If not, the datapath is checked if writable |
||
| 57 | * @return bool false, if any error occur |
||
| 58 | */ |
||
| 59 | public function init() : bool |
||
| 60 | { |
||
| 61 | $this->strLastError = ''; |
||
| 62 | $strXMLName = self::DATAFILE; |
||
| 63 | if (strlen($this->strDataPath) > 0) { |
||
| 64 | $strXMLName = $this->strDataPath . DIRECTORY_SEPARATOR . self::DATAFILE; |
||
| 65 | } |
||
| 66 | |||
| 67 | if (file_exists($strXMLName)) { |
||
| 68 | if (!is_writable($strXMLName)) { |
||
| 69 | $this->strLastError .= 'readonly data file ' . $strXMLName . '!'; |
||
| 70 | } else { |
||
| 71 | $this->oDoc = new \DOMDocument(); |
||
| 72 | $this->oDoc->load($strXMLName); |
||
| 73 | } |
||
| 74 | } else { |
||
| 75 | $strPath = realpath($this->strDataPath); |
||
| 76 | if ($strPath === false || !is_writable($strPath)) { |
||
| 77 | $this->strLastError .= ' (no rights to write on directory ' . $strPath . ')'; |
||
| 78 | } |
||
| 79 | } |
||
| 80 | return (strlen($this->strLastError) == 0); |
||
| 81 | } |
||
| 82 | |||
| 83 | /** |
||
| 84 | * Get the date the data has been updated last. |
||
| 85 | * @return int date as unix timestamp |
||
| 86 | */ |
||
| 87 | public function lastUpdated() : int |
||
| 88 | { |
||
| 89 | $uxtsLastUpdated = 0; |
||
| 90 | if ($this->oDoc !== null && $this->oDoc->documentElement !== null) { |
||
| 91 | $strDate = $this->oDoc->documentElement->getAttribute('created'); |
||
| 92 | if (strlen($strDate) > 0) { |
||
| 93 | $uxtsLastUpdated = strtotime($strDate); |
||
| 94 | } |
||
| 95 | } |
||
| 96 | return intval($uxtsLastUpdated); |
||
| 97 | } |
||
| 98 | |||
| 99 | /** |
||
| 100 | * Check, if the requested BIC exists. |
||
| 101 | * @param string $strBIC |
||
| 102 | * @return bool |
||
| 103 | */ |
||
| 104 | public function isValidBIC(string $strBIC) : bool |
||
| 105 | { |
||
| 106 | return ($this->getProviderNode($strBIC) !== null); |
||
| 107 | } |
||
| 108 | |||
| 109 | /** |
||
| 110 | * Get the name of the provider to the given BIC. |
||
| 111 | * Since the provider names in the supported directory all in uppercase, this |
||
| 112 | * can be converted to upper case words by setting the `$bToUCWords`parameter to true. |
||
| 113 | * @param string $strBIC BIC to get the name for |
||
| 114 | * @param bool $bToUCWords convert the provider names to Uppercase Words |
||
| 115 | * @return string name or empty string, if not exist |
||
| 116 | */ |
||
| 117 | public function getNameFromBIC(string $strBIC, bool $bToUCWords = false) : string |
||
| 118 | { |
||
| 119 | $strName = ''; |
||
| 120 | $oNode = $this->getProviderNode($strBIC); |
||
| 121 | if ($oNode !== null) { |
||
| 122 | $strName = ($bToUCWords ? $this->convToUCWords($oNode->nodeValue ?? '') : $oNode->nodeValue ?? ''); |
||
| 123 | } |
||
| 124 | return $strName; |
||
| 125 | } |
||
| 126 | |||
| 127 | /** |
||
| 128 | * Get the list of provider names. |
||
| 129 | * Since the provider names in the supported directory all in uppercase, this |
||
| 130 | * can be converted to upper case words by setting the `$bToUCWords`parameter to true. |
||
| 131 | * @param string $strCC country code the list should be generated for (leave empty for full list) |
||
| 132 | * @param bool $bToUCWords convert the provider names to Uppercase Words |
||
| 133 | * @return array<string,string> |
||
| 134 | */ |
||
| 135 | public function getProviderList(string $strCC = '', bool $bToUCWords = false) : array |
||
| 136 | { |
||
| 137 | $aList = []; |
||
| 138 | if ($this->oDoc !== null) { |
||
| 139 | $oXPath = new \DOMXPath($this->oDoc); |
||
| 140 | if (strlen($strCC) > 0) { |
||
| 141 | $oNodelist = $oXPath->query("//Provider[@CC='" . $strCC . "']"); |
||
| 142 | } else { |
||
| 143 | $oNodelist = $oXPath->query("//Provider"); |
||
| 144 | } |
||
| 145 | if ($oNodelist !== false) { |
||
| 146 | foreach ($oNodelist as $oNode) { |
||
| 147 | if ($oNode instanceof \DOMElement && $oNode->hasAttribute('BIC')) { |
||
| 148 | $aList[$oNode->getAttribute('BIC')] = ($bToUCWords ? $this->convToUCWords($oNode->nodeValue ?? '') : $oNode->nodeValue ?? ''); |
||
| 149 | } |
||
| 150 | } |
||
| 151 | } |
||
| 152 | } |
||
| 153 | return $aList; |
||
| 154 | } |
||
| 155 | |||
| 156 | /** |
||
| 157 | * Load the actual list from the internet. |
||
| 158 | * Since downloading and saving the data takes a certain amount of time and this |
||
| 159 | * list does not change constantly, an interval can be specified so that the data |
||
| 160 | * only is downloaded again after it has expired. |
||
| 161 | * The intervall can be specified as integer in seconds (according to a unix timestamp) |
||
| 162 | * or as string representing any `dateinterval´. |
||
| 163 | * > recommended values are 1..4 weeks (e.g. `'P2W'` for 2 weeks). |
||
| 164 | * @link https://www.php.net/manual/dateinterval.construct.php |
||
| 165 | * @param int|string|null $interval |
||
| 166 | * @return bool |
||
| 167 | */ |
||
| 168 | public function loadFromInternet($interval = null) : bool |
||
| 244 | } |
||
| 245 | |||
| 246 | /** |
||
| 247 | * Return last error occured. |
||
| 248 | * @return string |
||
| 249 | */ |
||
| 250 | public function getError() : string |
||
| 251 | { |
||
| 252 | return $this->strLastError; |
||
| 253 | } |
||
| 254 | |||
| 255 | /** |
||
| 256 | * Search for the node to the given BIC. |
||
| 257 | * If the requested BIC ends with 'XXX' and does'n exist, we're lookin |
||
| 258 | * for an entry without trailing 'XXX'. |
||
| 259 | * @param string $strBIC |
||
| 260 | * @return \DOMNode |
||
| 261 | */ |
||
| 262 | protected function getProviderNode(string $strBIC) : ?\DOMNode |
||
| 263 | { |
||
| 264 | $oNode = null; |
||
| 265 | if ($this->oDoc !== null) { |
||
| 266 | $oXPath = new \DOMXPath($this->oDoc); |
||
| 267 | $oNodelist = $oXPath->query("//Provider[@BIC='" . $strBIC . "']"); |
||
| 268 | if (($oNodelist === false || $oNodelist->length === 0) && strlen($strBIC) == 11 && substr($strBIC, 8, 3) == 'XXX') { |
||
| 269 | $strBIC = substr($strBIC, 0, 8); |
||
| 270 | $oNodelist = $oXPath->query("//Provider[@BIC='" . $strBIC . "']"); |
||
| 271 | } |
||
| 272 | if ($oNodelist !== false && $oNodelist->length > 0) { |
||
| 273 | $oNode = $oNodelist[0]; |
||
| 274 | } |
||
| 275 | } |
||
| 276 | return $oNode; |
||
| 277 | } |
||
| 278 | |||
| 279 | /** |
||
| 280 | * Check, if the intervall has expired since last download. |
||
| 281 | * The intervall can be specified as integer in seconds (according to a unix timestamp) |
||
| 282 | * or as string representing any `dateinterval´. |
||
| 283 | * > recommended values are 1..4 weeks (e.g. `'P2W'` for 2 weeks). |
||
| 284 | * @param int|string|null $interval |
||
| 285 | * @return bool |
||
| 286 | * @throws \Exception if $interval cannot be parsed in the \DateInterval constructor |
||
| 287 | */ |
||
| 288 | protected function hasIntervalExpired($interval = null) : bool |
||
| 289 | { |
||
| 290 | if ($interval === null) { |
||
| 291 | return true; |
||
| 292 | } |
||
| 293 | $uxtsLastUpdate = $this->lastUpdated(); |
||
| 294 | if ($uxtsLastUpdate == 0) { |
||
| 295 | return true; |
||
| 296 | } |
||
| 297 | if (is_numeric($interval)) { |
||
| 298 | // inteval is a timespan in seconds... |
||
| 299 | return $uxtsLastUpdate + $interval < time(); |
||
| 300 | } else { |
||
| 301 | $di = new \DateInterval($interval); |
||
| 302 | $dtLastUpdate = new \DateTime(); |
||
| 303 | $dtLastUpdate->setTimestamp($uxtsLastUpdate); |
||
| 304 | $dtLastUpdate->add($di); |
||
| 305 | |||
| 306 | return $dtLastUpdate->getTimestamp() < time(); |
||
| 307 | } |
||
| 308 | } |
||
| 309 | |||
| 310 | /** |
||
| 311 | * Convert to uppercase words. |
||
| 312 | * Some exceptions are converted to lowercase, some to uppercase and some |
||
| 313 | * will be replaced by special case... |
||
| 314 | * @param string $strText |
||
| 315 | * @return string |
||
| 316 | */ |
||
| 317 | protected function convToUCWords(string $strText) |
||
| 351 | } |
||
| 352 | } |