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 AbstractConfig 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 AbstractConfig, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
26 | abstract class AbstractConfig extends AbstractEntity implements |
||
27 | ConfigInterface, |
||
28 | ContainerInterface, |
||
29 | IteratorAggregate |
||
30 | { |
||
31 | use DelegatesAwareTrait; |
||
32 | use FileAwareTrait; |
||
33 | use SeparatorAwareTrait; |
||
34 | |||
35 | const DEFAULT_SEPARATOR = '.'; |
||
36 | |||
37 | /** |
||
38 | * Create the configuration. |
||
39 | * |
||
40 | * @param mixed $data Initial data. Either a filepath, |
||
41 | * an associative array, or an {@see Traversable iterable object}. |
||
42 | * @param EntityInterface[] $delegates An array of delegates (config) to set. |
||
43 | * @throws InvalidArgumentException If $data is invalid. |
||
44 | */ |
||
45 | final public function __construct($data = null, array $delegates = null) |
||
76 | |||
77 | /** |
||
78 | * Gets all default data from this store. |
||
79 | * |
||
80 | * Pre-populates new stores. |
||
81 | * |
||
82 | * May be reimplemented in inherited classes if any default values should be defined. |
||
83 | * |
||
84 | * @return array Key-value array of data |
||
85 | */ |
||
86 | public function defaults() |
||
90 | |||
91 | /** |
||
92 | * Adds new data, replacing / merging existing data with the same key. |
||
93 | * |
||
94 | * @uses self::offsetReplace() |
||
95 | * @param array|Traversable $data Key-value dataset to merge. |
||
96 | * Either an associative array or an {@see Traversable iterable object} |
||
97 | * (such as {@see ConfigInterface}). |
||
98 | * @return self |
||
99 | */ |
||
100 | public function merge($data) |
||
107 | |||
108 | /** |
||
109 | * Create a new iterator from the configuration instance. |
||
110 | * |
||
111 | * @see IteratorAggregate |
||
112 | * @return ArrayIterator |
||
113 | */ |
||
114 | public function getIterator() |
||
118 | |||
119 | /** |
||
120 | * Determines if this store contains the specified key and if its value is not NULL. |
||
121 | * |
||
122 | * Routine: |
||
123 | * - If the data key is {@see SeparatorAwareTrait::$separator nested}, |
||
124 | * the data-tree is traversed until the endpoint is found, if any; |
||
125 | * - If the data key does NOT exist on the store, a lookup is performed |
||
126 | * on each delegate store until a key is found, if any. |
||
127 | * |
||
128 | * @see \ArrayAccess |
||
129 | * @uses SeparatorAwareTrait::hasWithSeparator() |
||
130 | * @uses DelegatesAwareTrait::hasInDelegates() |
||
131 | * @param string $key The data key to check. |
||
132 | * @throws InvalidArgumentException If the $key is not a string or is a numeric value. |
||
133 | * @return boolean TRUE if $key exists and has a value other than NULL, FALSE otherwise. |
||
134 | */ |
||
135 | public function offsetExists($key) |
||
179 | |||
180 | /** |
||
181 | * Returns the value from the specified key on this entity. |
||
182 | * |
||
183 | * Routine: |
||
184 | * - If the data key is {@see SeparatorAwareTrait::$separator nested}, |
||
185 | * the data-tree is traversed until the endpoint to return its value, if any; |
||
186 | * - If the data key does NOT exist on the store, a lookup is performed |
||
187 | * on each delegate store until a value is found, if any. |
||
188 | * |
||
189 | * @see \ArrayAccess |
||
190 | * @uses SeparatorAwareTrait::getWithSeparator() |
||
191 | * @uses DelegatesAwareTrait::getInDelegates() |
||
192 | * @param string $key The data key to retrieve. |
||
193 | * @throws InvalidArgumentException If the $key is not a string or is a numeric value. |
||
194 | * @return mixed Value of the requested $key on success, NULL if the $key is not set. |
||
195 | */ |
||
196 | public function offsetGet($key) |
||
240 | |||
241 | /** |
||
242 | * Assigns the value to the specified key on this entity. |
||
243 | * |
||
244 | * Routine: |
||
245 | * - If the data key is {@see SeparatorAwareTrait::$separator nested}, |
||
246 | * the data-tree is traversed until the endpoint to assign its value; |
||
247 | * |
||
248 | * @see \ArrayAccess |
||
249 | * @uses SeparatorAwareTrait::setWithSeparator() |
||
250 | * @param string $key The data key to assign $value to. |
||
251 | * @param mixed $value The data value to assign to $key. |
||
252 | * @throws InvalidArgumentException If the $key is not a string or is a numeric value. |
||
253 | * @return void |
||
254 | */ |
||
255 | public function offsetSet($key, $value) |
||
288 | |||
289 | /** |
||
290 | * Replaces the value from the specified key. |
||
291 | * |
||
292 | * Routine: |
||
293 | * - When the value in the Config and the new value are both arrays, |
||
294 | * the method will replace their respective value recursively. |
||
295 | * - Then or otherwise, the new value is {@see self::offsetSet() assigned} to the Config. |
||
296 | * |
||
297 | * @uses self::offsetSet() |
||
298 | * @uses array_replace_recursive() |
||
299 | * @param string $key The data key to assign or merge $value to. |
||
300 | * @param mixed $value The data value to assign to or merge with $key. |
||
301 | * @throws InvalidArgumentException If the $key is not a string or is a numeric value. |
||
302 | * @return void |
||
303 | */ |
||
304 | public function offsetReplace($key, $value) |
||
328 | |||
329 | /** |
||
330 | * Adds a configuration file to the configset. |
||
331 | * |
||
332 | * Natively supported file formats: INI, JSON, PHP. |
||
333 | * |
||
334 | * @uses FileAwareTrait::loadFile() |
||
335 | * @param string $path The file to load and add. |
||
336 | * @return self |
||
337 | */ |
||
338 | public function addFile($path) |
||
346 | } |
||
347 |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.