Total Complexity | 67 |
Total Lines | 440 |
Duplicated Lines | 0 % |
Changes | 0 |
Complex classes like Arrays 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 Arrays, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
9 | class Arrays |
||
10 | { |
||
11 | use Arrays_Merge_Trait; |
||
12 | |||
13 | /** |
||
14 | * Taken from Kohana's Arr class. |
||
15 | * |
||
16 | * Tests if an array is associative or not. |
||
17 | * |
||
18 | * // Returns TRUE |
||
19 | * Arr::isAssoc(array('username' => 'john.doe')); |
||
20 | * |
||
21 | * // Returns FALSE |
||
22 | * Arr::isAssoc('foo', 'bar'); |
||
23 | * |
||
24 | * @param array $array array to check |
||
25 | * @return boolean |
||
26 | */ |
||
27 | public static function isAssociative(array $array) |
||
28 | { |
||
29 | // Keys of the array |
||
30 | $keys = array_keys($array); |
||
31 | |||
32 | // If the array keys of the keys match the keys, then the array must |
||
33 | // not be associative (e.g. the keys array looked like {0:0, 1:1...}). |
||
34 | return array_keys($keys) !== $keys; |
||
35 | } |
||
36 | |||
37 | /** |
||
38 | * Replacement of array_unique, keeping the first key. |
||
39 | * |
||
40 | * @param array|\Traversable $array |
||
41 | * @return array|\Traversable With unique values |
||
42 | * |
||
43 | * @todo Options to keep another key than the first one? |
||
44 | */ |
||
45 | public static function unique($array) |
||
68 | } |
||
69 | |||
70 | /** |
||
71 | */ |
||
72 | public static function keyExists($key, $array) |
||
73 | { |
||
74 | static::mustBeTraversable($array); |
||
75 | |||
76 | if (is_array($array)) { |
||
77 | return array_key_exists($key, $array); |
||
78 | } |
||
79 | elseif ($array instanceof ChainableArray || method_exists($array, 'keyExists')) { |
||
80 | return $array->keyExists($key); |
||
81 | } |
||
82 | else { |
||
83 | throw new \InvalidArgumentException( |
||
84 | "keyExists() method missing on :\n". var_export($array, true) |
||
85 | ); |
||
86 | } |
||
87 | |||
88 | return $array; |
||
|
|||
89 | } |
||
90 | |||
91 | /** |
||
92 | * Replacement of array_sum wich throws exceptions instead of skipping |
||
93 | * bad operands. |
||
94 | * |
||
95 | * @param array|\Traversable $array |
||
96 | * @return int|double The sum |
||
97 | * |
||
98 | * @todo Support options like 'strict', 'skip_non_scalars', 'native' |
||
99 | */ |
||
100 | public static function sum($array) |
||
101 | { |
||
102 | static::mustBeCountable($array); |
||
103 | |||
104 | $sum = 0; |
||
105 | foreach ($array as $key => &$value) { // &for optimization |
||
106 | if (is_scalar($value)) { |
||
107 | $sum += $value; |
||
108 | } |
||
109 | elseif (is_null($value)) { |
||
110 | continue; |
||
111 | } |
||
112 | elseif (is_array($value)) { |
||
113 | throw new \InvalidArgumentException( |
||
114 | "Trying to sum an array with '$sum': ".var_export($value, true) |
||
115 | ); |
||
116 | } |
||
117 | elseif (is_object($value)) { |
||
118 | if ( ! method_exists($value, 'toNumber')) { |
||
119 | throw new \InvalidArgumentEXception( |
||
120 | "Trying to sum a ".get_class($value)." object which cannot be casted as a number. " |
||
121 | ."Please add a toNumber() method." |
||
122 | ); |
||
123 | } |
||
124 | |||
125 | $sum += $value->toNumber(); |
||
126 | } |
||
127 | } |
||
128 | |||
129 | return $sum; |
||
130 | } |
||
131 | |||
132 | /** |
||
133 | * This method returns a classical mathemartic weighted mean. |
||
134 | * |
||
135 | * @todo It would ideally handled by a bridge with this fantastic math |
||
136 | * lib https://github.com/markrogoyski/math-php/ but we need the support |
||
137 | * of PHP 7 first. |
||
138 | * |
||
139 | * @see https://en.wikipedia.org/wiki/Weighted_arithmetic_mean |
||
140 | * @see https://github.com/markrogoyski/math-php/ |
||
141 | */ |
||
142 | public static function weightedMean($values, $weights) |
||
143 | { |
||
144 | if ($values instanceof ChainableArray) |
||
145 | $values = $values->toArray(); |
||
146 | |||
147 | if ($weights instanceof ChainableArray) |
||
148 | $weights = $weights->toArray(); |
||
149 | |||
150 | if ( ! is_array($values)) |
||
151 | $values = [$values]; |
||
152 | |||
153 | if ( ! is_array($weights)) |
||
154 | $weights = [$weights]; |
||
155 | |||
156 | if (count($values) != count($weights)) { |
||
157 | throw new \InvalidArgumentException( |
||
158 | "Different number of " |
||
159 | ." values and weights for weight mean calculation: \n" |
||
160 | .var_export($values, true)."\n\n" |
||
161 | .var_export($weights, true) |
||
162 | ); |
||
163 | } |
||
164 | |||
165 | if (!$values) |
||
166 | return null; |
||
167 | |||
168 | $weights_sum = array_sum($weights); |
||
169 | if (!$weights_sum) |
||
170 | return 0; |
||
171 | |||
172 | $weighted_sum = 0; |
||
173 | foreach ($values as $i => $value) { |
||
174 | $weighted_sum += $value * $weights[$i]; |
||
175 | } |
||
176 | |||
177 | return $weighted_sum / $weights_sum; |
||
178 | } |
||
179 | |||
180 | /** |
||
181 | * This is not required anymore with PHP 7. |
||
182 | * |
||
183 | * @return bool |
||
184 | */ |
||
185 | public static function isTraversable($value) |
||
186 | { |
||
187 | return $value instanceof \Traversable || is_array($value); |
||
188 | } |
||
189 | |||
190 | /** |
||
191 | * This is not required anymore with PHP 7. |
||
192 | * |
||
193 | * @return bool |
||
194 | */ |
||
195 | public static function isCountable($value) |
||
196 | { |
||
197 | return $value instanceof \Countable || is_array($value); |
||
198 | } |
||
199 | |||
200 | /** |
||
201 | * @param mixed $value |
||
202 | * @return bool Is the $value countable or not |
||
203 | * @throws InvalidArgumentException |
||
204 | * |
||
205 | * @todo NotCountableException |
||
206 | */ |
||
207 | public static function mustBeCountable($value) |
||
208 | { |
||
209 | if (static::isCountable($value)) |
||
210 | return true; |
||
211 | |||
212 | $exception = new \InvalidArgumentException( |
||
213 | "A value must be Countable instead of: \n" |
||
214 | .var_export($value, true) |
||
215 | ); |
||
216 | |||
217 | // The true location of the throw is still available through the backtrace |
||
218 | $trace_location = debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS, 1)[0]; |
||
219 | $reflectionClass = new \ReflectionClass( get_class($exception) ); |
||
220 | |||
221 | // file |
||
222 | if (isset($trace_location['file'])) { |
||
223 | $reflectionProperty = $reflectionClass->getProperty('file'); |
||
224 | $reflectionProperty->setAccessible(true); |
||
225 | $reflectionProperty->setValue($exception, $trace_location['file']); |
||
226 | } |
||
227 | |||
228 | // line |
||
229 | if (isset($trace_location['line'])) { |
||
230 | $reflectionProperty = $reflectionClass->getProperty('line'); |
||
231 | $reflectionProperty->setAccessible(true); |
||
232 | $reflectionProperty->setValue($exception, $trace_location['line']); |
||
233 | } |
||
234 | |||
235 | throw $exception; |
||
236 | } |
||
237 | |||
238 | /** |
||
239 | * @param mixed $value |
||
240 | * @return bool Is the $value traversable or not |
||
241 | * @throws InvalidArgumentException |
||
242 | * |
||
243 | * @todo NotTraversableException |
||
244 | */ |
||
245 | public static function mustBeTraversable($value) |
||
246 | { |
||
247 | if (static::isTraversable($value)) |
||
248 | return true; |
||
249 | |||
250 | $exception = new \InvalidArgumentException( |
||
251 | "A value must be Traversable instead of: \n" |
||
252 | .var_export($value, true) |
||
253 | ); |
||
254 | |||
255 | // The true location of the throw is still available through the backtrace |
||
256 | $trace_location = debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS, 1)[0]; |
||
257 | $reflectionClass = new \ReflectionClass( get_class($exception) ); |
||
258 | |||
259 | // file |
||
260 | if (isset($trace_location['file'])) { |
||
261 | $reflectionProperty = $reflectionClass->getProperty('file'); |
||
262 | $reflectionProperty->setAccessible(true); |
||
263 | $reflectionProperty->setValue($exception, $trace_location['file']); |
||
264 | } |
||
265 | |||
266 | // line |
||
267 | if (isset($trace_location['line'])) { |
||
268 | $reflectionProperty = $reflectionClass->getProperty('line'); |
||
269 | $reflectionProperty->setAccessible(true); |
||
270 | $reflectionProperty->setValue($exception, $trace_location['line']); |
||
271 | } |
||
272 | |||
273 | throw $exception; |
||
274 | } |
||
275 | |||
276 | /** |
||
277 | * Generates an id usable in hashes to identify a single grouped row. |
||
278 | * |
||
279 | * @param array $row The row of the array to group by. |
||
280 | * @param array $groups A list of the different groups. Groups can be |
||
281 | * strings describing a column name or a callable |
||
282 | * function, an array representing a callable, |
||
283 | * a function or an integer representing a column. |
||
284 | * If the index of the group is a string, it will |
||
285 | * be used as a prefix for the group name. |
||
286 | * Example: |
||
287 | * [ |
||
288 | * 'column_name', |
||
289 | * 'function_to_call', |
||
290 | * 4, //column_number |
||
291 | * 'group_prefix' => function($row){}, |
||
292 | * 'group_prefix2' => [$object, 'method'], |
||
293 | * ] |
||
294 | * |
||
295 | * @return string The unique identifier of the group |
||
296 | */ |
||
297 | public static function generateGroupId($row, array $groups_definitions, array $options=[]) |
||
396 | } |
||
397 | |||
398 | /** |
||
399 | * Search the value associated to the location_parts parameter into the input. |
||
400 | * |
||
401 | * @param array|ArrayAccess $input Typically $_POST|$_GET... |
||
402 | * @param scalar|scalar[] $location_parts |
||
403 | * |
||
404 | * @return mixed The value if it exists, null or $default_value otherwize. |
||
405 | * |
||
406 | * Example: |
||
407 | * $array = [ |
||
408 | * 'entry_1' => [ |
||
409 | * 'subentry_1' => 'lolo', |
||
410 | * 'subentry_2' => 'lala', |
||
411 | * ], |
||
412 | * 'entry_2' => [ |
||
413 | * 0 => 'lili', |
||
414 | * 1 => 'lulu', |
||
415 | * ], |
||
416 | * 'entry_3' => 'plop', |
||
417 | * ]; |
||
418 | * |
||
419 | * Arrays::getValueAt($array, ['entry_2', 1]); => 'lulu' |
||
420 | * Arrays::getValueAt($array, ['entry_1', 'subentry_2']); => null |
||
421 | * Arrays::getValueAt($array, ['entry_1', 'subentry_2'], 'lele'); => 'lele' |
||
422 | * Arrays::getValueAt($array, 'entry_3'); => 'plop' |
||
423 | */ |
||
424 | public static function getValueAt($input, $location_parts, $default_value = null) |
||
453 |
This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.
Unreachable code is most often the result of
return
,die
orexit
statements that have been added for debug purposes.In the above example, the last
return false
will never be executed, because a return statement has already been met in every possible execution path.