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 Appointment 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 Appointment, and based on these observations, apply Extract Interface, too.
| 1 | <?php  | 
            ||
| 17 | class Appointment extends EloquentModel implements HasPresenter  | 
            ||
| 18 | { | 
            ||
| 19 | /**  | 
            ||
| 20 | * The attributes that are mass assignable.  | 
            ||
| 21 | *  | 
            ||
| 22 | * @var array  | 
            ||
| 23 | */  | 
            ||
| 24 | protected $fillable = ['issuer_id', 'contact_id', 'business_id',  | 
            ||
| 25 | 'service_id', 'start_at', 'finish_at', 'duration', 'comments', ];  | 
            ||
| 26 | |||
| 27 | /**  | 
            ||
| 28 | * The attributes that aren't mass assignable.  | 
            ||
| 29 | *  | 
            ||
| 30 | * @var array  | 
            ||
| 31 | */  | 
            ||
| 32 | protected $guarded = ['id', 'hash', 'status', 'vacancy_id'];  | 
            ||
| 33 | |||
| 34 | /**  | 
            ||
| 35 | * The attributes that should be mutated to dates.  | 
            ||
| 36 | *  | 
            ||
| 37 | * @var array  | 
            ||
| 38 | */  | 
            ||
| 39 | protected $dates = ['start_at', 'finish_at'];  | 
            ||
| 40 | |||
| 41 | /**  | 
            ||
| 42 | * Appointment Hard Status Constants.  | 
            ||
| 43 | */  | 
            ||
| 44 | const STATUS_RESERVED = 'R';  | 
            ||
| 45 | const STATUS_CONFIRMED = 'C';  | 
            ||
| 46 | const STATUS_ANNULATED = 'A';  | 
            ||
| 47 | const STATUS_SERVED = 'S';  | 
            ||
| 48 | |||
| 49 | ///////////////  | 
            ||
| 50 | // PRESENTER //  | 
            ||
| 51 | ///////////////  | 
            ||
| 52 | |||
| 53 | /**  | 
            ||
| 54 | * Get Presenter Class.  | 
            ||
| 55 | *  | 
            ||
| 56 | * @return App\Presenters\AppointmentPresenter  | 
            ||
| 57 | */  | 
            ||
| 58 | 1 | public function getPresenterClass()  | 
            |
| 59 |     { | 
            ||
| 60 | 1 | return AppointmentPresenter::class;  | 
            |
| 61 | }  | 
            ||
| 62 | |||
| 63 | /**  | 
            ||
| 64 | * Generate hash and save the model to the database.  | 
            ||
| 65 | *  | 
            ||
| 66 | * @param array $options  | 
            ||
| 67 | *  | 
            ||
| 68 | * @return bool  | 
            ||
| 69 | */  | 
            ||
| 70 | 30 | public function save(array $options = [])  | 
            |
| 76 | |||
| 77 | ///////////////////  | 
            ||
| 78 | // Relationships //  | 
            ||
| 79 | ///////////////////  | 
            ||
| 80 | |||
| 81 | /**  | 
            ||
| 82 | * Get the issuer (the User that generated the Appointment).  | 
            ||
| 83 | *  | 
            ||
| 84 | * @return \Illuminate\Database\Eloquent\Relations\BelongsTo  | 
            ||
| 85 | */  | 
            ||
| 86 | 4 | public function issuer()  | 
            |
| 90 | |||
| 91 | /**  | 
            ||
| 92 | * Get the target Contact (for whom is reserved the Appointment).  | 
            ||
| 93 | *  | 
            ||
| 94 | * @return \Illuminate\Database\Eloquent\Relations\BelongsTo  | 
            ||
| 95 | */  | 
            ||
| 96 | 4 | public function contact()  | 
            |
| 100 | |||
| 101 | /**  | 
            ||
| 102 | * Get the holding Business (that has taken the reservation).  | 
            ||
| 103 | *  | 
            ||
| 104 | * @return \Illuminate\Database\Eloquent\Relations\BelongsTo  | 
            ||
| 105 | */  | 
            ||
| 106 | 11 | public function business()  | 
            |
| 110 | |||
| 111 | /**  | 
            ||
| 112 | * Get the reserved Service.  | 
            ||
| 113 | *  | 
            ||
| 114 | * @return \Illuminate\Database\Eloquent\Relations\BelongsTo  | 
            ||
| 115 | */  | 
            ||
| 116 | 9 | public function service()  | 
            |
| 120 | |||
| 121 | /**  | 
            ||
| 122 | * Get the Vacancy (that justifies the availability of resources for the  | 
            ||
| 123 | * Appointment generation).  | 
            ||
| 124 | *  | 
            ||
| 125 | * @return \Illuminate\Database\Eloquent\Relations\BelongsTo  | 
            ||
| 126 | */  | 
            ||
| 127 | 6 | public function vacancy()  | 
            |
| 131 | |||
| 132 | ///////////  | 
            ||
| 133 | // Other //  | 
            ||
| 134 | ///////////  | 
            ||
| 135 | |||
| 136 | /**  | 
            ||
| 137 | * Get the User through Contact.  | 
            ||
| 138 | *  | 
            ||
| 139 | * @return User  | 
            ||
| 140 | */  | 
            ||
| 141 | 2 | public function user()  | 
            |
| 145 | |||
| 146 | /**  | 
            ||
| 147 | * Determine if the new Appointment will hash-crash with another existing  | 
            ||
| 148 | * Appointment.  | 
            ||
| 149 | *  | 
            ||
| 150 | * @return bool  | 
            ||
| 151 | */  | 
            ||
| 152 | 5 | public function duplicates()  | 
            |
| 156 | |||
| 157 | 2 | public function duration()  | 
            |
| 161 | |||
| 162 | ///////////////  | 
            ||
| 163 | // Accessors //  | 
            ||
| 164 | ///////////////  | 
            ||
| 165 | |||
| 166 | /**  | 
            ||
| 167 | * Get Hash.  | 
            ||
| 168 | *  | 
            ||
| 169 | * @return string  | 
            ||
| 170 | */  | 
            ||
| 171 | 8 | public function getHashAttribute()  | 
            |
| 177 | |||
| 178 | /**  | 
            ||
| 179 | * Get Finish At:  | 
            ||
| 180 | * Calculates the start_at time plus duration in minutes.  | 
            ||
| 181 | *  | 
            ||
| 182 | * @return Carbon  | 
            ||
| 183 | */  | 
            ||
| 184 | 3 | public function getFinishAtAttribute()  | 
            |
| 196 | |||
| 197 | /**  | 
            ||
| 198 | * Get annulation deadline (target date).  | 
            ||
| 199 | *  | 
            ||
| 200 | * @return Carbon\Carbon  | 
            ||
| 201 | */  | 
            ||
| 202 | public function getAnnulationDeadlineAttribute()  | 
            ||
| 210 | |||
| 211 | /**  | 
            ||
| 212 | * Get the human readable status name.  | 
            ||
| 213 | *  | 
            ||
| 214 | * @return string  | 
            ||
| 215 | */  | 
            ||
| 216 | public function getStatusLabelAttribute()  | 
            ||
| 229 | |||
| 230 | /**  | 
            ||
| 231 | * Get the date of the Appointment.  | 
            ||
| 232 | *  | 
            ||
| 233 | * @return string  | 
            ||
| 234 | */  | 
            ||
| 235 | 3 | public function getDateAttribute()  | 
            |
| 241 | |||
| 242 | /**  | 
            ||
| 243 | * Get user-friendly unique identification code.  | 
            ||
| 244 | *  | 
            ||
| 245 | * @return string  | 
            ||
| 246 | */  | 
            ||
| 247 | 2 | public function getCodeAttribute()  | 
            |
| 253 | |||
| 254 | //////////////  | 
            ||
| 255 | // Mutators //  | 
            ||
| 256 | //////////////  | 
            ||
| 257 | |||
| 258 | /**  | 
            ||
| 259 | * Generate Appointment hash.  | 
            ||
| 260 | *  | 
            ||
| 261 | * @return string  | 
            ||
| 262 | */  | 
            ||
| 263 | 30 | public function doHash()  | 
            |
| 272 | |||
| 273 | /**  | 
            ||
| 274 | * Set start at.  | 
            ||
| 275 | *  | 
            ||
| 276 | * @param Carbon $datetime  | 
            ||
| 277 | */  | 
            ||
| 278 | 30 | public function setStartAtAttribute(Carbon $datetime)  | 
            |
| 282 | |||
| 283 | /**  | 
            ||
| 284 | * Set finish_at attribute.  | 
            ||
| 285 | *  | 
            ||
| 286 | * @param Carbon $datetime  | 
            ||
| 287 | */  | 
            ||
| 288 | 10 | public function setFinishAtAttribute(Carbon $datetime)  | 
            |
| 292 | |||
| 293 | /**  | 
            ||
| 294 | * Set Comments.  | 
            ||
| 295 | *  | 
            ||
| 296 | * @param string $comments  | 
            ||
| 297 | */  | 
            ||
| 298 | 30 | public function setCommentsAttribute($comments)  | 
            |
| 302 | |||
| 303 | /////////////////  | 
            ||
| 304 | // HARD STATUS //  | 
            ||
| 305 | /////////////////  | 
            ||
| 306 | |||
| 307 | /**  | 
            ||
| 308 | * Determine if is Reserved.  | 
            ||
| 309 | *  | 
            ||
| 310 | * @return bool  | 
            ||
| 311 | */  | 
            ||
| 312 | 1 | public function isReserved()  | 
            |
| 316 | |||
| 317 | ///////////////////////////  | 
            ||
| 318 | // Calculated attributes //  | 
            ||
| 319 | ///////////////////////////  | 
            ||
| 320 | |||
| 321 | /**  | 
            ||
| 322 | * Appointment Status Workflow.  | 
            ||
| 323 | *  | 
            ||
| 324 | * Hard Status: Those concrete values stored in DB  | 
            ||
| 325 | * Soft Status: Those values calculated from stored values in DB  | 
            ||
| 326 | *  | 
            ||
| 327 | * Suggested transitions (Binding is not mandatory)  | 
            ||
| 328 | * Reserved -> Confirmed -> Served  | 
            ||
| 329 | * Reserved -> Served  | 
            ||
| 330 | * Reserved -> Annulated  | 
            ||
| 331 | * Reserved -> Confirmed -> Annulated  | 
            ||
| 332 | *  | 
            ||
| 333 | * Soft Status  | 
            ||
| 334 | * (Active) [ Reserved | Confirmed ]  | 
            ||
| 335 | * (InActive) [ Annulated | Served ]  | 
            ||
| 336 | */  | 
            ||
| 337 | |||
| 338 | /**  | 
            ||
| 339 | * Determine if is Active.  | 
            ||
| 340 | *  | 
            ||
| 341 | * @return bool  | 
            ||
| 342 | */  | 
            ||
| 343 | 5 | public function isActive()  | 
            |
| 349 | |||
| 350 | /**  | 
            ||
| 351 | * Determine if is Pending.  | 
            ||
| 352 | *  | 
            ||
| 353 | * @return bool  | 
            ||
| 354 | */  | 
            ||
| 355 | 3 | public function isPending()  | 
            |
| 359 | |||
| 360 | /**  | 
            ||
| 361 | * Determine if is Future.  | 
            ||
| 362 | *  | 
            ||
| 363 | * @return bool  | 
            ||
| 364 | */  | 
            ||
| 365 | 4 | public function isFuture()  | 
            |
| 369 | |||
| 370 | /**  | 
            ||
| 371 | * Determine if is due.  | 
            ||
| 372 | *  | 
            ||
| 373 | * @return bool  | 
            ||
| 374 | */  | 
            ||
| 375 | 5 | public function isDue()  | 
            |
| 379 | |||
| 380 | ////////////  | 
            ||
| 381 | // Scopes //  | 
            ||
| 382 | ////////////  | 
            ||
| 383 | |||
| 384 | /**  | 
            ||
| 385 | * Scope to Business.  | 
            ||
| 386 | *  | 
            ||
| 387 | * @param Illuminate\Database\Query $query  | 
            ||
| 388 | *  | 
            ||
| 389 | * @return Illuminate\Database\Query  | 
            ||
| 390 | */  | 
            ||
| 391 | 1 | public function scopeOfBusiness($query, $businessId)  | 
            |
| 395 | |||
| 396 | /////////////////////////  | 
            ||
| 397 | // Hard Status Scoping //  | 
            ||
| 398 | /////////////////////////  | 
            ||
| 399 | |||
| 400 | /**  | 
            ||
| 401 | * Scope to Contacts Collection.  | 
            ||
| 402 | *  | 
            ||
| 403 | * @param Illuminate\Database\Query $query  | 
            ||
| 404 | *  | 
            ||
| 405 | * @return Illuminate\Database\Query  | 
            ||
| 406 | */  | 
            ||
| 407 | public function scopeForContacts($query, $contacts)  | 
            ||
| 411 | |||
| 412 | /**  | 
            ||
| 413 | * Scope to Unarchived Appointments.  | 
            ||
| 414 | *  | 
            ||
| 415 | * @param Illuminate\Database\Query $query  | 
            ||
| 416 | *  | 
            ||
| 417 | * @return Illuminate\Database\Query  | 
            ||
| 418 | */  | 
            ||
| 419 | public function scopeUnarchived($query)  | 
            ||
| 430 | |||
| 431 | /**  | 
            ||
| 432 | * Scope to Served Appointments.  | 
            ||
| 433 | *  | 
            ||
| 434 | * @param Illuminate\Database\Query $query  | 
            ||
| 435 | *  | 
            ||
| 436 | * @return Illuminate\Database\Query  | 
            ||
| 437 | */  | 
            ||
| 438 | public function scopeServed($query)  | 
            ||
| 442 | |||
| 443 | /**  | 
            ||
| 444 | * Scope to Annulated Appointments.  | 
            ||
| 445 | *  | 
            ||
| 446 | * @param Illuminate\Database\Query $query  | 
            ||
| 447 | *  | 
            ||
| 448 | * @return Illuminate\Database\Query  | 
            ||
| 449 | */  | 
            ||
| 450 | public function scopeAnnulated($query)  | 
            ||
| 454 | |||
| 455 | /////////////////////////  | 
            ||
| 456 | // Soft Status Scoping //  | 
            ||
| 457 | /////////////////////////  | 
            ||
| 458 | |||
| 459 | /**  | 
            ||
| 460 | * Scope to not Served Appointments.  | 
            ||
| 461 | *  | 
            ||
| 462 | * @param Illuminate\Database\Query $query  | 
            ||
| 463 | *  | 
            ||
| 464 | * @return Illuminate\Database\Query  | 
            ||
| 465 | */  | 
            ||
| 466 | public function scopeUnServed($query)  | 
            ||
| 470 | |||
| 471 | /**  | 
            ||
| 472 | * Scope to Active Appointments.  | 
            ||
| 473 | *  | 
            ||
| 474 | * @param Illuminate\Database\Query $query  | 
            ||
| 475 | *  | 
            ||
| 476 | * @return Illuminate\Database\Query  | 
            ||
| 477 | */  | 
            ||
| 478 | 18 | public function scopeActive($query)  | 
            |
| 482 | |||
| 483 | /**  | 
            ||
| 484 | * Scope of date.  | 
            ||
| 485 | *  | 
            ||
| 486 | * @param Illuminate\Database\Query $query  | 
            ||
| 487 | * @param Carbon $date  | 
            ||
| 488 | *  | 
            ||
| 489 | * @return Illuminate\Database\Query  | 
            ||
| 490 | */  | 
            ||
| 491 | public function scopeOfDate($query, Carbon $date)  | 
            ||
| 495 | |||
| 496 | /**  | 
            ||
| 497 | * Between Dates.  | 
            ||
| 498 | *  | 
            ||
| 499 | * @param Illuminate\Database\Query $query  | 
            ||
| 500 | * @param Carbon $startAt  | 
            ||
| 501 | * @param Carbon $finishAt  | 
            ||
| 502 | *  | 
            ||
| 503 | * @return Illuminate\Database\Query  | 
            ||
| 504 | */  | 
            ||
| 505 | 12 | public function scopeAffectingInterval($query, Carbon $startAt, Carbon $finishAt)  | 
            |
| 529 | |||
| 530 | //////////////////////////  | 
            ||
| 531 | // Soft Status Checkers //  | 
            ||
| 532 | //////////////////////////  | 
            ||
| 533 | |||
| 534 | /**  | 
            ||
| 535 | * User is target contact of the appointment.  | 
            ||
| 536 | *  | 
            ||
| 537 | * @param int $userId  | 
            ||
| 538 | *  | 
            ||
| 539 | * @return bool  | 
            ||
| 540 | */  | 
            ||
| 541 | public function isTarget($userId)  | 
            ||
| 545 | |||
| 546 | /**  | 
            ||
| 547 | * User is issuer of the appointment.  | 
            ||
| 548 | *  | 
            ||
| 549 | * @param int $userId  | 
            ||
| 550 | *  | 
            ||
| 551 | * @return bool  | 
            ||
| 552 | */  | 
            ||
| 553 | public function isIssuer($userId)  | 
            ||
| 557 | |||
| 558 | /**  | 
            ||
| 559 | * User is owner of business.  | 
            ||
| 560 | *  | 
            ||
| 561 | * @param int $userId  | 
            ||
| 562 | *  | 
            ||
| 563 | * @return bool  | 
            ||
| 564 | */  | 
            ||
| 565 | public function isOwner($userId)  | 
            ||
| 569 | |||
| 570 | /**  | 
            ||
| 571 | * can be annulated by user.  | 
            ||
| 572 | *  | 
            ||
| 573 | * @param int $userId  | 
            ||
| 574 | *  | 
            ||
| 575 | * @return bool  | 
            ||
| 576 | */  | 
            ||
| 577 | public function canAnnulate($userId)  | 
            ||
| 583 | |||
| 584 | /**  | 
            ||
| 585 | * Determine if it is still possible to annulate according business policy.  | 
            ||
| 586 | *  | 
            ||
| 587 | * @return bool  | 
            ||
| 588 | */  | 
            ||
| 589 | public function isOnTimeToAnnulate()  | 
            ||
| 597 | |||
| 598 | /**  | 
            ||
| 599 | * can Serve.  | 
            ||
| 600 | *  | 
            ||
| 601 | * @param int $userId  | 
            ||
| 602 | *  | 
            ||
| 603 | * @return bool  | 
            ||
| 604 | */  | 
            ||
| 605 | public function canServe($userId)  | 
            ||
| 609 | |||
| 610 | /**  | 
            ||
| 611 | * can confirm.  | 
            ||
| 612 | *  | 
            ||
| 613 | * @param int $userId  | 
            ||
| 614 | *  | 
            ||
| 615 | * @return bool  | 
            ||
| 616 | */  | 
            ||
| 617 | public function canConfirm($userId)  | 
            ||
| 621 | |||
| 622 | /**  | 
            ||
| 623 | * is Serveable by user.  | 
            ||
| 624 | *  | 
            ||
| 625 | * @param int $userId  | 
            ||
| 626 | *  | 
            ||
| 627 | * @return bool  | 
            ||
| 628 | */  | 
            ||
| 629 | public function isServeableBy($userId)  | 
            ||
| 633 | |||
| 634 | /**  | 
            ||
| 635 | * is Confirmable By user.  | 
            ||
| 636 | *  | 
            ||
| 637 | * @param int $userId  | 
            ||
| 638 | *  | 
            ||
| 639 | * @return bool  | 
            ||
| 640 | */  | 
            ||
| 641 | public function isConfirmableBy($userId)  | 
            ||
| 648 | |||
| 649 | /**  | 
            ||
| 650 | * is Annulable By user.  | 
            ||
| 651 | *  | 
            ||
| 652 | * @param int $userId  | 
            ||
| 653 | *  | 
            ||
| 654 | * @return bool  | 
            ||
| 655 | */  | 
            ||
| 656 | public function isAnnulableBy($userId)  | 
            ||
| 660 | |||
| 661 | /**  | 
            ||
| 662 | * Determine if the queried userId may confirm the appointment or not.  | 
            ||
| 663 | *  | 
            ||
| 664 | * @param int $userId  | 
            ||
| 665 | *  | 
            ||
| 666 | * @return bool  | 
            ||
| 667 | */  | 
            ||
| 668 | public function shouldConfirmBy($userId)  | 
            ||
| 673 | |||
| 674 | /**  | 
            ||
| 675 | * Determine if the target Contact's User is the same of the Appointment  | 
            ||
| 676 | * issuer User.  | 
            ||
| 677 | *  | 
            ||
| 678 | * @return bool  | 
            ||
| 679 | */  | 
            ||
| 680 | public function isSelfIssued()  | 
            ||
| 694 | |||
| 695 | /**  | 
            ||
| 696 | * Determine if the Serve action can be performed.  | 
            ||
| 697 | *  | 
            ||
| 698 | * @return bool  | 
            ||
| 699 | */  | 
            ||
| 700 | 1 | public function isServeable()  | 
            |
| 704 | |||
| 705 | /**  | 
            ||
| 706 | * Determine if the Confirm action can be performed.  | 
            ||
| 707 | *  | 
            ||
| 708 | * @return bool  | 
            ||
| 709 | */  | 
            ||
| 710 | 1 | public function isConfirmable()  | 
            |
| 714 | |||
| 715 | /**  | 
            ||
| 716 | * Determine if the Annulate action can be performed.  | 
            ||
| 717 | *  | 
            ||
| 718 | * @return bool  | 
            ||
| 719 | */  | 
            ||
| 720 | 1 | public function isAnnulable()  | 
            |
| 724 | |||
| 725 | /////////////////////////  | 
            ||
| 726 | // Hard Status Actions //  | 
            ||
| 727 | /////////////////////////  | 
            ||
| 728 | |||
| 729 | /**  | 
            ||
| 730 | * Check and perform Confirm action.  | 
            ||
| 731 | *  | 
            ||
| 732 | * @return $this  | 
            ||
| 733 | */  | 
            ||
| 734 | 5 | public function doReserve()  | 
            |
| 742 | |||
| 743 | /**  | 
            ||
| 744 | * Check and perform Confirm action.  | 
            ||
| 745 | *  | 
            ||
| 746 | * @return $this  | 
            ||
| 747 | */  | 
            ||
| 748 | 2 | public function doConfirm()  | 
            |
| 758 | |||
| 759 | /**  | 
            ||
| 760 | * Check and perform Annulate action.  | 
            ||
| 761 | *  | 
            ||
| 762 | * @return $this  | 
            ||
| 763 | */  | 
            ||
| 764 | 2 | public function doAnnulate()  | 
            |
| 774 | |||
| 775 | /**  | 
            ||
| 776 | * Check and perform Serve action.  | 
            ||
| 777 | *  | 
            ||
| 778 | * @return $this  | 
            ||
| 779 | */  | 
            ||
| 780 | 3 | public function doServe()  | 
            |
| 790 | }  | 
            ||
| 791 | 
Since your code implements the magic getter
_get, this function will be called for any read access on an undefined variable. You can add the@propertyannotation to your class or interface to document the existence of this variable.If the property has read access only, you can use the @property-read annotation instead.
Of course, you may also just have mistyped another name, in which case you should fix the error.
See also the PhpDoc documentation for @property.