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 absences_Request 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 absences_Request, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 47 | abstract class absences_Request extends absences_Record |
||
| 48 | { |
||
| 49 | |||
| 50 | /** |
||
| 51 | * |
||
| 52 | * @var absences_Agent |
||
| 53 | */ |
||
| 54 | protected $agent; |
||
| 55 | |||
| 56 | |||
| 57 | /** |
||
| 58 | * |
||
| 59 | * @var absences_Movement |
||
| 60 | */ |
||
| 61 | private $lastMovement; |
||
| 62 | |||
| 63 | |||
| 64 | |||
| 65 | /** |
||
| 66 | * @param int $id |
||
| 67 | * @return absences_Request |
||
| 68 | */ |
||
| 69 | abstract static public function getById($id); |
||
| 70 | |||
| 71 | |||
| 72 | /** |
||
| 73 | * @return absences_Agent |
||
| 74 | */ |
||
| 75 | 5 | View Code Duplication | public function getAgent() |
|
|
|||
| 76 | { |
||
| 77 | 5 | require_once dirname(__FILE__).'/agent.class.php'; |
|
| 78 | |||
| 79 | 5 | if (!isset($this->agent)) |
|
| 80 | 5 | { |
|
| 81 | 5 | $row = $this->getRow(); |
|
| 82 | 5 | $this->agent = absences_Agent::getFromIdUser($row['id_user']); |
|
| 83 | 5 | } |
|
| 84 | |||
| 85 | 5 | return $this->agent; |
|
| 86 | } |
||
| 87 | |||
| 88 | |||
| 89 | |||
| 90 | |||
| 91 | /** |
||
| 92 | * Method to sort requests by creation date |
||
| 93 | */ |
||
| 94 | public function createdOn() |
||
| 95 | { |
||
| 96 | return $this->createdOn; |
||
| 97 | } |
||
| 98 | |||
| 99 | /** |
||
| 100 | * Method to sort requests by modification date |
||
| 101 | */ |
||
| 102 | public function modifiedOn() |
||
| 103 | { |
||
| 104 | return $this->modifiedOn; |
||
| 105 | } |
||
| 106 | |||
| 107 | /** |
||
| 108 | * |
||
| 109 | * @return string |
||
| 110 | */ |
||
| 111 | public function getUserName() |
||
| 112 | { |
||
| 113 | return bab_getUserName($this->id_user); |
||
| 114 | } |
||
| 115 | |||
| 116 | |||
| 117 | protected function labelledValue($title, $value) |
||
| 118 | { |
||
| 119 | $W = bab_Widgets(); |
||
| 120 | return $W->FlowItems($W->Label($title)->addClass('widget-strong')->colon(), $W->Label($value))->setHorizontalSpacing(.4,'em'); |
||
| 121 | } |
||
| 122 | |||
| 123 | /** |
||
| 124 | * |
||
| 125 | * @return string |
||
| 126 | */ |
||
| 127 | abstract public function getRequestType(); |
||
| 128 | |||
| 129 | /** |
||
| 130 | * Get status on date |
||
| 131 | * @param string $date |
||
| 132 | * @return string |
||
| 133 | */ |
||
| 134 | 10 | public function getDateStatus($date) |
|
| 161 | |||
| 162 | /** |
||
| 163 | * @return string |
||
| 164 | */ |
||
| 165 | 1 | public function getShortStatusStr() |
|
| 166 | { |
||
| 167 | 1 | switch($this->status) |
|
| 168 | { |
||
| 169 | 1 | case 'Y': |
|
| 170 | 1 | return absences_translate("Accepted"); |
|
| 171 | case 'N': |
||
| 172 | return absences_translate("Refused"); |
||
| 173 | case 'P': |
||
| 174 | return absences_translate("Previsional request"); |
||
| 175 | default: |
||
| 176 | return absences_translate("Waiting approval"); |
||
| 177 | } |
||
| 178 | } |
||
| 179 | |||
| 180 | |||
| 181 | /** |
||
| 182 | * Get status as a string |
||
| 183 | * @return string |
||
| 184 | */ |
||
| 185 | public function getStatusStr() |
||
| 202 | |||
| 203 | |||
| 204 | |||
| 205 | /** |
||
| 206 | * @return Widget_Icon |
||
| 207 | */ |
||
| 208 | public function getStatusIcon() |
||
| 209 | { |
||
| 210 | $W = bab_Widgets(); |
||
| 211 | |||
| 212 | switch($this->status) |
||
| 213 | { |
||
| 214 | case 'Y': |
||
| 215 | $icon = Func_Icons::ACTIONS_DIALOG_OK; |
||
| 216 | break; |
||
| 217 | case 'N': |
||
| 218 | $icon = Func_Icons::ACTIONS_DIALOG_CANCEL; |
||
| 219 | break; |
||
| 220 | case 'P': |
||
| 221 | $icon = Func_Icons::STATUS_DIALOG_QUESTION; |
||
| 222 | break; |
||
| 223 | default: |
||
| 224 | $icon = Func_Icons::ACTIONS_VIEW_HISTORY; |
||
| 225 | break; |
||
| 226 | } |
||
| 227 | |||
| 228 | return $W->Icon($this->getStatusStr(), $icon); |
||
| 229 | } |
||
| 230 | |||
| 231 | |||
| 232 | /** |
||
| 233 | * Test if the has been fixed by a manager (not modifiable by the user) |
||
| 234 | * @return bool |
||
| 235 | */ |
||
| 236 | public function isFixed() |
||
| 237 | { |
||
| 238 | return false; |
||
| 239 | |||
| 240 | } |
||
| 241 | |||
| 242 | |||
| 243 | 4 | private function getNames($arr) |
|
| 244 | 4 | { |
|
| 245 | $return = array(); |
||
| 246 | |||
| 247 | foreach($arr as $k => $id_user) |
||
| 248 | 4 | { |
|
| 249 | $return[$k] = bab_getUserName($id_user); |
||
| 250 | } |
||
| 251 | |||
| 252 | return $return; |
||
| 253 | } |
||
| 254 | |||
| 255 | |||
| 256 | /** |
||
| 257 | * @return string |
||
| 258 | */ |
||
| 259 | 4 | public function getNextApprovers() |
|
| 260 | { |
||
| 261 | 4 | require_once $GLOBALS['babInstallPath'].'utilit/wfincl.php'; |
|
| 262 | |||
| 263 | $arr = bab_WFGetWaitingApproversInstance($this->idfai); |
||
| 264 | |||
| 265 | if (count($arr) <= 3) |
||
| 266 | { |
||
| 267 | return implode(', ', $this->getNames($arr)); |
||
| 268 | 4 | } |
|
| 269 | |||
| 270 | $first = array_shift($arr); |
||
| 271 | $second = array_shift($arr); |
||
| 272 | |||
| 273 | return sprintf(absences_translate('%s, %s and %d other approvers'), bab_getUserName($first), bab_getUserName($second), count($arr)); |
||
| 274 | } |
||
| 275 | |||
| 276 | /** |
||
| 277 | * Get the apporbation sheme ID for the request |
||
| 278 | * @return int |
||
| 279 | */ |
||
| 280 | 4 | abstract public function getApprobationId(); |
|
| 281 | |||
| 282 | |||
| 283 | |||
| 284 | /** |
||
| 285 | * @return bool |
||
| 286 | */ |
||
| 287 | 4 | protected function autoApproval() |
|
| 288 | { |
||
| 289 | $this->onConfirm(); |
||
| 290 | $this->addMovement(sprintf(absences_translate('The %s has been accepted with auto-approval'), $this->getTitle())); |
||
| 291 | 4 | return false; |
|
| 292 | } |
||
| 293 | |||
| 294 | |||
| 295 | |||
| 296 | 4 | private function getAutoApprovalUserId() |
|
| 297 | 4 | { |
|
| 298 | if (!absences_getVacationOption('auto_approval')) { |
||
| 299 | return 0; |
||
| 300 | } |
||
| 301 | |||
| 302 | 4 | return bab_getUserId(); |
|
| 303 | } |
||
| 304 | |||
| 305 | |||
| 306 | |||
| 307 | /** |
||
| 308 | * Create approbation instance and set it "notified" but do not send email notification |
||
| 309 | * retourne TRUE si la demande est bien devenue en attente d'approbation |
||
| 310 | * @return bool |
||
| 311 | */ |
||
| 312 | 4 | public function createApprobationInstance() |
|
| 359 | |||
| 360 | |||
| 361 | /** |
||
| 362 | * Approbator confirm |
||
| 363 | * move to next approbator or close instance and set the request accepted |
||
| 364 | * |
||
| 365 | * |
||
| 366 | * @param bool $status Accept or reject approbation step |
||
| 367 | * @param string $approver_comment |
||
| 368 | * |
||
| 369 | */ |
||
| 370 | public function approbationNext($status, $approver_comment) |
||
| 431 | |||
| 432 | |||
| 433 | |||
| 434 | /** |
||
| 435 | * Auto confirm the request |
||
| 436 | */ |
||
| 437 | public function autoConfirm() |
||
| 438 | { |
||
| 439 | $this->addMovement( |
||
| 440 | sprintf(absences_translate('The %s has been confirmed automatically because of the validation timout'), $this->getTitle()) |
||
| 441 | ); |
||
| 442 | |||
| 443 | |||
| 444 | if ($this->idfai > 0) |
||
| 445 | { |
||
| 446 | require_once $GLOBALS['babInstallPath'].'utilit/wfincl.php'; |
||
| 447 | bab_WFDeleteInstance($this->idfai); |
||
| 448 | } |
||
| 449 | |||
| 450 | $this->idfai = 0; |
||
| 451 | $this->status = 'Y'; |
||
| 452 | $this->onConfirm(); |
||
| 453 | |||
| 454 | $this->id_approver = 0; |
||
| 455 | $this->save(); |
||
| 456 | $this->notifyOwner(); |
||
| 457 | } |
||
| 458 | |||
| 459 | |||
| 460 | |||
| 461 | /** |
||
| 462 | * Test if the logged in user can modify the entry |
||
| 463 | * @return bool |
||
| 464 | */ |
||
| 465 | public function canModify() |
||
| 500 | |||
| 501 | |||
| 502 | /** |
||
| 503 | * Test if the user can delete the entry |
||
| 504 | * @return bool |
||
| 505 | */ |
||
| 506 | public function canDelete() |
||
| 507 | { |
||
| 508 | return $this->canModify(); |
||
| 509 | } |
||
| 510 | |||
| 511 | |||
| 512 | |||
| 513 | |||
| 514 | |||
| 515 | /** |
||
| 516 | * Process specific code when the request is rejected |
||
| 517 | * |
||
| 518 | */ |
||
| 519 | abstract protected function onReject(); |
||
| 520 | |||
| 521 | /** |
||
| 522 | * Process specific code when the request is confirmed |
||
| 523 | */ |
||
| 524 | abstract protected function onConfirm(); |
||
| 525 | |||
| 526 | /** |
||
| 527 | * request title |
||
| 528 | * @return string |
||
| 529 | */ |
||
| 530 | abstract public function getTitle(); |
||
| 531 | |||
| 532 | |||
| 533 | |||
| 534 | /** |
||
| 535 | * Get a list of field to display in the notifications for this request |
||
| 536 | * @return array |
||
| 537 | */ |
||
| 538 | abstract public function getNotifyFields(); |
||
| 539 | |||
| 540 | |||
| 541 | /** |
||
| 542 | * annee de la demande |
||
| 543 | * @return int |
||
| 544 | */ |
||
| 545 | abstract public function getYear(); |
||
| 546 | |||
| 547 | |||
| 548 | /** |
||
| 549 | * annee de la demande, pour l'archivage par annee, tiens compte du parametrage du mois de debut de periode. |
||
| 550 | * Utilise dans l'archivage par annee |
||
| 551 | * @return int |
||
| 552 | */ |
||
| 553 | abstract public function getArchiveYear(); |
||
| 554 | |||
| 555 | |||
| 556 | abstract public function archive(); |
||
| 557 | |||
| 558 | |||
| 559 | /** |
||
| 560 | * approver has been notified |
||
| 561 | */ |
||
| 562 | abstract public function setNotified(); |
||
| 563 | |||
| 564 | |||
| 565 | /** |
||
| 566 | * Url to edit request as a manager |
||
| 567 | */ |
||
| 568 | abstract public function getManagerEditUrl(); |
||
| 569 | |||
| 570 | /** |
||
| 571 | * Url to delete request as a manager |
||
| 572 | */ |
||
| 573 | abstract public function getManagerDeleteUrl(); |
||
| 574 | |||
| 575 | |||
| 576 | /** |
||
| 577 | * Notify request next approvers |
||
| 578 | */ |
||
| 579 | public function notifyApprovers() |
||
| 580 | { |
||
| 581 | $defer = (bool) absences_getVacationOption('approb_email_defer'); |
||
| 582 | if (!$defer) |
||
| 583 | { |
||
| 584 | require_once dirname(__FILE__).'/request.notify.php'; |
||
| 585 | absences_notifyRequestApprovers(); |
||
| 586 | } |
||
| 587 | } |
||
| 588 | |||
| 589 | |||
| 590 | /** |
||
| 591 | * Notify request owner about the approval or reject of the request |
||
| 592 | */ |
||
| 593 | public function notifyOwner() |
||
| 594 | { |
||
| 595 | $appliquant_email = (bool) absences_getVacationOption('appliquant_email'); |
||
| 596 | |||
| 597 | if (!$appliquant_email) |
||
| 598 | { |
||
| 599 | return; |
||
| 600 | } |
||
| 601 | |||
| 602 | require_once dirname(__FILE__).'/request.notify.php'; |
||
| 603 | |||
| 604 | |||
| 605 | |||
| 606 | switch($this->status) |
||
| 607 | { |
||
| 608 | case 'N': |
||
| 609 | $subject = sprintf(absences_translate("Your %s has been refused"), $this->getTitle()); |
||
| 610 | break; |
||
| 611 | |||
| 612 | case 'Y': |
||
| 613 | $subject = sprintf(absences_translate("Your %s has been accepted"), $this->getTitle()); |
||
| 614 | break; |
||
| 615 | |||
| 616 | default: // next approver |
||
| 617 | return; |
||
| 618 | } |
||
| 619 | |||
| 620 | absences_notifyRequestAuthor(array($this), $subject, $subject, $this->id_user); |
||
| 621 | } |
||
| 622 | |||
| 623 | |||
| 624 | /** |
||
| 625 | * Get request card frame to display in requests list |
||
| 626 | * @param bool $display_username Affiche le nom du demandeur dans la card frame ou non, utile pour les listes contenant plusieurs demandeurs possibles |
||
| 627 | * @param int $rfrom si les demandes de la liste sont modifiee par un gestionnaire ou gestionnaire delegue |
||
| 628 | * @param int $ide id entite de l'organigramme >0 si gestionnaire delegue seulement |
||
| 629 | * @return Widget_Frame |
||
| 630 | */ |
||
| 631 | abstract public function getCardFrame($display_username, $rfrom, $ide); |
||
| 632 | |||
| 633 | |||
| 634 | /** |
||
| 635 | * Get request frame to display in manager lists |
||
| 636 | * @return Widget_Frame |
||
| 637 | */ |
||
| 638 | abstract public function getManagerFrame(); |
||
| 639 | |||
| 640 | |||
| 641 | |||
| 642 | /** |
||
| 643 | * Delete request |
||
| 644 | */ |
||
| 645 | public function delete() |
||
| 646 | { |
||
| 647 | if ($this->idfai > 0) |
||
| 648 | { |
||
| 649 | require_once $GLOBALS['babInstallPath'].'utilit/wfincl.php'; |
||
| 650 | bab_WFDeleteInstance($this->idfai); |
||
| 651 | } |
||
| 652 | |||
| 653 | global $babDB; |
||
| 654 | $babDB->db_query("UPDATE absences_movement SET id_request='0' WHERE request_class=".$babDB->quote(get_class($this))." AND id_request=".$babDB->quote($this->id)); |
||
| 655 | } |
||
| 656 | |||
| 657 | |||
| 658 | |||
| 659 | /** |
||
| 660 | * @return absences_MovementIterator |
||
| 661 | */ |
||
| 662 | public function getMovementIterator() |
||
| 663 | { |
||
| 664 | require_once dirname(__FILE__).'/movement.class.php'; |
||
| 665 | |||
| 666 | $I = new absences_MovementIterator; |
||
| 667 | $I->setRequest($this); |
||
| 668 | |||
| 669 | return $I; |
||
| 670 | } |
||
| 671 | |||
| 672 | |||
| 673 | |||
| 674 | /** |
||
| 675 | * |
||
| 676 | * @param string $message Generated message |
||
| 677 | * @param string $comment Author comment |
||
| 678 | */ |
||
| 679 | 4 | View Code Duplication | public function addMovement($message, $comment = '', $createdOn = null) |
| 691 | |||
| 692 | |||
| 693 | |||
| 694 | /** |
||
| 695 | * @return absences_Movement |
||
| 696 | */ |
||
| 697 | public function getLastMovement() |
||
| 698 | { |
||
| 699 | if (!isset($this->lastMovement)) |
||
| 700 | { |
||
| 701 | $this->lastMovement = false; |
||
| 702 | $I = $this->getMovementIterator(); |
||
| 703 | foreach($I as $movement) |
||
| 704 | { |
||
| 705 | $this->lastMovement = $movement; |
||
| 706 | break; |
||
| 707 | } |
||
| 708 | } |
||
| 709 | |||
| 710 | return $this->lastMovement; |
||
| 711 | } |
||
| 712 | |||
| 713 | |||
| 714 | /** |
||
| 715 | * Faut-il allerter l'approbateur que le delai d'approbation est depasse |
||
| 716 | * @return bool |
||
| 717 | */ |
||
| 718 | public function approbAlert() |
||
| 719 | { |
||
| 720 | $last = $this->getLastMovement(); |
||
| 721 | |||
| 722 | if (!$last) |
||
| 723 | { |
||
| 724 | return false; |
||
| 739 | } |
||
| 740 | |||
| 1014 |
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.