Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like ModelVersionInfo 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 ModelVersionInfo, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
26 | class ModelVersionInfo |
||
27 | { |
||
28 | |||
29 | /** |
||
30 | * Constant used in the $_model_changes array to indicate that a model |
||
31 | * was completely new in this version |
||
32 | */ |
||
33 | const MODEL_ADDED = 'model_added_in_this_version'; |
||
34 | |||
35 | /** |
||
36 | * Top-level keys are versions (major and minor version numbers, eg "4.6") |
||
37 | * next-level keys are model names (eg "Event") that underwent some change in that version |
||
38 | * and the value is either Model_Version_Info::model_added (indicating the model is completely NEW in this version), |
||
39 | * or it's an array where the values are model field names, |
||
40 | * or API resource properties (ie, non-model fields that appear in REST API results) |
||
41 | * If a version is missing then we don't know anything about what changes it introduced from the previous version |
||
42 | * |
||
43 | * @var array |
||
44 | */ |
||
45 | protected $model_changes = array(); |
||
46 | |||
47 | /** |
||
48 | * top-level keys are version numbers, |
||
49 | * next-level keys are model CLASSNAMES (even parent classnames), |
||
50 | * and next-level keys are extra resource properties to attach to those models' resources, |
||
51 | * and next-level key-value pairs, where the keys are: |
||
52 | * 'raw', 'type', 'nullable', 'table_alias', 'table_column', 'always_available' |
||
53 | * |
||
54 | * @var array |
||
55 | */ |
||
56 | protected $resource_changes = array(); |
||
57 | |||
58 | /** |
||
59 | * @var string indicating what version of the API was requested |
||
60 | * (eg although core might be at version 4.8.11, they may have sent a request |
||
61 | * for 4.6) |
||
62 | */ |
||
63 | protected $requested_version = null; |
||
64 | |||
65 | /** |
||
66 | * Keys are model names, values are their classnames. |
||
67 | * We cache this so we only need to calculate this once per request |
||
68 | * |
||
69 | * @var array |
||
70 | */ |
||
71 | protected $cached_models_for_requested_version = null; |
||
72 | |||
73 | /** |
||
74 | * @var array |
||
75 | */ |
||
76 | protected $cached_model_changes_between_requested_version_and_current = null; |
||
77 | |||
78 | /** |
||
79 | * @var array |
||
80 | */ |
||
81 | protected $cached_resource_changes_between_requested_version_and_current = null; |
||
82 | |||
83 | /** |
||
84 | * 2d array where top-level keys are model names, 2nd-level keys are field names |
||
85 | * and values are the actual field objects |
||
86 | * |
||
87 | * @var array |
||
88 | */ |
||
89 | protected $cached_fields_on_models = array(); |
||
90 | |||
91 | |||
92 | |||
93 | /** |
||
94 | * Model_Version_Info constructor. |
||
95 | * |
||
96 | * @param string $requested_version |
||
97 | */ |
||
98 | public function __construct($requested_version) |
||
99 | { |
||
100 | $this->requested_version = (string)$requested_version; |
||
101 | $this->model_changes = array( |
||
102 | '4.8.29' => array( |
||
103 | //first version where the REST API is in EE core, so no need |
||
104 | //to specify how its different from the previous |
||
105 | ), |
||
106 | ); |
||
107 | //setup data for "extra" fields added onto resources which don't actually exist on models |
||
108 | $this->resource_changes = apply_filters( |
||
109 | 'FHEE__Model_Version_Info___construct__extra_resource_properties_for_models', |
||
110 | array() |
||
111 | ); |
||
112 | $defaults = array( |
||
113 | 'raw' => false, |
||
114 | 'type' => 'N/A', |
||
115 | 'nullable' => true, |
||
116 | 'table_alias' => 'N/A', |
||
117 | 'table_column' => 'N/A', |
||
118 | 'always_available' => true, |
||
119 | ); |
||
120 | foreach ($this->resource_changes as $version => $model_classnames) { |
||
121 | foreach ($model_classnames as $model_classname => $extra_fields) { |
||
122 | foreach ($extra_fields as $fieldname => $field_data) { |
||
123 | $this->resource_changes[$model_classname][$fieldname]['name'] = $fieldname; |
||
124 | foreach ($defaults as $attribute => $default_value) { |
||
125 | if (! isset($this->resource_changes[$model_classname][$fieldname][$attribute])) { |
||
126 | $this->resource_changes[$model_classname][$fieldname][$attribute] = $default_value; |
||
127 | } |
||
128 | } |
||
129 | } |
||
130 | } |
||
131 | } |
||
132 | } |
||
133 | |||
134 | |||
135 | |||
136 | /** |
||
137 | * Returns a slice of Model_Version_Info::model_changes()'s array |
||
138 | * indicating exactly what changes happened between the current core version, |
||
139 | * and the version requested |
||
140 | * |
||
141 | * @return array |
||
142 | */ |
||
143 | View Code Duplication | public function modelChangesBetweenRequestedVersionAndCurrent() |
|
156 | |||
157 | |||
158 | |||
159 | /** |
||
160 | * Returns a slice of Model_Version_Info::model_changes()'s array |
||
161 | * indicating exactly what changes happened between the current core version, |
||
162 | * and the version requested |
||
163 | * |
||
164 | * @return array |
||
165 | */ |
||
166 | View Code Duplication | public function resourceChangesBetweenRequestedVersionAndCurrent() |
|
179 | |||
180 | |||
181 | |||
182 | /** |
||
183 | * If a request was sent to 'wp-json/ee/v4.7/events' this would be '4.7' |
||
184 | * |
||
185 | * @return string like '4.6' |
||
186 | */ |
||
187 | public function requestedVersion() |
||
191 | |||
192 | |||
193 | |||
194 | /** |
||
195 | * Returns an array describing how the models have changed in each version of core |
||
196 | * that supports the API (starting at 4.6) |
||
197 | * Top-level keys are versions (major and minor version numbers, eg "4.6") |
||
198 | * next-level keys are model names (eg "Event") that underwent some change in that version |
||
199 | * and the value is either NULL (indicating the model is completely NEW in this version), |
||
200 | * or it's an array where fields are value names. |
||
201 | * If a version is missing then we don't know anything about what changes it introduced from the previous version |
||
202 | * |
||
203 | * @return array |
||
204 | */ |
||
205 | public function modelChanges() |
||
209 | |||
210 | |||
211 | |||
212 | /** |
||
213 | * Takes into account the requested version, and the current version, and |
||
214 | * what changed between the two, and tries to return. |
||
215 | * Analogous to EE_Registry::instance()->non_abstract_db_models |
||
216 | * |
||
217 | * @return array keys are model names, values are their classname |
||
218 | */ |
||
219 | public function modelsForRequestedVersion() |
||
238 | |||
239 | |||
240 | |||
241 | /** |
||
242 | * Determines if this is a valid model name in the requested version. |
||
243 | * Similar to EE_Registry::instance()->is_model_name(), but takes the requested |
||
244 | * version's models into account |
||
245 | * |
||
246 | * @param string $model_name eg 'Event' |
||
247 | * @return boolean |
||
248 | */ |
||
249 | public function isModelNameInThisVersion($model_name) |
||
258 | |||
259 | |||
260 | |||
261 | /** |
||
262 | * Wrapper for EE_Registry::instance()->load_model(), but takes the requested |
||
263 | * version's models into account |
||
264 | * |
||
265 | * @param string $model_name |
||
266 | * @return \EEM_Base |
||
267 | * @throws \EE_Error |
||
268 | */ |
||
269 | public function loadModel($model_name) |
||
286 | |||
287 | |||
288 | |||
289 | /** |
||
290 | * Gets all the fields that should exist on this model right now |
||
291 | * |
||
292 | * @param \EEM_Base $model |
||
293 | * @return array|\EE_Model_Field_Base[] |
||
294 | */ |
||
295 | public function fieldsOnModelInThisVersion($model) |
||
317 | |||
318 | |||
319 | |||
320 | /** |
||
321 | * Determines if $object is of one of the classes of $classes. Similar to |
||
322 | * in_array(), except this checks if $object is a subclass of the classnames provided |
||
323 | * in $classnames |
||
324 | * |
||
325 | * @param object $object |
||
326 | * @param array $classnames |
||
327 | * @return boolean |
||
328 | */ |
||
329 | public function isSubclassOfOne($object, $classnames) |
||
338 | |||
339 | |||
340 | |||
341 | /** |
||
342 | * Returns the list of model field classes that that the API basically ignores |
||
343 | * |
||
344 | * @return array |
||
345 | */ |
||
346 | public function fieldsIgnored() |
||
353 | |||
354 | |||
355 | |||
356 | /** |
||
357 | * If this field one that should be ignored by the API? |
||
358 | * |
||
359 | * @param EE_Model_Field_Base |
||
360 | * @return boolean |
||
361 | */ |
||
362 | public function fieldIsIgnored($field_obj) |
||
366 | |||
367 | |||
368 | |||
369 | /** |
||
370 | * Returns the list of model field classes that have a "raw" and non-raw formats. |
||
371 | * Normally the "raw" versions are only accessible to those who can edit them. |
||
372 | * |
||
373 | * @return array an array of EE_Model_Field_Base child classnames |
||
374 | */ |
||
375 | public function fieldsThatHaveRenderedFormat() |
||
382 | |||
383 | |||
384 | |||
385 | /** |
||
386 | * If this field one that has a raw format |
||
387 | * |
||
388 | * @param EE_Model_Field_Base |
||
389 | * @return boolean |
||
390 | */ |
||
391 | public function fieldHasRenderedFormat($field_obj) |
||
395 | |||
396 | |||
397 | |||
398 | /** |
||
399 | * Returns the list of model field classes that have a "_pretty" and non-pretty versions. |
||
400 | * The pretty version of the field is NOT query-able or editable, but requires no extra permissions |
||
401 | * to view |
||
402 | * |
||
403 | * @return array an array of EE_Model_Field_Base child classnames |
||
404 | */ |
||
405 | public function fieldsThatHavePrettyFormat() |
||
412 | |||
413 | |||
414 | |||
415 | /** |
||
416 | * If this field one that has a pretty equivalent |
||
417 | * |
||
418 | * @param EE_Model_Field_Base |
||
419 | * @return boolean |
||
420 | */ |
||
421 | public function fieldHasPrettyFormat($field_obj) |
||
425 | |||
426 | |||
427 | |||
428 | /** |
||
429 | * Returns an array describing what extra API resource properties have been added through the versions |
||
430 | * |
||
431 | * @return array @see $this->_extra_resource_properties_for_models |
||
432 | */ |
||
433 | public function resourceChanges() |
||
437 | |||
438 | |||
439 | |||
440 | /** |
||
441 | * Returns an array where keys are extra resource properties in this version of the API, |
||
442 | * and values are key-value pairs describing the new properties. @see Model_Version::_resource_changes |
||
443 | * |
||
444 | * @param \EEM_Base $model |
||
445 | * @return array |
||
446 | */ |
||
447 | public function extraResourcePropertiesForModel($model) |
||
459 | |||
460 | |||
461 | |||
462 | /** |
||
463 | * Gets all the related models for the specified model. It's good to use this |
||
464 | * in case this model didn't exist for this version or something |
||
465 | * |
||
466 | * @param \EEM_Base $model |
||
467 | * @return \EE_Model_Relation_Base[] |
||
468 | */ |
||
469 | public function relationSettings(\EEM_Base $model) |
||
484 | } |
||
485 | |||
487 |