Complex classes like SimpleArrayLibrary 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 SimpleArrayLibrary, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
8 | class SimpleArrayLibrary |
||
9 | { |
||
10 | /** |
||
11 | * @param array $config |
||
12 | * @param array $keys |
||
13 | * @param mixed $value |
||
14 | * |
||
15 | * @return array |
||
16 | */ |
||
17 | public static function addConfigRow(array $config, array $keys, $value) |
||
18 | { |
||
19 | // validation |
||
20 | if (self::isAssociative($keys)) { |
||
21 | throw new UnexpectedValueException('Array of config keys must be numeric'); |
||
22 | } |
||
23 | |||
24 | $row = $value; |
||
25 | for ($i = count($keys) - 1; $i >= 0; $i--) { |
||
26 | $row = [$keys[$i] => $row]; |
||
27 | } |
||
28 | $config = self::insertSubArray($config, $row); |
||
29 | |||
30 | return $config; |
||
31 | } |
||
32 | |||
33 | /** |
||
34 | * Checks if all elements of the array have same value |
||
35 | * |
||
36 | * @param array $haystack |
||
37 | * @param mixed $needle |
||
38 | * |
||
39 | * @return boolean |
||
40 | */ |
||
41 | public static function allElementsEqual(array $haystack, $needle = null) |
||
60 | |||
61 | const TYPE_INT = 'int'; |
||
62 | const TYPE_STRING = 'string'; |
||
63 | const TYPE_FLOAT = 'float'; |
||
64 | const TYPE_BOOL = 'bool'; |
||
65 | const TYPE_ARRAY = 'array'; |
||
66 | const TYPE_OBJECT = 'object'; |
||
67 | |||
68 | /** |
||
69 | * @param array $matrix |
||
70 | * @param array $castMap |
||
71 | * @param bool $allKeysMustBePresent |
||
72 | * |
||
73 | * @return array |
||
74 | */ |
||
75 | public static function castColumns(array $matrix, array $castMap, $allKeysMustBePresent = true) |
||
76 | { |
||
77 | if (!is_bool($allKeysMustBePresent)) { |
||
78 | throw new InvalidArgumentException('Third parameter must be a boolean'); |
||
79 | } |
||
80 | if (empty($matrix)) { |
||
81 | return $matrix; |
||
82 | } |
||
83 | if (self::countMinDepth($matrix) < 2) { |
||
84 | throw new UnexpectedValueException('Can not cast columns on one dimensional array'); |
||
85 | } |
||
86 | |||
87 | foreach ($matrix as $key => $row) { |
||
88 | foreach ($castMap as $column => $type) { |
||
89 | if (isset($row[$column]) || array_key_exists($column, $row)) { |
||
90 | switch ($type) { |
||
91 | case self::TYPE_INT: |
||
92 | $matrix[$key][$column] = (int)$row[$column]; |
||
93 | break; |
||
94 | case self::TYPE_STRING: |
||
95 | $matrix[$key][$column] = (string)$row[$column]; |
||
96 | break; |
||
97 | case self::TYPE_FLOAT: |
||
98 | $matrix[$key][$column] = (float)$row[$column]; |
||
99 | break; |
||
100 | case self::TYPE_BOOL: |
||
101 | $matrix[$key][$column] = (bool)$row[$column]; |
||
102 | break; |
||
103 | case self::TYPE_ARRAY: |
||
104 | $matrix[$key][$column] = (array)$row[$column]; |
||
105 | break; |
||
106 | case self::TYPE_OBJECT: |
||
107 | $matrix[$key][$column] = (object)$row[$column]; |
||
108 | break; |
||
109 | default: |
||
110 | throw new UnexpectedValueException('Invalid type: ' . $type); |
||
111 | } |
||
112 | } elseif ($allKeysMustBePresent) { |
||
113 | throw new UnexpectedValueException('Column: ' . $column . ' missing in row: ' . $key); |
||
114 | } |
||
115 | } |
||
116 | } |
||
117 | |||
118 | return $matrix; |
||
119 | } |
||
120 | |||
121 | /** |
||
122 | * Counts maximum array depth recursively |
||
123 | * |
||
124 | * @param array $array |
||
125 | * |
||
126 | * @return int |
||
127 | */ |
||
128 | public static function countMaxDepth(array $array) |
||
141 | |||
142 | /** |
||
143 | * Counts maximum array depth iteratively |
||
144 | * |
||
145 | * @param array $array |
||
146 | * |
||
147 | * @return int |
||
148 | */ |
||
149 | public static function countMaxDepthIterative(array $array) |
||
150 | { |
||
151 | $copy = $array; |
||
152 | $maxDepth = 1; |
||
153 | |||
154 | foreach ($copy as $element) { |
||
155 | $depth = 1; |
||
156 | while (!empty($element)) { |
||
157 | if (is_array($element)) { |
||
158 | ++$depth; |
||
159 | $tmp = array_shift($element); |
||
160 | if (is_array($tmp)) { |
||
161 | array_push($element, array_shift($tmp)); |
||
162 | } |
||
163 | } else { |
||
164 | break; |
||
165 | } |
||
166 | } |
||
167 | if ($depth > $maxDepth) { |
||
168 | $maxDepth = $depth; |
||
169 | } |
||
170 | } |
||
171 | |||
172 | return $maxDepth; |
||
173 | } |
||
174 | |||
175 | /** |
||
176 | * Counts maximum array depth |
||
177 | * |
||
178 | * @param mixed $potentialArray |
||
179 | * @param int $depth |
||
180 | * |
||
181 | * @return int |
||
182 | * @throws InvalidArgumentException |
||
183 | */ |
||
184 | public static function countMinDepth($potentialArray, $depth = 0) |
||
185 | { |
||
186 | // validation, must be positive int or 0 |
||
187 | if (!self::isLogicallyCastableToInt($depth)) { |
||
188 | throw new InvalidArgumentException('Depth parameter must be non-negative integer'); |
||
189 | } |
||
190 | if ($depth < 0) { |
||
191 | throw new InvalidArgumentException('Depth parameter must be non-negative integer'); |
||
192 | } |
||
193 | |||
194 | $return = $depth; |
||
195 | if (is_array($potentialArray)) { |
||
196 | $return++; |
||
197 | $childrenDepths = array(); |
||
198 | foreach ($potentialArray as $element) { |
||
199 | $childrenDepths[] = self::countMinDepth($element, $return); |
||
200 | } |
||
201 | $return = empty($childrenDepths) ? $return : min($childrenDepths); |
||
202 | } |
||
203 | |||
204 | return $return; |
||
205 | } |
||
206 | |||
207 | /** |
||
208 | * @param array $matrix |
||
209 | * @param mixed $columns |
||
210 | * |
||
211 | * @return array |
||
212 | */ |
||
213 | public static function deleteColumns(array $matrix, array $columns) |
||
214 | { |
||
215 | // validate input |
||
216 | if (self::countMinDepth($matrix) < 2) { |
||
217 | throw new UnexpectedValueException('Can not delete columns on one dimensional array'); |
||
218 | } |
||
219 | if (self::countMinDepth($columns) != 1) { |
||
220 | throw new InvalidArgumentException('Invalid column'); |
||
221 | } |
||
222 | |||
223 | // remove columns in every row |
||
224 | foreach ($matrix as $key => $row) { |
||
225 | foreach ($columns as $column) { |
||
226 | unset($matrix[$key][$column]); |
||
227 | } |
||
228 | } |
||
229 | |||
230 | return $matrix; |
||
231 | } |
||
232 | |||
233 | /** |
||
234 | * Extracts a column from an array |
||
235 | * |
||
236 | * @param array $array |
||
237 | * @param array $columns |
||
238 | * @param bool $allRowsMustHaveAllColumns |
||
239 | * |
||
240 | * @return array |
||
241 | * @throws UnexpectedValueException |
||
242 | */ |
||
243 | public static function getColumns(array $array, array $columns, $allRowsMustHaveAllColumns = false) |
||
244 | { |
||
245 | // validation |
||
246 | foreach ($array as $key => $row) { |
||
247 | if (!is_array($row)) { |
||
248 | throw new UnexpectedValueException('Array element "' . $key . '" is not an array'); |
||
249 | } |
||
250 | } |
||
251 | foreach ($columns as $key => $column) { |
||
252 | if (!is_string($column) && !is_numeric($column)) { |
||
253 | throw new InvalidArgumentException('Invalid column type in columns array, index "' . $key . '"'); |
||
254 | } |
||
255 | } |
||
256 | if (!is_bool($allRowsMustHaveAllColumns)) { |
||
257 | throw new InvalidArgumentException('allRowsMustHaveAllColumns flag must be boolean'); |
||
258 | } |
||
259 | |||
260 | $return = array_fill_keys($columns, array()); |
||
261 | foreach ($array as $key => $row) { |
||
262 | foreach ($columns as $column) { |
||
263 | if (isset($row[$column]) || array_key_exists($column, $row)) { |
||
264 | $return[$column][$key] = $row[$column]; |
||
265 | } elseif ($allRowsMustHaveAllColumns) { |
||
266 | throw new UnexpectedValueException('Row "' . $key . '" is missing column: "' . $column . '"'); |
||
267 | } |
||
268 | } |
||
269 | } |
||
270 | |||
271 | return $return; |
||
272 | } |
||
273 | |||
274 | /** |
||
275 | * Checks if an array is rectangular array and returns dimensions or -1 if it's not rectangular |
||
276 | * |
||
277 | * @param array $array |
||
278 | * |
||
279 | * @return int|array |
||
280 | */ |
||
281 | public static function getRectangularDimensions(array $array) |
||
282 | { |
||
283 | $return = -1; |
||
284 | $allArrays = array_map('is_array', $array); |
||
285 | // all elements are arrays, iterate through them and call the static function recursively |
||
286 | if (self::allElementsEqual($allArrays, true)) { |
||
287 | $elementsPerArray = array(); |
||
288 | foreach ($array as $row) { |
||
289 | $noElements = self::getRectangularDimensions($row); |
||
290 | if ($noElements == -1) { |
||
291 | return $noElements; |
||
292 | } |
||
293 | $elementsPerArray[] = $noElements; |
||
294 | } |
||
295 | if (!self::allElementsEqual($elementsPerArray)) { |
||
296 | return -1; |
||
297 | } else { |
||
298 | $return = reset($elementsPerArray); |
||
299 | $return[] = count($elementsPerArray); |
||
300 | } |
||
301 | } // none of the elements are arrays, return number of elements of the "bottom" array |
||
302 | elseif (self::allElementsEqual($allArrays, false)) { |
||
303 | $return = array(0 => count($array)); |
||
304 | } |
||
305 | |||
306 | return $return; |
||
307 | } |
||
308 | |||
309 | /** |
||
310 | * Checks if $array's keys contain all of $subArray's values |
||
311 | * |
||
312 | * @param array $haystack |
||
313 | * @param array $needles |
||
314 | * |
||
315 | * @return bool |
||
316 | */ |
||
317 | public static function hasAllKeys(array $haystack, array $needles) |
||
321 | |||
322 | /** |
||
323 | * Checks if $array's keys contain all of $subArray's values |
||
324 | * |
||
325 | * @param array $haystack |
||
326 | * @param array $needles |
||
327 | * |
||
328 | * @return bool |
||
329 | */ |
||
330 | public static function hasAllValues(array $haystack, array $needles) |
||
334 | |||
335 | /** |
||
336 | * Checks if $array's keys contain all of $subArray's values |
||
337 | * |
||
338 | * @param array $haystack |
||
339 | * @param array $needles |
||
340 | * |
||
341 | * @return bool |
||
342 | */ |
||
343 | public static function hasAllValuesMultiDimensional(array $haystack, array $needles) |
||
344 | { |
||
345 | $return = true; |
||
346 | foreach ($needles as $needle) { |
||
347 | if (!in_array($needle, $haystack)) { |
||
348 | $return = false; |
||
349 | break; |
||
350 | } |
||
351 | } |
||
352 | |||
353 | return $return; |
||
354 | } |
||
355 | |||
356 | /** |
||
357 | * Checks whether array has only provided keys as indexes |
||
358 | * |
||
359 | * @param array $haystack |
||
360 | * @param array $needles |
||
361 | * |
||
362 | * @return bool |
||
363 | */ |
||
364 | public static function hasOnlyKeys(array $haystack, array $needles) |
||
368 | |||
369 | /** |
||
370 | * Checks if two arrays have all equal keys |
||
371 | * |
||
372 | * @param array $array1 |
||
373 | * @param array $array2 |
||
374 | * |
||
375 | * @return boolean |
||
376 | */ |
||
377 | public static function haveSameKeys(array $array1, array $array2) |
||
381 | |||
382 | /** |
||
383 | * Check if two arrays have all equal values |
||
384 | * |
||
385 | * @param array $array1 |
||
386 | * @param array $array2 |
||
387 | * |
||
388 | * @return bool |
||
389 | */ |
||
390 | public static function haveSameValues($array1, $array2) |
||
394 | |||
395 | /** |
||
396 | * @param mixed $array |
||
397 | * @param mixed $subArray |
||
398 | * @param bool $overwrite |
||
399 | * @param bool $ignoreIfExists |
||
400 | * |
||
401 | * @return array |
||
402 | */ |
||
403 | public static function insertSubArray($array, $subArray, $overwrite = false, $ignoreIfExists = false) |
||
404 | { |
||
405 | // validate |
||
406 | if (!is_bool($overwrite)) { |
||
407 | throw new InvalidArgumentException('Overwrite indicator must be a boolean'); |
||
408 | } |
||
409 | if (!is_bool($ignoreIfExists)) { |
||
410 | throw new InvalidArgumentException('Ignore if exists indicator must be a boolean'); |
||
411 | } |
||
412 | |||
413 | if (!is_array($subArray) || !is_array($array)) { |
||
414 | // $subArray[$key] is leaf of the array |
||
415 | if ($overwrite) { |
||
416 | $array = $subArray; |
||
417 | } elseif (!$ignoreIfExists) { |
||
418 | throw new UnexpectedValueException('Sub-array already exists'); |
||
419 | } |
||
420 | } else { |
||
421 | $key = key($subArray); |
||
422 | if (isset($array[$key]) || array_key_exists($key, $array)) { |
||
423 | $array[$key] = self::insertSubArray($array[$key], $subArray[$key], $overwrite, $ignoreIfExists); |
||
424 | } else { |
||
425 | $array[$key] = $subArray[$key]; |
||
426 | } |
||
427 | } |
||
428 | |||
429 | return $array; |
||
430 | } |
||
431 | |||
432 | /** |
||
433 | * Checks whether array is associative or numeric |
||
434 | * |
||
435 | * @param array $array |
||
436 | * |
||
437 | * @return bool |
||
438 | */ |
||
439 | public static function isAssociative(array $array) |
||
443 | |||
444 | /** |
||
445 | * Checks whether array is numeric |
||
446 | * |
||
447 | * @param array $array |
||
448 | * |
||
449 | * @return bool |
||
450 | */ |
||
451 | public static function isNumeric(array $array) |
||
452 | { |
||
455 | |||
456 | /** |
||
457 | * Checks whether $subArray is contained in $array |
||
458 | * |
||
459 | * @param array $array |
||
460 | * @param array $subArray |
||
461 | * @param bool $strictComparison |
||
462 | * |
||
463 | * @return bool |
||
464 | * @throws InvalidArgumentException |
||
465 | */ |
||
466 | public static function isSubArray(array $array, array $subArray, $strictComparison = true) |
||
488 | |||
489 | /** |
||
490 | * Selects random sub array |
||
491 | * |
||
492 | * @param array $array |
||
493 | * @param int $numberOfRequiredElements |
||
494 | * |
||
495 | * @return array |
||
496 | * @throws InvalidArgumentException |
||
497 | */ |
||
498 | public static function selectRandomArrayElements(array $array, $numberOfRequiredElements) |
||
519 | |||
520 | /** |
||
521 | * @param array $matrix |
||
522 | * @param mixed $column |
||
523 | * @param mixed $value |
||
524 | * @param bool $insertIfMissing |
||
525 | * @param bool $overwrite |
||
526 | * |
||
527 | * @return array |
||
528 | */ |
||
529 | public static function setColumn(array $matrix, $column, $value, $insertIfMissing = true, $overwrite = true) |
||
555 | |||
556 | /** |
||
557 | * Check whether casting a variable to int would conway useful information |
||
558 | * |
||
559 | * @param mixed $input |
||
560 | * |
||
561 | * @return bool |
||
562 | */ |
||
563 | private static function isLogicallyCastableToInt($input) |
||
567 | } |