| Total Complexity | 46 | 
| Total Lines | 303 | 
| Duplicated Lines | 0 % | 
| Changes | 7 | ||
| Bugs | 3 | Features | 0 | 
Complex classes like Student 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.
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 Student, and based on these observations, apply Extract Interface, too.
| 1 | <?php  | 
            ||
| 23 | class Student extends Model implements HasMedia  | 
            ||
| 24 | { | 
            ||
| 25 | use CrudTrait;  | 
            ||
| 
                                                                                                    
                        
                         | 
                |||
| 26 | use InteractsWithMedia;  | 
            ||
| 27 | use LogsActivity;  | 
            ||
| 28 | |||
| 29 | protected $dispatchesEvents = [  | 
            ||
| 30 | 'deleting' => StudentDeleting::class,  | 
            ||
| 31 | 'updated' => StudentUpdated::class,  | 
            ||
| 32 | ];  | 
            ||
| 33 | |||
| 34 | public $timestamps = true;  | 
            ||
| 35 | |||
| 36 | protected $guarded = [];  | 
            ||
| 37 | |||
| 38 | public $incrementing = false;  | 
            ||
| 39 | |||
| 40 | protected $with = ['user', 'phone', 'institution', 'profession', 'title'];  | 
            ||
| 41 | |||
| 42 | protected $appends = ['email', 'name', 'firstname', 'lastname', 'student_age', 'student_birthdate', 'is_enrolled'];  | 
            ||
| 43 | |||
| 44 | protected static bool $logUnguarded = true;  | 
            ||
| 45 | |||
| 46 | |||
| 47 | public function scopeEnrolled($query)  | 
            ||
| 48 |     { | 
            ||
| 49 |         return $query->whereHas('enrollments', function ($q) { | 
            ||
| 50 |             return $q->whereHas('course', function ($q) { | 
            ||
| 51 |                 return $q->where('period_id', Period::get_default_period()->id); | 
            ||
| 52 | });  | 
            ||
| 53 | });  | 
            ||
| 54 | }  | 
            ||
| 55 | |||
| 56 | public function scopeComputedLeadType($query, $leadTypeId)  | 
            ||
| 57 |     { | 
            ||
| 58 |         return match ($leadTypeId) { | 
            ||
| 59 |             1 => $query->whereHas('enrollments', fn ($query) => $query->whereHas('course', function ($q) { | 
            ||
| 60 |                 $q->where('period_id', Period::get_default_period()->id); | 
            ||
| 61 | })),  | 
            ||
| 62 | |||
| 63 |             2, 3 => $query->where('lead_type_id', $leadTypeId), | 
            ||
| 64 | |||
| 65 | 4 => $query  | 
            ||
| 66 |                 ->where('lead_type_id', $leadTypeId) | 
            ||
| 67 |                 ->orWhere(function ($query) { | 
            ||
| 68 | $query  | 
            ||
| 69 |                         ->whereNull('lead_type_id') | 
            ||
| 70 |                         ->whereHas('enrollments', fn ($query) => $query | 
            ||
| 71 |                             ->whereHas('course', function ($q) { | 
            ||
| 72 |                                 $q->where('period_id', '!=', Period::get_default_period()->id); | 
            ||
| 73 | }))  | 
            ||
| 74 |                         ->whereDoesntHave('enrollments', fn ($query) => $query | 
            ||
| 75 |                             ->whereHas('course', function ($q) { | 
            ||
| 76 |                                 $q->where('period_id', Period::get_default_period()->id); | 
            ||
| 77 | }));  | 
            ||
| 78 | }  | 
            ||
| 79 | ),  | 
            ||
| 80 | |||
| 81 | default => $query,  | 
            ||
| 82 | };  | 
            ||
| 83 | }  | 
            ||
| 84 | |||
| 85 | public function scopeNewInPeriod(Builder $query, int $periodId)  | 
            ||
| 86 |     { | 
            ||
| 87 |         return $query->whereIn('id', Period::find($periodId)->newStudents()->pluck(['student_id'])->toArray()); | 
            ||
| 88 | }  | 
            ||
| 89 | |||
| 90 | public function registerMediaConversions(Media $media = null): void  | 
            ||
| 91 |     { | 
            ||
| 92 |         $this->addMediaConversion('thumb') | 
            ||
| 93 | ->fit(Manipulations::FIT_MAX, 1200, 1200)  | 
            ||
| 94 | ->optimize();  | 
            ||
| 95 | }  | 
            ||
| 96 | |||
| 97 | /** relations */  | 
            ||
| 98 | public function user()  | 
            ||
| 99 |     { | 
            ||
| 100 | return $this->belongsTo(User::class, 'id', 'id');  | 
            ||
| 101 | }  | 
            ||
| 102 | |||
| 103 | public function attendance()  | 
            ||
| 104 |     { | 
            ||
| 105 | return $this->hasMany(Attendance::class);  | 
            ||
| 106 | }  | 
            ||
| 107 | |||
| 108 | public function periodAbsences(Period $period = null)  | 
            ||
| 109 |     { | 
            ||
| 110 |         if ($period == null) { | 
            ||
| 111 | $period = Period::get_default_period();  | 
            ||
| 112 | }  | 
            ||
| 113 | |||
| 114 | return $this->hasMany(Attendance::class)  | 
            ||
| 115 |         ->where('attendance_type_id', 4) // absence | 
            ||
| 116 |         ->whereHas('event', fn ($q) => $q->whereHas('course', fn ($c) => $c->where('period_id', $period->id))); | 
            ||
| 117 | }  | 
            ||
| 118 | |||
| 119 | public function periodAttendance(Period $period = null)  | 
            ||
| 120 |     { | 
            ||
| 121 |         if ($period == null) { | 
            ||
| 122 | $period = Period::get_default_period();  | 
            ||
| 123 | }  | 
            ||
| 124 | |||
| 125 | return $this->hasMany(Attendance::class)  | 
            ||
| 126 |         ->whereHas('event', fn ($q) => $q->whereHas('course', fn ($c) => $c->where('period_id', $period->id))); | 
            ||
| 127 | }  | 
            ||
| 128 | |||
| 129 | public function contacts()  | 
            ||
| 130 |     { | 
            ||
| 131 | return $this->hasMany(Contact::class, 'student_id');  | 
            ||
| 132 | }  | 
            ||
| 133 | |||
| 134 | public function comments()  | 
            ||
| 135 |     { | 
            ||
| 136 | return $this->morphMany(Comment::class, 'commentable');  | 
            ||
| 137 | }  | 
            ||
| 138 | |||
| 139 | public function phone()  | 
            ||
| 140 |     { | 
            ||
| 141 | return $this->morphMany(PhoneNumber::class, 'phoneable');  | 
            ||
| 142 | }  | 
            ||
| 143 | |||
| 144 | public function enrollments()  | 
            ||
| 145 |     { | 
            ||
| 146 | return $this->hasMany(Enrollment::class)  | 
            ||
| 147 |             ->with('course'); | 
            ||
| 148 | }  | 
            ||
| 149 | |||
| 150 | public function leadType()  | 
            ||
| 151 |     { | 
            ||
| 152 | return $this->belongsTo(LeadType::class);  | 
            ||
| 153 | }  | 
            ||
| 154 | |||
| 155 | public function real_enrollments()  | 
            ||
| 156 |     { | 
            ||
| 157 | return $this->hasMany(Enrollment::class)  | 
            ||
| 158 |             ->with('course') | 
            ||
| 159 |             ->whereIn('status_id', ['1', '2']) | 
            ||
| 160 |             ->whereDoesntHave('childrenEnrollments'); | 
            ||
| 161 | }  | 
            ||
| 162 | |||
| 163 | public function institution()  | 
            ||
| 164 |     { | 
            ||
| 165 | return $this->belongsTo(Institution::class);  | 
            ||
| 166 | }  | 
            ||
| 167 | |||
| 168 | public function profession()  | 
            ||
| 169 |     { | 
            ||
| 170 | return $this->belongsTo(Profession::class);  | 
            ||
| 171 | }  | 
            ||
| 172 | |||
| 173 | public function books()  | 
            ||
| 174 |     { | 
            ||
| 175 |         return $this->belongsToMany(Book::class)->withPivot('id', 'code', 'status_id', 'expiry_date'); | 
            ||
| 176 | }  | 
            ||
| 177 | |||
| 178 | public function title()  | 
            ||
| 181 | }  | 
            ||
