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 AbstractObserver 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 AbstractObserver, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 36 | abstract class AbstractObserver implements ObserverInterface |
||
| 37 | { |
||
| 38 | |||
| 39 | /** |
||
| 40 | * The actual row, that has to be processed. |
||
| 41 | * |
||
| 42 | * @var array |
||
| 43 | */ |
||
| 44 | protected $row = array(); |
||
| 45 | |||
| 46 | /** |
||
| 47 | * The obeserver's subject instance. |
||
| 48 | * |
||
| 49 | * @var object |
||
| 50 | */ |
||
| 51 | protected $subject; |
||
| 52 | |||
| 53 | /** |
||
| 54 | * Initializes the observer with the passed subject instance. |
||
| 55 | * |
||
| 56 | * @param object|null $subject The observer's subject instance |
||
| 57 | */ |
||
| 58 | public function __construct($subject = null) |
||
| 64 | |||
| 65 | /** |
||
| 66 | * Set's the obeserver's subject instance to initialize the observer with. |
||
| 67 | * |
||
| 68 | * @param object $subject The observer's subject |
||
| 69 | * |
||
| 70 | * @return void |
||
| 71 | */ |
||
| 72 | public function setSubject($subject) |
||
| 76 | |||
| 77 | /** |
||
| 78 | * Return's the observer's subject instance. |
||
| 79 | * |
||
| 80 | * @return object The observer's subject instance |
||
| 81 | */ |
||
| 82 | public function getSubject() |
||
| 86 | |||
| 87 | /** |
||
| 88 | * Set's the array containing header row. |
||
| 89 | * |
||
| 90 | * @param array $headers The array with the header row |
||
| 91 | * |
||
| 92 | * @return void |
||
| 93 | */ |
||
| 94 | public function setHeaders(array $headers) |
||
| 98 | |||
| 99 | /** |
||
| 100 | * Return's the array containing header row. |
||
| 101 | * |
||
| 102 | * @return array The array with the header row |
||
| 103 | */ |
||
| 104 | public function getHeaders() |
||
| 108 | |||
| 109 | /** |
||
| 110 | * Return's the RegistryProcessor instance to handle the running threads. |
||
| 111 | * |
||
| 112 | * @return \TechDivision\Import\Services\RegistryProcessorInterface The registry processor instance |
||
| 113 | */ |
||
| 114 | public function getRegistryProcessor() |
||
| 118 | |||
| 119 | /** |
||
| 120 | * Set's the actual row, that has to be processed. |
||
| 121 | * |
||
| 122 | * @param array $row The row |
||
| 123 | * |
||
| 124 | * @return void |
||
| 125 | */ |
||
| 126 | protected function setRow(array $row) |
||
| 130 | |||
| 131 | /** |
||
| 132 | * Return's the actual row, that has to be processed. |
||
| 133 | * |
||
| 134 | * @return array The row |
||
| 135 | */ |
||
| 136 | protected function getRow() |
||
| 140 | |||
| 141 | /** |
||
| 142 | * Append's the exception suffix containing filename and line number to the |
||
| 143 | * passed message. If no message has been passed, only the suffix will be |
||
| 144 | * returned |
||
| 145 | * |
||
| 146 | * @param string|null $message The message to append the exception suffix to |
||
| 147 | * @param string|null $filename The filename used to create the suffix |
||
| 148 | * @param string|null $lineNumber The line number used to create the suffx |
||
| 149 | * |
||
| 150 | * @return string The message with the appended exception suffix |
||
| 151 | */ |
||
| 152 | protected function appendExceptionSuffix($message = null, $filename = null, $lineNumber = null) |
||
| 156 | |||
| 157 | /** |
||
| 158 | * Wraps the passed exeception into a new one by trying to resolve the original filname, |
||
| 159 | * line number and column name and use it for a detailed exception message. |
||
| 160 | * |
||
| 161 | * @param string $columnName The column name that should be resolved |
||
| 162 | * @param \Exception $parent The exception we want to wrap |
||
| 163 | * @param string $className The class name of the exception type we want to wrap the parent one |
||
| 164 | * |
||
| 165 | * @return \Exception the wrapped exception |
||
| 166 | */ |
||
| 167 | protected function wrapException( |
||
| 174 | |||
| 175 | /** |
||
| 176 | * Queries whether or not debug mode is enabled or not, default is TRUE. |
||
| 177 | * |
||
| 178 | * @return boolean TRUE if debug mode is enabled, else FALSE |
||
| 179 | */ |
||
| 180 | protected function isDebugMode() |
||
| 184 | |||
| 185 | /** |
||
| 186 | * Stop's observer execution on the actual row. |
||
| 187 | * |
||
| 188 | * @return void |
||
| 189 | */ |
||
| 190 | protected function skipRow() |
||
| 194 | |||
| 195 | /** |
||
| 196 | * Return's the name of the file to import. |
||
| 197 | * |
||
| 198 | * @return string The filename |
||
| 199 | */ |
||
| 200 | protected function getFilename() |
||
| 204 | |||
| 205 | /** |
||
| 206 | * Return's the actual line number. |
||
| 207 | * |
||
| 208 | * @return integer The line number |
||
| 209 | */ |
||
| 210 | protected function getLineNumber() |
||
| 214 | |||
| 215 | /** |
||
| 216 | * Return's the logger with the passed name, by default the system logger. |
||
| 217 | * |
||
| 218 | * @param string $name The name of the requested system logger |
||
| 219 | * |
||
| 220 | * @return \Psr\Log\LoggerInterface The logger instance |
||
| 221 | * @throws \Exception Is thrown, if the requested logger is NOT available |
||
| 222 | */ |
||
| 223 | protected function getSystemLogger($name = LoggerKeys::SYSTEM) |
||
| 227 | |||
| 228 | /** |
||
| 229 | * Return's the array with the system logger instances. |
||
| 230 | * |
||
| 231 | * @return array The logger instance |
||
| 232 | */ |
||
| 233 | protected function getSystemLoggers() |
||
| 237 | |||
| 238 | /** |
||
| 239 | * Return's the multiple field delimiter character to use, default value is comma (,). |
||
| 240 | * |
||
| 241 | * @return string The multiple field delimiter character |
||
| 242 | */ |
||
| 243 | protected function getMultipleFieldDelimiter() |
||
| 247 | |||
| 248 | /** |
||
| 249 | * Return's the multiple value delimiter character to use, default value is comma (|). |
||
| 250 | * |
||
| 251 | * @return string The multiple value delimiter character |
||
| 252 | */ |
||
| 253 | protected function getMultipleValueDelimiter() |
||
| 257 | |||
| 258 | /** |
||
| 259 | * Queries whether or not the header with the passed name is available. |
||
| 260 | * |
||
| 261 | * @param string $name The header name to query |
||
| 262 | * |
||
| 263 | * @return boolean TRUE if the header is available, else FALSE |
||
| 264 | */ |
||
| 265 | protected function hasHeader($name) |
||
| 269 | |||
| 270 | /** |
||
| 271 | * Return's the header value for the passed name. |
||
| 272 | * |
||
| 273 | * @param string $name The name of the header to return the value for |
||
| 274 | * |
||
| 275 | * @return mixed The header value |
||
| 276 | * \InvalidArgumentException Is thrown, if the header with the passed name is NOT available |
||
| 277 | */ |
||
| 278 | protected function getHeader($name) |
||
| 282 | |||
| 283 | /** |
||
| 284 | * Add's the header with the passed name and position, if not NULL. |
||
| 285 | * |
||
| 286 | * @param string $name The header name to add |
||
| 287 | * |
||
| 288 | * @return integer The new headers position |
||
| 289 | */ |
||
| 290 | protected function addHeader($name) |
||
| 294 | |||
| 295 | /** |
||
| 296 | * Return's the ID of the product that has been created recently. |
||
| 297 | * |
||
| 298 | * @return string The entity Id |
||
| 299 | */ |
||
| 300 | protected function getLastEntityId() |
||
| 304 | |||
| 305 | /** |
||
| 306 | * Return's the source date format to use. |
||
| 307 | * |
||
| 308 | * @return string The source date format |
||
| 309 | */ |
||
| 310 | protected function getSourceDateFormat() |
||
| 314 | |||
| 315 | /** |
||
| 316 | * Cast's the passed value based on the backend type information. |
||
| 317 | * |
||
| 318 | * @param string $backendType The backend type to cast to |
||
| 319 | * @param mixed $value The value to be casted |
||
| 320 | * |
||
| 321 | * @return mixed The casted value |
||
| 322 | */ |
||
| 323 | protected function castValueByBackendType($backendType, $value) |
||
| 327 | |||
| 328 | /** |
||
| 329 | * Set's the store view code the create the product/attributes for. |
||
| 330 | * |
||
| 331 | * @param string $storeViewCode The store view code |
||
| 332 | * |
||
| 333 | * @return void |
||
| 334 | */ |
||
| 335 | protected function setStoreViewCode($storeViewCode) |
||
| 339 | |||
| 340 | /** |
||
| 341 | * Return's the store view code the create the product/attributes for. |
||
| 342 | * |
||
| 343 | * @param string|null $default The default value to return, if the store view code has not been set |
||
| 344 | * |
||
| 345 | * @return string The store view code |
||
| 346 | */ |
||
| 347 | protected function getStoreViewCode($default = null) |
||
| 351 | |||
| 352 | /** |
||
| 353 | * Prepare's the store view code in the subject. |
||
| 354 | * |
||
| 355 | * @return void |
||
| 356 | */ |
||
| 357 | protected function prepareStoreViewCode() |
||
| 361 | |||
| 362 | /** |
||
| 363 | * Return's the store ID of the actual row, or of the default store |
||
| 364 | * if no store view code is set in the CSV file. |
||
| 365 | * |
||
| 366 | * @param string|null $default The default store view code to use, if no store view code is set in the CSV file |
||
| 367 | * |
||
| 368 | * @return integer The ID of the actual store |
||
| 369 | * @throws \Exception Is thrown, if the store with the actual code is not available |
||
| 370 | */ |
||
| 371 | protected function getRowStoreId($default = null) |
||
| 375 | |||
| 376 | /** |
||
| 377 | * Tries to format the passed value to a valid date with format 'Y-m-d H:i:s'. |
||
| 378 | * If the passed value is NOT a valid date, NULL will be returned. |
||
| 379 | * |
||
| 380 | * @param string|null $value The value to format |
||
| 381 | * |
||
| 382 | * @return string The formatted date |
||
| 383 | */ |
||
| 384 | protected function formatDate($value) |
||
| 388 | |||
| 389 | /** |
||
| 390 | * Extracts the elements of the passed value by exploding them |
||
| 391 | * with the also passed delimiter. |
||
| 392 | * |
||
| 393 | * @param string $value The value to extract |
||
| 394 | * @param string|null $delimiter The delimiter used to extrace the elements |
||
| 395 | * |
||
| 396 | * @return array The exploded values |
||
| 397 | */ |
||
| 398 | protected function explode($value, $delimiter = null) |
||
| 402 | |||
| 403 | /** |
||
| 404 | * Query whether or not a value for the column with the passed name exists. |
||
| 405 | * |
||
| 406 | * @param string $name The column name to query for a valid value |
||
| 407 | * |
||
| 408 | * @return boolean TRUE if the value is set, else FALSE |
||
| 409 | */ |
||
| 410 | View Code Duplication | protected function hasValue($name) |
|
| 425 | |||
| 426 | /** |
||
| 427 | * Set the value in the passed column name. |
||
| 428 | * |
||
| 429 | * @param string $name The column name to set the value for |
||
| 430 | * @param mixed $value The value to set |
||
| 431 | * |
||
| 432 | * @return void |
||
| 433 | */ |
||
| 434 | protected function setValue($name, $value) |
||
| 438 | |||
| 439 | /** |
||
| 440 | * Resolve's the value with the passed colum name from the actual row. If a callback will |
||
| 441 | * be passed, the callback will be invoked with the found value as parameter. If |
||
| 442 | * the value is NULL or empty, the default value will be returned. |
||
| 443 | * |
||
| 444 | * @param string $name The name of the column to return the value for |
||
| 445 | * @param mixed|null $default The default value, that has to be returned, if the row's value is empty |
||
| 446 | * @param callable|null $callback The callback that has to be invoked on the value, e. g. to format it |
||
| 447 | * |
||
| 448 | * @return mixed|null The, almost formatted, value |
||
| 449 | */ |
||
| 450 | View Code Duplication | protected function getValue($name, $default = null, callable $callback = null) |
|
| 479 | |||
| 480 | /** |
||
| 481 | * Return's the Magento configuration value. |
||
| 482 | * |
||
| 483 | * @param string $path The Magento path of the requested configuration value |
||
| 484 | * @param mixed $default The default value that has to be returned, if the requested configuration value is not set |
||
| 485 | * @param string $scope The scope the configuration value has been set |
||
| 486 | * @param integer $scopeId The scope ID the configuration value has been set |
||
| 487 | * |
||
| 488 | * @return mixed The configuration value |
||
| 489 | * @throws \Exception Is thrown, if nor a value can be found or a default value has been passed |
||
| 490 | */ |
||
| 491 | protected function getCoreConfigData($path, $default = null, $scope = ScopeKeys::SCOPE_DEFAULT, $scopeId = 0) |
||
| 495 | |||
| 496 | /** |
||
| 497 | * Initialize's and return's a new entity with the status 'create'. |
||
| 498 | * |
||
| 499 | * @param array $attr The attributes to merge into the new entity |
||
| 500 | * |
||
| 501 | * @return array The initialized entity |
||
| 502 | */ |
||
| 503 | protected function initializeEntity(array $attr = array()) |
||
| 507 | |||
| 508 | /** |
||
| 509 | * Merge's and return's the entity with the passed attributes and set's the |
||
| 510 | * status to 'update'. |
||
| 511 | * |
||
| 512 | * @param array $entity The entity to merge the attributes into |
||
| 513 | * @param array $attr The attributes to be merged |
||
| 514 | * |
||
| 515 | * @return array The merged entity |
||
| 516 | */ |
||
| 517 | protected function mergeEntity(array $entity, array $attr) |
||
| 521 | } |
||
| 522 |
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.