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 MessageLocator 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 MessageLocator, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
24 | class MessageLocator implements \ArrayAccess |
||
25 | { |
||
26 | use CallbackChainHelperTrait; |
||
27 | |||
28 | const CALLBACK_POSITION_PREPEND = 1, |
||
29 | CALLBACK_POSITION_APPEND = 2; |
||
30 | /** |
||
31 | * @var callable[] |
||
32 | */ |
||
33 | private $messageSearchers = []; |
||
34 | /** |
||
35 | * @var callable[] |
||
36 | */ |
||
37 | private $missingMessageHandlers = []; |
||
38 | /** |
||
39 | * @var array |
||
40 | */ |
||
41 | private $fetchedMessages = []; |
||
42 | |||
43 | /** |
||
44 | * Append a function to handle missing messages. |
||
45 | * |
||
46 | * The callbacks given to this method is executed when the corresponding MessageLocator |
||
47 | * could not find a valid message for a given id. |
||
48 | * The callback should return a string. |
||
49 | * The callback may also prevent return false to allow subsequent callbacks to execute. |
||
50 | * <code> |
||
51 | * $locator->addMissingMessageHandler(function($msgId, MessageLocator $locator) { |
||
52 | * if(MyOwnLocator::canLocate($msgId)) { |
||
53 | * return $msg; |
||
54 | * } |
||
55 | * $locator->terminateCallbackChain(); |
||
56 | * }); |
||
57 | * </code> |
||
58 | * |
||
59 | * @param callable $locator |
||
60 | * @param int $position |
||
61 | * @return $this |
||
62 | */ |
||
63 | 52 | public function addMissingMessageHandler(callable $locator, $position = self::CALLBACK_POSITION_APPEND) |
|
73 | |||
74 | /** |
||
75 | * @param string|int $messageId |
||
76 | * @return bool |
||
77 | * @throws \Exception |
||
78 | */ |
||
79 | 22 | public function messageExists($messageId) |
|
93 | |||
94 | /** |
||
95 | * @param string|int $mId |
||
96 | * @return bool |
||
97 | */ |
||
98 | 22 | private static function validateMessageId($mId) |
|
102 | |||
103 | /** |
||
104 | * @param string|int $messageId |
||
105 | * @param bool $runMissingMessageHandlers |
||
106 | * @return array|bool |
||
107 | * @throws \Exception |
||
108 | */ |
||
109 | 80 | protected function fetchMessage($messageId, $runMissingMessageHandlers = true) |
|
156 | |||
157 | |||
158 | /** |
||
159 | * @param int|string $messageId |
||
160 | * @param bool $ignoreCachedEntries |
||
161 | * @return string|null |
||
162 | * @throws \Exception |
||
163 | */ |
||
164 | 68 | public function getMessage($messageId, $ignoreCachedEntries = false) |
|
169 | |||
170 | /** |
||
171 | * @param int|string $messageId |
||
172 | * @param bool $ignoreCachedEntries |
||
173 | * @return array |
||
174 | */ |
||
175 | 68 | public function getMessageWithInfo($messageId, $ignoreCachedEntries = false) |
|
185 | |||
186 | /** |
||
187 | * @param string $path |
||
188 | * @return \Pvra\Result\MessageLocator |
||
189 | */ |
||
190 | 58 | public static function fromPhpFile($path) |
|
198 | |||
199 | 6 | public static function fromJsonFile($file) |
|
212 | |||
213 | /** |
||
214 | * @param $array |
||
215 | * @return static |
||
216 | */ |
||
217 | 80 | public static function fromArray($array) |
|
230 | |||
231 | /** |
||
232 | * @param callable $searcher |
||
233 | * @param int $position |
||
234 | * @return $this |
||
235 | */ |
||
236 | 90 | public function addMessageSearcher(callable $searcher, $position = self::CALLBACK_POSITION_APPEND) |
|
246 | |||
247 | /** |
||
248 | * @codeCoverageIgnore |
||
249 | */ |
||
250 | public function clearInstanceCache() |
||
254 | |||
255 | |||
256 | /** |
||
257 | * OffsetExists |
||
258 | * Whether a message with the given id can be found without invoking missing |
||
259 | * message handlers |
||
260 | * |
||
261 | * @link http://php.net/manual/en/arrayaccess.offsetexists.php |
||
262 | * @param string|int $id message id |
||
263 | * @return boolean true on success or false on failure. |
||
264 | */ |
||
265 | 2 | public function offsetExists($id) |
|
269 | |||
270 | /** |
||
271 | * OffsetGet |
||
272 | * This method proxies ::getMessage($offset) |
||
273 | * |
||
274 | * @link http://php.net/manual/en/arrayaccess.offsetget.php |
||
275 | * @param string|int $id the id to retrieve |
||
276 | * @return string the message template |
||
277 | */ |
||
278 | 4 | public function offsetGet($id) |
|
282 | |||
283 | /** |
||
284 | * Appends a new message searcher |
||
285 | * Only the following syntax is valid: `$locator[] = function($id, $locator) {}; |
||
286 | * |
||
287 | * @param null $offset |
||
288 | * @param callable $value |
||
289 | * @throws \InvalidArgumentException Exception is thrown if an offset is specified |
||
290 | * or the value is not callable |
||
291 | */ |
||
292 | 4 | public function offsetSet($offset, $value) |
|
300 | |||
301 | /** |
||
302 | * @param mixed $offset |
||
303 | * @throws \RuntimeException Is always thrown as this operation is not supported |
||
304 | */ |
||
305 | 2 | public function offsetUnset($offset) |
|
309 | } |
||
310 |