| 182 | |||
| 183 | /** attributes */  | 
            ||
| 184 | public function getFirstnameAttribute(): string  | 
            ||
| 185 |     { | 
            ||
| 186 |         if ($this->user) { | 
            ||
| 187 | return Str::title($this->user->firstname);  | 
            ||
| 188 | }  | 
            ||
| 189 | |||
| 190 | return '';  | 
            ||
| 191 | }  | 
            ||
| 192 | |||
| 193 | public function getLastnameAttribute(): string  | 
            ||
| 200 | }  | 
            ||
| 201 | |||
| 202 | public function getEmailAttribute(): ?string  | 
            ||
| 203 |     { | 
            ||
| 204 | return $this?->user?->email;  | 
            ||
| 205 | }  | 
            ||
| 206 | |||
| 207 | public function getNameAttribute(): string  | 
            ||
| 208 |     { | 
            ||
| 209 |         if ($this->user) { | 
            ||
| 210 | return ($this->title ? ($this->title->title.' ') : '').$this->firstname.' '.$this->lastname;  | 
            ||
| 211 | }  | 
            ||
| 212 | |||
| 213 | return '';  | 
            ||
| 214 | }  | 
            ||
| 215 | |||
| 216 | public function getStudentAgeAttribute()  | 
            ||
| 217 |     { | 
            ||
| 218 |         if ($this->birthdate) { | 
            ||
| 219 |             return Carbon::parse($this->birthdate)->age.' '.__('years old'); | 
            ||
| 220 | }  | 
            ||
| 221 | |||
| 222 | return '';  | 
            ||
| 223 | }  | 
            ||
| 224 | |||
| 225 | public function getStudentBirthdateAttribute()  | 
            ||
