Complex classes like ViewableData 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 ViewableData, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
26 | class ViewableData extends Object implements IteratorAggregate |
||
27 | { |
||
28 | |||
29 | /** |
||
30 | * An array of objects to cast certain fields to. This is set up as an array in the format: |
||
31 | * |
||
32 | * <code> |
||
33 | * public static $casting = array ( |
||
34 | * 'FieldName' => 'ClassToCastTo(Arguments)' |
||
35 | * ); |
||
36 | * </code> |
||
37 | * |
||
38 | * @var array |
||
39 | * @config |
||
40 | */ |
||
41 | private static $casting = array( |
||
42 | 'CSSClasses' => 'Varchar' |
||
43 | ); |
||
44 | |||
45 | /** |
||
46 | * The default object to cast scalar fields to if casting information is not specified, and casting to an object |
||
47 | * is required. |
||
48 | * |
||
49 | * @var string |
||
50 | * @config |
||
51 | */ |
||
52 | private static $default_cast = 'Text'; |
||
53 | |||
54 | /** |
||
55 | * @var array |
||
56 | */ |
||
57 | private static $casting_cache = array(); |
||
58 | |||
59 | // ----------------------------------------------------------------------------------------------------------------- |
||
60 | |||
61 | /** |
||
62 | * A failover object to attempt to get data from if it is not present on this object. |
||
63 | * |
||
64 | * @var ViewableData |
||
65 | */ |
||
66 | protected $failover; |
||
67 | |||
68 | /** |
||
69 | * @var ViewableData |
||
70 | */ |
||
71 | protected $customisedObject; |
||
72 | |||
73 | /** |
||
74 | * @var array |
||
75 | */ |
||
76 | private $objCache = array(); |
||
77 | |||
78 | // ----------------------------------------------------------------------------------------------------------------- |
||
79 | |||
80 | // FIELD GETTERS & SETTERS ----------------------------------------------------------------------------------------- |
||
81 | |||
82 | /** |
||
83 | * Check if a field exists on this object or its failover. |
||
84 | * Note that, unlike the core isset() implementation, this will return true if the property is defined |
||
85 | * and set to null. |
||
86 | * |
||
87 | * @param string $property |
||
88 | * @return bool |
||
89 | */ |
||
90 | public function __isset($property) |
||
103 | |||
104 | /** |
||
105 | * Get the value of a property/field on this object. This will check if a method called get{$property} exists, then |
||
106 | * check if a field is available using {@link ViewableData::getField()}, then fall back on a failover object. |
||
107 | * |
||
108 | * @param string $property |
||
109 | * @return mixed |
||
110 | */ |
||
111 | public function __get($property) |
||
124 | |||
125 | /** |
||
126 | * Set a property/field on this object. This will check for the existence of a method called set{$property}, then |
||
127 | * use the {@link ViewableData::setField()} method. |
||
128 | * |
||
129 | * @param string $property |
||
130 | * @param mixed $value |
||
131 | */ |
||
132 | public function __set($property, $value) |
||
141 | |||
142 | /** |
||
143 | * Set a failover object to attempt to get data from if it is not present on this object. |
||
144 | * |
||
145 | * @param ViewableData $failover |
||
146 | */ |
||
147 | public function setFailover(ViewableData $failover) |
||
157 | |||
158 | /** |
||
159 | * Get the current failover object if set |
||
160 | * |
||
161 | * @return ViewableData|null |
||
162 | */ |
||
163 | public function getFailover() |
||
167 | |||
168 | /** |
||
169 | * Check if a field exists on this object. This should be overloaded in child classes. |
||
170 | * |
||
171 | * @param string $field |
||
172 | * @return bool |
||
173 | */ |
||
174 | public function hasField($field) |
||
178 | |||
179 | /** |
||
180 | * Get the value of a field on this object. This should be overloaded in child classes. |
||
181 | * |
||
182 | * @param string $field |
||
183 | * @return mixed |
||
184 | */ |
||
185 | public function getField($field) |
||
189 | |||
190 | /** |
||
191 | * Set a field on this object. This should be overloaded in child classes. |
||
192 | * |
||
193 | * @param string $field |
||
194 | * @param mixed $value |
||
195 | * @return $this |
||
196 | */ |
||
197 | public function setField($field, $value) |
||
203 | |||
204 | // ----------------------------------------------------------------------------------------------------------------- |
||
205 | |||
206 | /** |
||
207 | * Add methods from the {@link ViewableData::$failover} object, as well as wrapping any methods prefixed with an |
||
208 | * underscore into a {@link ViewableData::cachedCall()}. |
||
209 | * |
||
210 | * @throws LogicException |
||
211 | */ |
||
212 | public function defineMethods() |
||
227 | |||
228 | /** |
||
229 | * Merge some arbitrary data in with this object. This method returns a {@link ViewableData_Customised} instance |
||
230 | * with references to both this and the new custom data. |
||
231 | * |
||
232 | * Note that any fields you specify will take precedence over the fields on this object. |
||
233 | * |
||
234 | * @param array|ViewableData $data |
||
235 | * @return ViewableData_Customised |
||
236 | */ |
||
237 | public function customise($data) |
||
251 | |||
252 | /** |
||
253 | * @return ViewableData |
||
254 | */ |
||
255 | public function getCustomisedObj() |
||
259 | |||
260 | /** |
||
261 | * @param ViewableData $object |
||
262 | */ |
||
263 | public function setCustomisedObj(ViewableData $object) |
||
267 | |||
268 | // CASTING --------------------------------------------------------------------------------------------------------- |
||
269 | |||
270 | /** |
||
271 | * Return the "casting helper" (a piece of PHP code that when evaluated creates a casted value object) |
||
272 | * for a field on this object. This helper will be a subclass of DBField. |
||
273 | * |
||
274 | * @param string $field |
||
275 | * @return string Casting helper As a constructor pattern, and may include arguments. |
||
276 | */ |
||
277 | public function castingHelper($field) |
||
302 | |||
303 | /** |
||
304 | * Get the class name a field on this object will be casted to. |
||
305 | * |
||
306 | * @param string $field |
||
307 | * @return string |
||
308 | */ |
||
309 | public function castingClass($field) |
||
315 | |||
316 | /** |
||
317 | * Return the string-format type for the given field. |
||
318 | * |
||
319 | * @param string $field |
||
320 | * @return string 'xml'|'raw' |
||
321 | */ |
||
322 | public function escapeTypeForField($field) |
||
332 | |||
333 | // TEMPLATE ACCESS LAYER ------------------------------------------------------------------------------------------- |
||
334 | |||
335 | /** |
||
336 | * Render this object into the template, and get the result as a string. You can pass one of the following as the |
||
337 | * $template parameter: |
||
338 | * - a template name (e.g. Page) |
||
339 | * - an array of possible template names - the first valid one will be used |
||
340 | * - an SSViewer instance |
||
341 | * |
||
342 | * @param string|array|SSViewer $template the template to render into |
||
343 | * @param array $customFields fields to customise() the object with before rendering |
||
344 | * @return DBHTMLText |
||
345 | */ |
||
346 | public function renderWith($template, $customFields = null) |
||
365 | |||
366 | /** |
||
367 | * Generate the cache name for a field |
||
368 | * |
||
369 | * @param string $fieldName Name of field |
||
370 | * @param array $arguments List of optional arguments given |
||
371 | * @return string |
||
372 | */ |
||
373 | protected function objCacheName($fieldName, $arguments) |
||
379 | |||
380 | /** |
||
381 | * Get a cached value from the field cache |
||
382 | * |
||
383 | * @param string $key Cache key |
||
384 | * @return mixed |
||
385 | */ |
||
386 | protected function objCacheGet($key) |
||
393 | |||
394 | /** |
||
395 | * Store a value in the field cache |
||
396 | * |
||
397 | * @param string $key Cache key |
||
398 | * @param mixed $value |
||
399 | * @return $this |
||
400 | */ |
||
401 | protected function objCacheSet($key, $value) |
||
406 | |||
407 | /** |
||
408 | * Clear object cache |
||
409 | * |
||
410 | * @return $this |
||
411 | */ |
||
412 | protected function objCacheClear() |
||
417 | |||
418 | /** |
||
419 | * Get the value of a field on this object, automatically inserting the value into any available casting objects |
||
420 | * that have been specified. |
||
421 | * |
||
422 | * @param string $fieldName |
||
423 | * @param array $arguments |
||
424 | * @param bool $cache Cache this object |
||
425 | * @param string $cacheName a custom cache name |
||
426 | * @return Object|DBField |
||
427 | */ |
||
428 | public function obj($fieldName, $arguments = [], $cache = false, $cacheName = null) |
||
463 | |||
464 | /** |
||
465 | * A simple wrapper around {@link ViewableData::obj()} that automatically caches the result so it can be used again |
||
466 | * without re-running the method. |
||
467 | * |
||
468 | * @param string $field |
||
469 | * @param array $arguments |
||
470 | * @param string $identifier an optional custom cache identifier |
||
471 | * @return Object|DBField |
||
472 | */ |
||
473 | public function cachedCall($field, $arguments = [], $identifier = null) |
||
477 | |||
478 | /** |
||
479 | * Checks if a given method/field has a valid value. If the result is an object, this will return the result of the |
||
480 | * exists method, otherwise will check if the result is not just an empty paragraph tag. |
||
481 | * |
||
482 | * @param string $field |
||
483 | * @param array $arguments |
||
484 | * @param bool $cache |
||
485 | * @return bool |
||
486 | */ |
||
487 | public function hasValue($field, $arguments = [], $cache = true) |
||
492 | |||
493 | /** |
||
494 | * Get the string value of a field on this object that has been suitable escaped to be inserted directly into a |
||
495 | * template. |
||
496 | * |
||
497 | * @param string $field |
||
498 | * @param array $arguments |
||
499 | * @param bool $cache |
||
500 | * @return string |
||
501 | */ |
||
502 | public function XML_val($field, $arguments = [], $cache = false) |
||
508 | |||
509 | /** |
||
510 | * Get an array of XML-escaped values by field name |
||
511 | * |
||
512 | * @param array $fields an array of field names |
||
513 | * @return array |
||
514 | */ |
||
515 | public function getXMLValues($fields) |
||
525 | |||
526 | // ITERATOR SUPPORT ------------------------------------------------------------------------------------------------ |
||
527 | |||
528 | /** |
||
529 | * Return a single-item iterator so you can iterate over the fields of a single record. |
||
530 | * |
||
531 | * This is useful so you can use a single record inside a <% control %> block in a template - and then use |
||
532 | * to access individual fields on this object. |
||
533 | * |
||
534 | * @return ArrayIterator |
||
535 | */ |
||
536 | public function getIterator() |
||
540 | |||
541 | // UTILITY METHODS ------------------------------------------------------------------------------------------------- |
||
542 | |||
543 | /** |
||
544 | * When rendering some objects it is necessary to iterate over the object being rendered, to do this, you need |
||
545 | * access to itself. |
||
546 | * |
||
547 | * @return ViewableData |
||
548 | */ |
||
549 | public function Me() |
||
553 | |||
554 | /** |
||
555 | * Get part of the current classes ancestry to be used as a CSS class. |
||
556 | * |
||
557 | * This method returns an escaped string of CSS classes representing the current classes ancestry until it hits a |
||
558 | * stop point - e.g. "Page DataObject ViewableData". |
||
559 | * |
||
560 | * @param string $stopAtClass the class to stop at (default: ViewableData) |
||
561 | * @return string |
||
562 | * @uses ClassInfo |
||
563 | */ |
||
564 | public function CSSClasses($stopAtClass = self::class) |
||
587 | |||
588 | /** |
||
589 | * Return debug information about this object that can be rendered into a template |
||
590 | * |
||
591 | * @return ViewableData_Debugger |
||
592 | */ |
||
593 | public function Debug() |
||
597 | } |
||
598 |
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.
If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.
In this case you can add the
@ignore
PhpDoc annotation to the duplicate definition and it will be ignored.