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 Job 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 Job, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 58 | class Job extends Model |
||
| 59 | { |
||
| 60 | use SoftDeletes, ForUser; |
||
| 61 | use Searchable { |
||
| 62 | getIndexBody as parentGetIndexBody; |
||
| 63 | } |
||
| 64 | |||
| 65 | const MONTH = 1; |
||
| 66 | const YEAR = 2; |
||
| 67 | const WEEK = 3; |
||
| 68 | const HOUR = 4; |
||
| 69 | |||
| 70 | const STUDENT = 1; |
||
| 71 | const JUNIOR = 2; |
||
| 72 | const MID = 3; |
||
| 73 | const SENIOR = 4; |
||
| 74 | const LEAD = 5; |
||
| 75 | const MANAGER = 6; |
||
| 76 | |||
| 77 | const NET = 0; |
||
| 78 | const GROSS = 1; |
||
| 79 | |||
| 80 | /** |
||
| 81 | * Filling each field adds points to job offer score. |
||
| 82 | */ |
||
| 83 | const SCORE_CONFIG = [ |
||
| 84 | 'job' => ['salary_from' => 25, 'salary_to' => 25, 'city' => 15, 'seniority_id' => 5], |
||
| 85 | 'firm' => ['name' => 15, 'logo' => 5, 'website' => 1, 'description' => 5] |
||
| 86 | ]; |
||
| 87 | |||
| 88 | /** |
||
| 89 | * The attributes that are mass assignable. |
||
| 90 | * |
||
| 91 | * @var array |
||
| 92 | */ |
||
| 93 | protected $fillable = [ |
||
| 94 | 'title', |
||
| 95 | 'description', |
||
| 96 | 'requirements', |
||
| 97 | 'recruitment', |
||
| 98 | 'is_remote', |
||
| 99 | 'is_gross', |
||
| 100 | 'remote_range', |
||
| 101 | 'country_id', |
||
| 102 | 'salary_from', |
||
| 103 | 'salary_to', |
||
| 104 | 'currency_id', |
||
| 105 | 'rate_id', |
||
| 106 | 'employment_id', |
||
| 107 | 'deadline', // column does not really exist in db (model attribute instead) |
||
| 108 | 'deadline_at', |
||
| 109 | 'email', |
||
| 110 | 'enable_apply', |
||
| 111 | 'seniority_id', |
||
| 112 | 'plan_id' |
||
| 113 | ]; |
||
| 114 | |||
| 115 | /** |
||
| 116 | * Default fields values. |
||
| 117 | * |
||
| 118 | * @var array |
||
| 119 | */ |
||
| 120 | protected $attributes = [ |
||
| 121 | 'enable_apply' => true, |
||
| 122 | 'is_remote' => false, |
||
| 123 | 'title' => '' |
||
| 124 | ]; |
||
| 125 | |||
| 126 | /** |
||
| 127 | * Cast to when calling toArray() (for example before index in elasticsearch). |
||
| 128 | * |
||
| 129 | * @var array |
||
| 130 | */ |
||
| 131 | protected $casts = [ |
||
| 132 | 'is_remote' => 'boolean', |
||
| 133 | 'is_boost' => 'boolean', |
||
| 134 | 'is_gross' => 'boolean', |
||
| 135 | 'is_publish' => 'boolean', |
||
| 136 | 'is_ads' => 'boolean', |
||
| 137 | 'is_highlight' => 'boolean', |
||
| 138 | 'is_on_top' => 'boolean' |
||
| 139 | ]; |
||
| 140 | |||
| 141 | /** |
||
| 142 | * @var string |
||
| 143 | */ |
||
| 144 | protected $dateFormat = 'Y-m-d H:i:se'; |
||
| 145 | |||
| 146 | /** |
||
| 147 | * @var array |
||
| 148 | */ |
||
| 149 | protected $dates = ['created_at', 'updated_at', 'deadline_at', 'boost_at']; |
||
| 150 | |||
| 151 | /** |
||
| 152 | * @var array |
||
| 153 | */ |
||
| 154 | protected $appends = ['deadline']; |
||
| 155 | |||
| 156 | /** |
||
| 157 | * Elasticsearch type mapping |
||
| 158 | * |
||
| 159 | * @var array |
||
| 160 | */ |
||
| 161 | protected $mapping = [ |
||
| 162 | "id" => [ |
||
| 163 | "type" => "long" |
||
| 164 | ], |
||
| 165 | "locations" => [ |
||
| 166 | "type" => "nested", |
||
| 167 | "properties" => [ |
||
| 168 | "city" => [ |
||
| 169 | "type" => "string", |
||
| 170 | "analyzer" => "keyword_asciifolding_analyzer", |
||
| 171 | "fields" => [ |
||
| 172 | "original" => ["type" => "text", "analyzer" => "keyword_analyzer", "fielddata" => true] |
||
| 173 | ] |
||
| 174 | ], |
||
| 175 | "coordinates" => [ |
||
| 176 | "type" => "geo_point" |
||
| 177 | ] |
||
| 178 | ] |
||
| 179 | ], |
||
| 180 | "title" => [ |
||
| 181 | "type" => "text", |
||
| 182 | "analyzer" => "default_analyzer" |
||
| 183 | ], |
||
| 184 | "description" => [ |
||
| 185 | "type" => "text", |
||
| 186 | "analyzer" => "default_analyzer" |
||
| 187 | ], |
||
| 188 | "requirements" => [ |
||
| 189 | "type" => "text", |
||
| 190 | "analyzer" => "default_analyzer" |
||
| 191 | ], |
||
| 192 | "is_remote" => [ |
||
| 193 | "type" => "boolean" |
||
| 194 | ], |
||
| 195 | "remote_range" => [ |
||
| 196 | "type" => "integer" |
||
| 197 | ], |
||
| 198 | "tags" => [ |
||
| 199 | "type" => "text", |
||
| 200 | "fields" => [ |
||
| 201 | "original" => ["type" => "keyword"] |
||
| 202 | ] |
||
| 203 | ], |
||
| 204 | "firm" => [ |
||
| 205 | "type" => "object", |
||
| 206 | "properties" => [ |
||
| 207 | "name" => [ |
||
| 208 | "type" => "text", |
||
| 209 | "analyzer" => "default_analyzer", |
||
| 210 | "fields" => [ |
||
| 211 | // filtrujemy firmy po tym polu |
||
| 212 | "original" => ["type" => "text", "analyzer" => "keyword_analyzer", "fielddata" => true] |
||
| 213 | ] |
||
| 214 | ], |
||
| 215 | "slug" => [ |
||
| 216 | "type" => "text", |
||
| 217 | "analyzer" => "keyword_analyzer" |
||
| 218 | ] |
||
| 219 | ] |
||
| 220 | ], |
||
| 221 | "created_at" => [ |
||
| 222 | "type" => "date", |
||
| 223 | "format" => "yyyy-MM-dd HH:mm:ss" |
||
| 224 | ], |
||
| 225 | "updated_at" => [ |
||
| 226 | "type" => "date", |
||
| 227 | "format" => "yyyy-MM-dd HH:mm:ss" |
||
| 228 | ], |
||
| 229 | "deadline_at" => [ |
||
| 230 | "type" => "date", |
||
| 231 | "format" => "yyyy-MM-dd HH:mm:ss" |
||
| 232 | ], |
||
| 233 | "boost_at" => [ |
||
| 234 | "type" => "date", |
||
| 235 | "format" => "yyyy-MM-dd HH:mm:ss" |
||
| 236 | ], |
||
| 237 | "salary" => [ |
||
| 238 | "type" => "float" |
||
| 239 | ], |
||
| 240 | "score" => [ |
||
| 241 | "type" => "long" |
||
| 242 | ], |
||
| 243 | "is_boost" => [ |
||
| 244 | "type" => "boolean" |
||
| 245 | ], |
||
| 246 | "is_publish" => [ |
||
| 247 | "type" => "boolean" |
||
| 248 | ], |
||
| 249 | "is_ads" => [ |
||
| 250 | "type" => "boolean" |
||
| 251 | ], |
||
| 252 | "is_on_top" => [ |
||
| 253 | "type" => "boolean" |
||
| 254 | ], |
||
| 255 | "is_highlight" => [ |
||
| 256 | "type" => "boolean" |
||
| 257 | ] |
||
| 258 | ]; |
||
| 259 | |||
| 260 | /** |
||
| 261 | * We need to set firm id to null offer is private |
||
| 262 | */ |
||
| 263 | public static function boot() |
||
| 285 | |||
| 286 | /** |
||
| 287 | * @return string[] |
||
| 288 | */ |
||
| 289 | public static function getRatesList() |
||
| 293 | |||
| 294 | /** |
||
| 295 | * @return string[] |
||
| 296 | */ |
||
| 297 | public static function getTaxList() |
||
| 301 | |||
| 302 | /** |
||
| 303 | * @return string[] |
||
| 304 | */ |
||
| 305 | public static function getEmploymentList() |
||
| 309 | |||
| 310 | /** |
||
| 311 | * @return string[] |
||
| 312 | */ |
||
| 313 | public static function getSeniorityList() |
||
| 317 | |||
| 318 | /** |
||
| 319 | * @return array |
||
| 320 | */ |
||
| 321 | public static function getRemoteRangeList() |
||
| 331 | |||
| 332 | /** |
||
| 333 | * @return int |
||
| 334 | */ |
||
| 335 | public function getScore() |
||
| 366 | |||
| 367 | /** |
||
| 368 | * Scope for currently active job offers |
||
| 369 | * |
||
| 370 | * @param \Illuminate\Database\Query\Builder $query |
||
| 371 | * @return \Illuminate\Database\Query\Builder |
||
| 372 | */ |
||
| 373 | public function scopePriorDeadline($query) |
||
| 377 | |||
| 378 | /** |
||
| 379 | * @return HasMany |
||
| 380 | */ |
||
| 381 | public function locations() |
||
| 387 | |||
| 388 | /** |
||
| 389 | * @return \Illuminate\Database\Eloquent\Relations\MorphOne |
||
| 390 | */ |
||
| 391 | public function page() |
||
| 395 | |||
| 396 | /** |
||
| 397 | * @return \Illuminate\Database\Eloquent\Relations\BelongsTo |
||
| 398 | */ |
||
| 399 | public function firm() |
||
| 403 | |||
| 404 | /** |
||
| 405 | * @return \Illuminate\Database\Eloquent\Relations\BelongsTo |
||
| 406 | */ |
||
| 407 | public function currency() |
||
| 411 | |||
| 412 | /** |
||
| 413 | * @return \Illuminate\Database\Eloquent\Relations\HasMany |
||
| 414 | */ |
||
| 415 | public function referers() |
||
| 419 | |||
| 420 | /** |
||
| 421 | * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany |
||
| 422 | */ |
||
| 423 | public function tags() |
||
| 427 | |||
| 428 | /** |
||
| 429 | * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany |
||
| 430 | */ |
||
| 431 | public function features() |
||
| 435 | |||
| 436 | /** |
||
| 437 | * @return \Illuminate\Database\Eloquent\Relations\HasMany |
||
| 438 | */ |
||
| 439 | public function subscribers() |
||
| 443 | |||
| 444 | /** |
||
| 445 | * @return \Illuminate\Database\Eloquent\Relations\HasMany |
||
| 446 | */ |
||
| 447 | public function applications() |
||
| 451 | |||
| 452 | /** |
||
| 453 | * @return \Illuminate\Database\Eloquent\Relations\BelongsTo |
||
| 454 | */ |
||
| 455 | public function user() |
||
| 459 | |||
| 460 | /** |
||
| 461 | * @return \Illuminate\Database\Eloquent\Relations\BelongsTo |
||
| 462 | */ |
||
| 463 | public function country() |
||
| 467 | |||
| 468 | /** |
||
| 469 | * @return \Illuminate\Database\Eloquent\Relations\HasMany |
||
| 470 | */ |
||
| 471 | public function payments() |
||
| 475 | |||
| 476 | /** |
||
| 477 | * @return \Illuminate\Database\Eloquent\Relations\BelongsTo |
||
| 478 | */ |
||
| 479 | public function plan() |
||
| 483 | |||
| 484 | /** |
||
| 485 | * @param string $title |
||
| 486 | */ |
||
| 487 | View Code Duplication | public function setTitleAttribute($title) |
|
| 494 | |||
| 495 | /** |
||
| 496 | * @param string $value |
||
| 497 | */ |
||
| 498 | public function setSalaryFromAttribute($value) |
||
| 502 | |||
| 503 | /** |
||
| 504 | * @param string $value |
||
| 505 | */ |
||
| 506 | public function setSalaryToAttribute($value) |
||
| 510 | |||
| 511 | /** |
||
| 512 | * @param int $value |
||
| 513 | */ |
||
| 514 | public function setDeadlineAttribute($value) |
||
| 518 | |||
| 519 | /** |
||
| 520 | * @return int |
||
| 521 | */ |
||
| 522 | public function getDeadlineAttribute() |
||
| 526 | |||
| 527 | /** |
||
| 528 | * @return bool |
||
| 529 | */ |
||
| 530 | public function getIsExpiredAttribute() |
||
| 534 | |||
| 535 | /** |
||
| 536 | * @return mixed |
||
| 537 | */ |
||
| 538 | public function getCityAttribute() |
||
| 542 | |||
| 543 | /** |
||
| 544 | * @return string |
||
| 545 | */ |
||
| 546 | public function getCurrencySymbolAttribute() |
||
| 550 | |||
| 551 | /** |
||
| 552 | * @param int $userId |
||
| 553 | */ |
||
| 554 | public function setDefaultUserId($userId) |
||
| 560 | |||
| 561 | /** |
||
| 562 | * @param mixed $features |
||
| 563 | */ |
||
| 564 | public function setDefaultFeatures($features) |
||
| 573 | |||
| 574 | /** |
||
| 575 | * @param int $planId |
||
| 576 | */ |
||
| 577 | public function setDefaultPlanId($planId) |
||
| 583 | |||
| 584 | /** |
||
| 585 | * @return bool |
||
| 586 | */ |
||
| 587 | public function isPlanOngoing() |
||
| 595 | |||
| 596 | /** |
||
| 597 | * @return Payment |
||
| 598 | */ |
||
| 599 | public function getUnpaidPayment() |
||
| 603 | |||
| 604 | /** |
||
| 605 | * @return string|null |
||
| 606 | */ |
||
| 607 | public function getPaymentUuid() |
||
| 611 | |||
| 612 | /** |
||
| 613 | * @param string $url |
||
| 614 | */ |
||
| 615 | public function addReferer($url) |
||
| 627 | |||
| 628 | /** |
||
| 629 | * Check if user has applied for this job offer. |
||
| 630 | * |
||
| 631 | * @param int|null $userId |
||
| 632 | * @param string $sessionId |
||
| 633 | * @return boolean |
||
| 634 | */ |
||
| 635 | public function hasApplied($userId, $sessionId) |
||
| 643 | |||
| 644 | /** |
||
| 645 | * @return array |
||
| 646 | */ |
||
| 647 | protected function getIndexBody() |
||
| 703 | |||
| 704 | /** |
||
| 705 | * @param float|null $salary |
||
| 706 | * @return float|null |
||
| 707 | */ |
||
| 708 | private function monthlySalary($salary) |
||
| 725 | } |
||
| 726 |
If you implement
__calland you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.This is often the case, when
__callis implemented by a parent class and only the child class knows which methods exist: