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 | } |