| 226 |     { | 
            ||
| 227 |         if ($this->birthdate) { | 
            ||
| 228 |             return Carbon::parse($this->birthdate)->locale(App::getLocale())->isoFormat('LL'); | 
            ||
| 229 | }  | 
            ||
| 230 | |||
| 231 | return '';  | 
            ||
| 232 | }  | 
            ||
| 233 | |||
| 234 | public function getIsEnrolledAttribute()  | 
            ||
| 235 |     { | 
            ||
| 236 | // if the student is currently enrolled  | 
            ||
| 237 |         if ($this->enrollments()->whereHas('course', fn ($q) => $q->where('period_id', Period::get_default_period()->id))->count() > 0) { | 
            ||
| 238 | return 1;  | 
            ||
| 239 | }  | 
            ||
| 240 | }  | 
            ||
| 241 | |||
| 242 | public function getLeadStatusNameAttribute()  | 
            ||
| 243 |     { | 
            ||
| 244 | return LeadType::find($this->lead_type_id)->name ?? null;  | 
            ||
| 245 | }  | 
            ||
| 246 | |||
| 247 | public function lead_status()  | 
            ||
| 248 |     { | 
            ||
| 249 | // if the student is currently enrolled, they are CONVERTED  | 
            ||
| 250 |         if ($this->is_enrolled) { | 
            ||
| 251 | return 1;  | 
            ||
| 252 | }  | 
            ||
| 253 | |||
| 254 | // if the student has a special status, return it  | 
            ||
| 255 |         if ($this->leadType != null) { | 
            ||
| 256 | return $this->leadType->id;  | 
            ||
| 257 | }  | 
            ||
| 258 | |||
| 259 | // if the student was previously enrolled, they must be potential students  | 
            ||
| 260 |         if ($this->has('enrollments')) { | 
            ||
| 261 | return 4;  | 
            ||
| 262 | }  | 
            ||
| 263 | |||
| 264 | // otherwise, their status cannot be determined and should be left blank  | 
            ||
| 265 | return null;  | 
            ||
| 266 | }  | 
            ||
| 267 | |||
| 268 | /** functions */  | 
            ||
| 269 | |||
| 270 | /**  | 
            ||
| 271 | * enroll the student in a course.  | 
            ||
| 272 | * If the course has any children, we also enroll the student in the children courses.  | 
            ||
| 273 | */  | 
            ||
| 274 | public function enroll(Course $course): int  | 
            ||
| 275 |     { | 
            ||
| 276 | // avoid duplicates by retrieving an potential existing enrollment for the same course  | 
            ||
| 277 | $enrollment = Enrollment::firstOrCreate([  | 
            ||
| 278 | 'student_id' => $this->id,  | 
            ||
| 279 | 'course_id' => $course->id,  | 
            ||
| 280 | ],  | 
            ||
| 281 | [  | 
            ||
| 282 | 'responsible_id' => backpack_user()->id ?? 1,  | 
            ||
| 283 | ]);  | 
            ||
| 284 | |||
| 285 | // if the course has children, enroll in children as well.  | 
            ||
| 286 |         if ($course->children_count > 0) { | 
            ||
| 287 |             foreach ($course->children as $children_course) { | 
            ||
| 288 | Enrollment::firstOrCreate([  | 
            ||
| 289 | 'student_id' => $this->id,  | 
            ||
| 290 | 'course_id' => $children_course->id,  | 
            ||
| 291 | 'parent_id' => $enrollment->id,  | 
            ||
| 292 | ],  | 
            ||
| 293 | [  | 
            ||
| 294 | 'responsible_id' => backpack_user()->id ?? 1,  | 
            ||
| 295 | ]);  | 
            ||
| 296 | }  | 
            ||
| 297 | }  | 
            ||
| 298 | |||
| 299 | $this->update(['lead_type_id' => null]); // fallback to default (converted)  | 
            ||
| 300 | |||
| 301 | // to subscribe the student to mailing lists and so on  | 
            ||
| 302 |         $listId = config('mailing-system.mailerlite.activeStudentsListId'); | 
            ||
| 303 | LeadStatusUpdatedEvent::dispatch($this, $listId);  | 
            ||
| 304 | |||
| 305 |         foreach ($this->contacts as $contact) { | 
            ||
| 306 | LeadStatusUpdatedEvent::dispatch($contact, $listId);  | 
            ||
| 307 | }  | 
            ||
| 308 | |||
| 309 | return $enrollment->id;  | 
            ||
| 310 | }  | 
            ||
| 311 | |||
| 312 | /** SETTERS */  | 
            ||
| 313 | public function setFirstnameAttribute($value)  | 
            ||
| 314 |     { | 
            ||
| 315 | $this->user->update(['firstname' => $value]);  | 
            ||
| 316 | }  | 
            ||
| 317 | |||
| 318 | public function setLastnameAttribute($value)  | 
            ||
| 319 |     { | 
            ||
| 320 | $this->user->update(['lastname' => $value]);  | 
            ||
| 321 | }  | 
            ||
| 322 | |||
| 323 | public function setEmailAttribute($value)  | 
            ||
| 326 | }  | 
            ||
| 327 | }  | 
            ||
| 328 |