Complex classes like DataFilter 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 DataFilter, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
122 | class DataFilter extends Model |
||
123 | { |
||
124 | const TYPE_INTEGER = 'integer'; |
||
125 | const TYPE_FLOAT = 'float'; |
||
126 | const TYPE_BOOLEAN = 'boolean'; |
||
127 | const TYPE_STRING = 'string'; |
||
128 | const TYPE_ARRAY = 'array'; |
||
129 | |||
130 | /** |
||
131 | * @var string name of the attribute that handles filter value. |
||
132 | * The name is used to load data via [[load()]] method. |
||
133 | */ |
||
134 | public $filterAttributeName = 'filter'; |
||
135 | /** |
||
136 | * @var string label for the filter attribute specified via [[filterAttributeName]]. |
||
137 | * It will be used during error messages composition. |
||
138 | */ |
||
139 | public $filterAttributeLabel; |
||
140 | /** |
||
141 | * @var array keywords or expressions that could be used in a filter. |
||
142 | * Array keys are the expressions used in raw filter value obtained from user request. |
||
143 | * Array values are internal build keys used in this class methods. |
||
144 | * |
||
145 | * Any unspecified keyword will not be recognized as a filter control and will be treated as |
||
146 | * an attribute name. Thus you should avoid conflicts between control keywords and attribute names. |
||
147 | * For example: in case you have control keyword 'like' and an attribute named 'like', specifying condition |
||
148 | * for such attribute will be impossible. |
||
149 | * |
||
150 | * You may specify several keywords for the same filter build key, creating multiple aliases. For example: |
||
151 | * |
||
152 | * ```php |
||
153 | * [ |
||
154 | * 'eq' => '=', |
||
155 | * '=' => '=', |
||
156 | * '==' => '=', |
||
157 | * '===' => '=', |
||
158 | * // ... |
||
159 | * ] |
||
160 | * ``` |
||
161 | * |
||
162 | * > Note: while specifying filter controls take actual data exchange format, which your API uses, in mind. |
||
163 | * Make sure each specified control keyword is valid for the format. For example, in XML tag name can start |
||
164 | * only with a letter character, thus controls like `>`, '=' or `$gt` will break the XML schema. |
||
165 | */ |
||
166 | public $filterControls = [ |
||
167 | 'and' => 'AND', |
||
168 | 'or' => 'OR', |
||
169 | 'not' => 'NOT', |
||
170 | 'lt' => '<', |
||
171 | 'gt' => '>', |
||
172 | 'lte' => '<=', |
||
173 | 'gte' => '>=', |
||
174 | 'eq' => '=', |
||
175 | 'neq' => '!=', |
||
176 | 'in' => 'IN', |
||
177 | 'nin' => 'NOT IN', |
||
178 | 'like' => 'LIKE', |
||
179 | ]; |
||
180 | /** |
||
181 | * @var array maps filter condition keywords to validation methods. |
||
182 | * These methods are used by [[validateCondition()]] to validate raw filter conditions. |
||
183 | */ |
||
184 | public $conditionValidators = [ |
||
185 | 'AND' => 'validateConjunctionCondition', |
||
186 | 'OR' => 'validateConjunctionCondition', |
||
187 | 'NOT' => 'validateBlockCondition', |
||
188 | '<' => 'validateOperatorCondition', |
||
189 | '>' => 'validateOperatorCondition', |
||
190 | '<=' => 'validateOperatorCondition', |
||
191 | '>=' => 'validateOperatorCondition', |
||
192 | '=' => 'validateOperatorCondition', |
||
193 | '!=' => 'validateOperatorCondition', |
||
194 | 'IN' => 'validateOperatorCondition', |
||
195 | 'NOT IN' => 'validateOperatorCondition', |
||
196 | 'LIKE' => 'validateOperatorCondition', |
||
197 | ]; |
||
198 | /** |
||
199 | * @var array specifies the list of supported search attribute types per each operator. |
||
200 | * This field should be in format: 'operatorKeyword' => ['type1', 'type2' ...]. |
||
201 | * Supported types list can be specified as `*`, which indicates that operator supports all types available. |
||
202 | * Any unspecified keyword will not be considered as a valid operator. |
||
203 | */ |
||
204 | public $operatorTypes = [ |
||
205 | '<' => [self::TYPE_INTEGER, self::TYPE_FLOAT], |
||
206 | '>' => [self::TYPE_INTEGER, self::TYPE_FLOAT], |
||
207 | '<=' => [self::TYPE_INTEGER, self::TYPE_FLOAT], |
||
208 | '>=' => [self::TYPE_INTEGER, self::TYPE_FLOAT], |
||
209 | '=' => '*', |
||
210 | '!=' => '*', |
||
211 | 'IN' => '*', |
||
212 | 'NOT IN' => '*', |
||
213 | 'LIKE' => [self::TYPE_STRING], |
||
214 | ]; |
||
215 | /** |
||
216 | * @var array list of operators keywords, which should accept multiple values. |
||
217 | */ |
||
218 | public $multiValueOperators = [ |
||
219 | 'IN', |
||
220 | 'NOT IN', |
||
221 | ]; |
||
222 | /** |
||
223 | * @var array actual attribute names to be used in searched condition, in format: [filterAttribute => actualAttribute]. |
||
224 | * For example, in case of using table joins in the search query, attribute map may look like the following: |
||
225 | * |
||
226 | * ```php |
||
227 | * [ |
||
228 | * 'authorName' => '{{author}}.[[name]]' |
||
229 | * ] |
||
230 | * ``` |
||
231 | * |
||
232 | * Attribute map will be applied to filter condition in [[normalize()]] method. |
||
233 | */ |
||
234 | public $attributeMap = []; |
||
235 | |||
236 | /** |
||
237 | * @var array|\Closure list of error messages responding to invalid filter structure, in format: `[errorKey => message]`. |
||
238 | */ |
||
239 | private $_errorMessages = [ |
||
240 | 'invalidFilter' => 'The format of {filter} is invalid.', |
||
241 | 'operatorRequireMultipleOperands' => "Operator '{operator}' requires multiple operands.", |
||
242 | 'unknownAttribute' => "Unknown filter attribute '{attribute}'", |
||
243 | 'invalidAttributeValueFormat' => "Condition for '{attribute}' should be either a value or a valid operator specification.", |
||
244 | 'operatorRequireAttribute' => "Operator '{operator}' must be used with a search attribute.", |
||
245 | 'unsupportedOperatorType' => "'{attribute}' does not support '{operator}' operator.", |
||
246 | ]; |
||
247 | /** |
||
248 | * @var mixed raw filter specification. |
||
249 | */ |
||
250 | private $_filter; |
||
251 | /** |
||
252 | * @var Model|array|string|callable model to be used for filter attributes validation. |
||
253 | */ |
||
254 | private $_searchModel; |
||
255 | /** |
||
256 | * @var array list of search attribute types in format: attributeName => type |
||
257 | */ |
||
258 | private $_searchAttributeTypes; |
||
259 | |||
260 | |||
261 | /** |
||
262 | * @return mixed raw filter value. |
||
263 | */ |
||
264 | 28 | public function getFilter() |
|
268 | |||
269 | /** |
||
270 | * @param mixed $filter raw filter value. |
||
271 | */ |
||
272 | 28 | public function setFilter($filter) |
|
276 | |||
277 | /** |
||
278 | * @return Model model instance. |
||
279 | * @throws InvalidConfigException on invalid configuration. |
||
280 | */ |
||
281 | 15 | public function getSearchModel() |
|
292 | |||
293 | /** |
||
294 | * @param Model|array|string|callable $model model instance or its DI compatible configuration. |
||
295 | * @throws InvalidConfigException on invalid configuration. |
||
296 | */ |
||
297 | 28 | public function setSearchModel($model) |
|
304 | |||
305 | /** |
||
306 | * @return array search attribute type map. |
||
307 | */ |
||
308 | 14 | public function getSearchAttributeTypes() |
|
315 | |||
316 | /** |
||
317 | * @param array|null $searchAttributeTypes search attribute type map. |
||
318 | */ |
||
319 | public function setSearchAttributeTypes($searchAttributeTypes) |
||
323 | |||
324 | /** |
||
325 | * Composes default value for [[searchAttributeTypes]] from the [[searchModel]] validation rules. |
||
326 | * @return array attribute type map. |
||
327 | */ |
||
328 | 14 | protected function detectSearchAttributeTypes() |
|
358 | |||
359 | /** |
||
360 | * @return array error messages in format `[errorKey => message]`. |
||
361 | */ |
||
362 | 5 | public function getErrorMessages() |
|
376 | |||
377 | /** |
||
378 | * Sets the list of error messages responding to invalid filter structure, in format: `[errorKey => message]`. |
||
379 | * Message may contain placeholders that will be populated depending on the message context. |
||
380 | * For each message a `{filter}` placeholder is available referring to the label for [[filterAttributeName]] attribute. |
||
381 | * @param array|\Closure $errorMessages error messages in `[errorKey => message]` format, or a PHP callback returning them. |
||
382 | */ |
||
383 | 1 | public function setErrorMessages($errorMessages) |
|
390 | |||
391 | /** |
||
392 | * Returns default values for [[errorMessages]]. |
||
393 | * @return array default error messages in `[errorKey => message]` format. |
||
394 | */ |
||
395 | 1 | protected function defaultErrorMessages() |
|
406 | |||
407 | /** |
||
408 | * Parses content of the message from [[errorMessages]], specified by message key. |
||
409 | * @param string $messageKey message key. |
||
410 | * @param array $params params to be parsed into the message. |
||
411 | * @return string composed message string. |
||
412 | */ |
||
413 | 4 | protected function parseErrorMessage($messageKey, $params = []) |
|
431 | |||
432 | // Model specific: |
||
433 | |||
434 | /** |
||
435 | * @inheritdoc |
||
436 | */ |
||
437 | public function attributes() |
||
443 | |||
444 | /** |
||
445 | * @inheritdoc |
||
446 | */ |
||
447 | 1 | public function formName() |
|
451 | |||
452 | /** |
||
453 | * @inheritdoc |
||
454 | */ |
||
455 | 21 | public function rules() |
|
461 | |||
462 | /** |
||
463 | * @inheritdoc |
||
464 | */ |
||
465 | 4 | public function attributeLabels() |
|
471 | |||
472 | // Validation: |
||
473 | |||
474 | /** |
||
475 | * Validates filter attribute value to match filer condition specification. |
||
476 | */ |
||
477 | 20 | public function validateFilter() |
|
484 | |||
485 | /** |
||
486 | * Validates filter condition. |
||
487 | * @param mixed $condition raw filter condition. |
||
488 | */ |
||
489 | 19 | protected function validateCondition($condition) |
|
511 | |||
512 | /** |
||
513 | * Validates conjunction condition that consists of multiple independent ones. |
||
514 | * This covers such operators as `and` and `or`. |
||
515 | * @param string $operator raw operator control keyword. |
||
516 | * @param mixed $condition raw condition. |
||
517 | */ |
||
518 | 6 | protected function validateConjunctionCondition($operator, $condition) |
|
529 | |||
530 | /** |
||
531 | * Validates block condition that consists of a single condition. |
||
532 | * This covers such operators as `not`. |
||
533 | * @param string $operator raw operator control keyword. |
||
534 | * @param mixed $condition raw condition. |
||
535 | */ |
||
536 | 3 | protected function validateBlockCondition($operator, $condition) |
|
540 | |||
541 | /** |
||
542 | * Validates search condition for a particular attribute. |
||
543 | * @param string $attribute search attribute name. |
||
544 | * @param mixed $condition search condition. |
||
545 | */ |
||
546 | 14 | protected function validateAttributeCondition($attribute, $condition) |
|
578 | |||
579 | /** |
||
580 | * Validates operator condition. |
||
581 | * @param string $operator raw operator control keyword. |
||
582 | * @param mixed $condition attribute condition. |
||
583 | * @param string $attribute attribute name. |
||
584 | */ |
||
585 | 7 | protected function validateOperatorCondition($operator, $condition, $attribute = null) |
|
620 | |||
621 | /** |
||
622 | * Validates attribute value in the scope of [[model]]. |
||
623 | * @param string $attribute attribute name. |
||
624 | * @param mixed $value attribute value. |
||
625 | */ |
||
626 | 14 | protected function validateAttributeValue($attribute, $value) |
|
640 | |||
641 | /** |
||
642 | * Validates attribute value in the scope of [[searchModel]], applying attribute value filters if any. |
||
643 | * @param string $attribute attribute name. |
||
644 | * @param mixed $value attribute value. |
||
645 | * @return mixed filtered attribute value. |
||
646 | */ |
||
647 | 6 | protected function filterAttributeValue($attribute, $value) |
|
662 | |||
663 | // Build: |
||
664 | |||
665 | /** |
||
666 | * Builds actual filter specification form [[filter]] value. |
||
667 | * @param boolean $runValidation whether to perform validation (calling [[validate()]]) |
||
668 | * before building the filter. Defaults to `true`. If the validation fails, no filter will |
||
669 | * be built and this method will return `false`. |
||
670 | * @return mixed|false built actual filter value, or `false` if validation fails. |
||
671 | */ |
||
672 | 8 | public function build($runValidation = true) |
|
679 | |||
680 | /** |
||
681 | * Performs actual filter build. |
||
682 | * By default this method returns result of [[normalize()]]. |
||
683 | * The child class may override this method providing more specific implementation. |
||
684 | * @return mixed built actual filter value. |
||
685 | */ |
||
686 | protected function buildInternal() |
||
690 | |||
691 | /** |
||
692 | * Normalizes filter value, replacing raw keys according to [[filterControls]] and [[attributeMap]]. |
||
693 | * @param bool $runValidation whether to perform validation (calling [[validate()]]) |
||
694 | * before normalizing the filter. Defaults to `true`. If the validation fails, no filter will |
||
695 | * be processed and this method will return `false`. |
||
696 | * @return array|bool normalized filter value, or `false` if validation fails. |
||
697 | */ |
||
698 | 15 | public function normalize($runValidation = true) |
|
711 | |||
712 | /** |
||
713 | * Normalizes complex filter recursively. |
||
714 | * @param array $filter raw filter. |
||
715 | * @return array normalized filter. |
||
716 | */ |
||
717 | 11 | private function normalizeComplexFilter(array $filter) |
|
734 | |||
735 | // Property access: |
||
736 | |||
737 | /** |
||
738 | * @inheritdoc |
||
739 | */ |
||
740 | public function canGetProperty($name, $checkVars = true, $checkBehaviors = true) |
||
747 | |||
748 | /** |
||
749 | * @inheritdoc |
||
750 | */ |
||
751 | public function canSetProperty($name, $checkVars = true, $checkBehaviors = true) |
||
758 | |||
759 | /** |
||
760 | * @inheritdoc |
||
761 | */ |
||
762 | public function __get($name) |
||
770 | |||
771 | /** |
||
772 | * @inheritdoc |
||
773 | */ |
||
774 | 28 | public function __set($name, $value) |
|
782 | |||
783 | /** |
||
784 | * @inheritdoc |
||
785 | */ |
||
786 | public function __isset($name) |
||
794 | |||
795 | /** |
||
796 | * @inheritdoc |
||
797 | */ |
||
798 | public function __unset($name) |
||
806 | } |
||
807 |
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
.