Complex classes like Filterer 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 Filterer, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
13 | final class Filterer implements FiltererInterface |
||
14 | { |
||
15 | /** |
||
16 | * @var array |
||
17 | */ |
||
18 | const DEFAULT_FILTER_ALIASES = [ |
||
19 | 'array' => '\\TraderInteractive\\Filter\\Arrays::filter', |
||
20 | 'arrayize' => '\\TraderInteractive\\Filter\\Arrays::arrayize', |
||
21 | 'bool' => '\\TraderInteractive\\Filter\\Booleans::filter', |
||
22 | 'bool-convert' => '\\TraderInteractive\\Filter\\Booleans::convert', |
||
23 | 'concat' => '\\TraderInteractive\\Filter\\Strings::concat', |
||
24 | 'date' => '\\TraderInteractive\\Filter\\DateTime::filter', |
||
25 | 'date-format' => '\\TraderInteractive\\Filter\\DateTime::format', |
||
26 | 'email' => '\\TraderInteractive\\Filter\\Email::filter', |
||
27 | 'explode' => '\\TraderInteractive\\Filter\\Strings::explode', |
||
28 | 'flatten' => '\\TraderInteractive\\Filter\\Arrays::flatten', |
||
29 | 'float' => '\\TraderInteractive\\Filter\\Floats::filter', |
||
30 | 'in' => '\\TraderInteractive\\Filter\\Arrays::in', |
||
31 | 'int' => '\\TraderInteractive\\Filter\\Ints::filter', |
||
32 | 'ofArray' => '\\TraderInteractive\\Filterer::ofArray', |
||
33 | 'ofArrays' => '\\TraderInteractive\\Filterer::ofArrays', |
||
34 | 'ofScalars' => '\\TraderInteractive\\Filterer::ofScalars', |
||
35 | 'redact' => '\\TraderInteractive\\Filter\\Strings::redact', |
||
36 | 'string' => '\\TraderInteractive\\Filter\\Strings::filter', |
||
37 | 'strip-tags' => '\\TraderInteractive\\Filter\\Strings::stripTags', |
||
38 | 'timezone' => '\\TraderInteractive\\Filter\\DateTimeZone::filter', |
||
39 | 'translate' => '\\TraderInteractive\\Filter\\Strings::translate', |
||
40 | 'uint' => '\\TraderInteractive\\Filter\\UnsignedInt::filter', |
||
41 | 'url' => '\\TraderInteractive\\Filter\\Url::filter', |
||
42 | ]; |
||
43 | |||
44 | /** |
||
45 | * @var array |
||
46 | */ |
||
47 | const DEFAULT_OPTIONS = [ |
||
48 | 'allowUnknowns' => false, |
||
49 | 'defaultRequired' => false, |
||
50 | 'responseType' => self::RESPONSE_TYPE_ARRAY, |
||
51 | ]; |
||
52 | |||
53 | /** |
||
54 | * @var string |
||
55 | */ |
||
56 | const RESPONSE_TYPE_ARRAY = 'array'; |
||
57 | |||
58 | /** |
||
59 | * @var string |
||
60 | */ |
||
61 | const RESPONSE_TYPE_FILTER = FilterResponse::class; |
||
62 | |||
63 | /** |
||
64 | * @var array |
||
65 | */ |
||
66 | private static $registeredFilterAliases = self::DEFAULT_FILTER_ALIASES; |
||
67 | |||
68 | /** |
||
69 | * @var array |
||
70 | */ |
||
71 | private $filterAliases; |
||
72 | |||
73 | /** |
||
74 | * @var array |
||
75 | */ |
||
76 | private $specification; |
||
77 | |||
78 | /** |
||
79 | * @var bool |
||
80 | */ |
||
81 | private $allowUnknowns; |
||
82 | |||
83 | /** |
||
84 | * @var bool |
||
85 | */ |
||
86 | private $defaultRequired; |
||
87 | |||
88 | /** |
||
89 | * @param array $specification The specification to apply to the value. |
||
90 | * @param array $options The options apply during filtering. |
||
91 | * 'allowUnknowns' (default false) true to allow or false to treat as error. |
||
92 | * 'defaultRequired' (default false) true to make fields required by default. |
||
93 | * @param array|null $filterAliases The filter aliases to accept. |
||
94 | * |
||
95 | * @throws InvalidArgumentException if 'allowUnknowns' option was not a bool |
||
96 | * @throws InvalidArgumentException if 'defaultRequired' option was not a bool |
||
97 | */ |
||
98 | public function __construct(array $specification, array $options = [], array $filterAliases = null) |
||
107 | |||
108 | /** |
||
109 | * @param mixed $input The input to filter. |
||
110 | * |
||
111 | * @return FilterResponse |
||
112 | * |
||
113 | * @throws InvalidArgumentException Thrown if the filters for a field were not an array. |
||
114 | * @throws InvalidArgumentException Thrown if any one filter for a field was not an array. |
||
115 | * @throws InvalidArgumentException Thrown if the 'required' value for a field was not a bool. |
||
116 | */ |
||
117 | public function execute(array $input) : FilterResponse |
||
170 | |||
171 | /** |
||
172 | * @return array |
||
173 | * |
||
174 | * @see FiltererInterface::getAliases |
||
175 | */ |
||
176 | public function getAliases() : array |
||
180 | |||
181 | /** |
||
182 | * @return array |
||
183 | * |
||
184 | * @see FiltererInterface::getSpecification |
||
185 | */ |
||
186 | public function getSpecification() : array |
||
190 | |||
191 | /** |
||
192 | * @param array $filterAliases |
||
193 | * |
||
194 | * @return FiltererInterface |
||
195 | * |
||
196 | * @see FiltererInterface::withAliases |
||
197 | */ |
||
198 | public function withAliases(array $filterAliases) : FiltererInterface |
||
202 | |||
203 | /** |
||
204 | * @param array $specification |
||
205 | * |
||
206 | * @return FiltererInterface |
||
207 | * |
||
208 | * @see FiltererInterface::withSpecification |
||
209 | */ |
||
210 | public function withSpecification(array $specification) : FiltererInterface |
||
214 | |||
215 | /** |
||
216 | * @return array |
||
217 | */ |
||
218 | private function getOptions() : array |
||
225 | |||
226 | /** |
||
227 | * Example: |
||
228 | * <pre> |
||
229 | * <?php |
||
230 | * class AppendFilter |
||
231 | * { |
||
232 | * public function filter($value, $extraArg) |
||
233 | * { |
||
234 | * return $value . $extraArg; |
||
235 | * } |
||
236 | * } |
||
237 | * $appendFilter = new AppendFilter(); |
||
238 | * |
||
239 | * $trimFunc = function($val) { return trim($val); }; |
||
240 | * |
||
241 | * list($status, $result, $error, $unknowns) = TraderInteractive\Filterer::filter( |
||
242 | * [ |
||
243 | * 'field one' => [[$trimFunc], ['substr', 0, 3], [[$appendFilter, 'filter'], 'boo']], |
||
244 | * 'field two' => ['required' => true, ['floatval']], |
||
245 | * 'field three' => ['required' => false, ['float']], |
||
246 | * 'field four' => ['required' => true, 'default' => 1, ['uint']], |
||
247 | * ], |
||
248 | * ['field one' => ' abcd', 'field two' => '3.14'] |
||
249 | * ); |
||
250 | * |
||
251 | * var_dump($status); |
||
252 | * var_dump($result); |
||
253 | * var_dump($error); |
||
254 | * var_dump($unknowns); |
||
255 | * </pre> |
||
256 | * prints: |
||
257 | * <pre> |
||
258 | * bool(true) |
||
259 | * array(3) { |
||
260 | * 'field one' => |
||
261 | * string(6) "abcboo" |
||
262 | * 'field two' => |
||
263 | * double(3.14) |
||
264 | * 'field four' => |
||
265 | * int(1) |
||
266 | * } |
||
267 | * NULL |
||
268 | * array(0) { |
||
269 | * } |
||
270 | * </pre> |
||
271 | * |
||
272 | * @param array $specification The specification to apply to the input. |
||
273 | * @param array $input The input the apply the specification to. |
||
274 | * @param array $options The options apply during filtering. |
||
275 | * 'allowUnknowns' (default false) true to allow or false to treat as error. |
||
276 | * 'defaultRequired' (default false) true to make fields required by default. |
||
277 | * 'responseType' (default RESPONSE_TYPE_ARRAY) |
||
278 | * Determines the return type, as described in the return section. |
||
279 | * |
||
280 | * @return array|FilterResponse If 'responseType' option is RESPONSE_TYPE_ARRAY: |
||
281 | * On success: [true, $input filtered, null, array of unknown fields] |
||
282 | * On error: [false, null, 'error message', array of unknown fields] |
||
283 | * If 'responseType' option is RESPONSE_TYPE_FILTER: a FilterResponse instance |
||
284 | * |
||
285 | * @throws Exception |
||
286 | * @throws InvalidArgumentException Thrown if the 'allowUnknowns' option was not a bool |
||
287 | * @throws InvalidArgumentException Thrown if the 'defaultRequired' option was not a bool |
||
288 | * @throws InvalidArgumentException Thrown if the 'responseType' option was not a recognized type. |
||
289 | * @throws InvalidArgumentException Thrown if the filters for a field were not an array. |
||
290 | * @throws InvalidArgumentException Thrown if any one filter for a field was not an array. |
||
291 | * @throws InvalidArgumentException Thrown if the 'required' value for a field was not a bool. |
||
292 | * |
||
293 | * @see FiltererInterface::getSpecification For more information on specifications. |
||
294 | */ |
||
295 | public static function filter(array $specification, array $input, array $options = []) |
||
305 | |||
306 | /** |
||
307 | * Return the filter aliases. |
||
308 | * |
||
309 | * @return array array where keys are aliases and values pass is_callable(). |
||
310 | */ |
||
311 | public static function getFilterAliases() : array |
||
315 | |||
316 | /** |
||
317 | * Set the filter aliases. |
||
318 | * |
||
319 | * @param array $aliases array where keys are aliases and values pass is_callable(). |
||
320 | * @return void |
||
321 | * |
||
322 | * @throws Exception Thrown if any of the given $aliases is not valid. @see registerAlias() |
||
323 | */ |
||
324 | public static function setFilterAliases(array $aliases) |
||
337 | |||
338 | /** |
||
339 | * Register a new alias with the Filterer |
||
340 | * |
||
341 | * @param string|int $alias the alias to register |
||
342 | * @param callable $filter the aliased callable filter |
||
343 | * @param bool $overwrite Flag to overwrite existing alias if it exists |
||
344 | * |
||
345 | * @return void |
||
346 | * |
||
347 | * @throws \InvalidArgumentException if $alias was not a string or int |
||
348 | * @throws Exception if $overwrite is false and $alias exists |
||
349 | */ |
||
350 | public static function registerAlias($alias, callable $filter, bool $overwrite = false) |
||
356 | |||
357 | /** |
||
358 | * Filter an array by applying filters to each member |
||
359 | * |
||
360 | * @param array $values an array to be filtered. Use the Arrays::filter() before this method to ensure counts when |
||
361 | * you pass into Filterer |
||
362 | * @param array $filters filters with each specified the same as in @see self::filter. |
||
363 | * Eg [['string', false, 2], ['uint']] |
||
364 | * |
||
365 | * @return array the filtered $values |
||
366 | * |
||
367 | * @throws FilterException if any member of $values fails filtering |
||
368 | */ |
||
369 | public static function ofScalars(array $values, array $filters) : array |
||
383 | |||
384 | /** |
||
385 | * Filter an array by applying filters to each member |
||
386 | * |
||
387 | * @param array $values as array to be filtered. Use the Arrays::filter() before this method to ensure counts when |
||
388 | * you pass into Filterer |
||
389 | * @param array $spec spec to apply to each $values member, specified the same as in @see self::filter. |
||
390 | * Eg ['key' => ['required' => true, ['string', false], ['unit']], 'key2' => ...] |
||
391 | * |
||
392 | * @return array the filtered $values |
||
393 | * |
||
394 | * @throws Exception if any member of $values fails filtering |
||
395 | */ |
||
396 | public static function ofArrays(array $values, array $spec) : array |
||
421 | |||
422 | /** |
||
423 | * Filter $value by using a Filterer $spec and Filterer's default options. |
||
424 | * |
||
425 | * @param array $value array to be filtered. Use the Arrays::filter() before this method to ensure counts when you |
||
426 | * pass into Filterer |
||
427 | * @param array $spec spec to apply to $value, specified the same as in @see self::filter. |
||
428 | * Eg ['key' => ['required' => true, ['string', false], ['unit']], 'key2' => ...] |
||
429 | * |
||
430 | * @return array the filtered $value |
||
431 | * |
||
432 | * @throws FilterException if $value fails filtering |
||
433 | */ |
||
434 | public static function ofArray(array $value, array $spec) : array |
||
443 | |||
444 | private static function assertIfStringOrInt($alias) |
||
450 | |||
451 | private static function assertIfAliasExists($alias, bool $overwrite) |
||
457 | |||
458 | private static function checkForUnknowns(array $leftOverInput, array $errors) : array |
||
466 | |||
467 | private static function handleAllowUnknowns(bool $allowUnknowns, array $leftOverInput, array $errors) : array |
||
475 | |||
476 | private static function handleRequiredFields(bool $required, string $field, array $errors) : array |
||
483 | |||
484 | private static function getRequired($filters, $defaultRequired, $field) : bool |
||
493 | |||
494 | private static function assertFiltersIsAnArray($filters, string $field) |
||
500 | |||
501 | private static function handleCustomError( |
||
520 | |||
521 | private static function assertFunctionIsCallable($function, string $field) |
||
529 | |||
530 | private static function handleFilterAliases($function, $filterAliases) |
||
538 | |||
539 | private static function assertFilterIsNotArray($filter, string $field) |
||
545 | |||
546 | private static function validateCustomError(array &$filters, string $field) |
||
560 | |||
561 | private static function getAllowUnknowns(array $options) : bool |
||
570 | |||
571 | private static function getDefaultRequired(array $options) : bool |
||
580 | |||
581 | /** |
||
582 | * @param string $responseType The type of object that should be returned. |
||
583 | * @param FilterResponse $filterResponse The filter response to generate the typed response from. |
||
584 | * |
||
585 | * @return array|FilterResponse |
||
586 | * |
||
587 | * @see filter For more information on how responseType is handled and returns are structured. |
||
588 | */ |
||
589 | private static function generateFilterResponse(string $responseType, FilterResponse $filterResponse) |
||
606 | } |
||
607 |
Our type inference engine has found an assignment of a scalar value (like a string, an integer or null) to a property which is an array.
Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property.
To type hint that a parameter can be either an array or null, you can set a type hint of array and a default value of null. The PHP interpreter will then accept both an array or null for that parameter.
The function can be called with either null or an array for the parameter
$needle
but will only accept an array as$haystack
.