Passed
Push — master ( 26caeb...0e28f7 )
by Thomas
11:18
created

Student   B

Complexity

Total Complexity 46

Size/Duplication

Total Lines 303
Duplicated Lines 0 %

Importance

Changes 7
Bugs 3 Features 0
Metric Value
eloc 107
c 7
b 3
f 0
dl 0
loc 303
rs 8.72
wmc 46

31 Methods

Rating   Name   Duplication   Size   Complexity  
A title() 0 3 1
A institution() 0 3 1
A getLastnameAttribute() 0 7 2
A setEmailAttribute() 0 3 1
A real_enrollments() 0 6 1
A enrollments() 0 4 1
A scopeNewInPeriod() 0 3 1
A attendance() 0 3 1
A getFirstnameAttribute() 0 7 2
A comments() 0 3 1
A scopeComputedLeadType() 0 26 1
A getEmailAttribute() 0 3 1
A periodAbsences() 0 9 2
A registerMediaConversions() 0 5 1
A contacts() 0 3 1
A user() 0 3 1
A getNameAttribute() 0 7 3
A getIsEnrolledAttribute() 0 5 2
A profession() 0 3 1
A periodAttendance() 0 8 2
A getLeadStatusNameAttribute() 0 3 1
A phone() 0 3 1
A setFirstnameAttribute() 0 3 1
A leadType() 0 3 1
A getStudentAgeAttribute() 0 7 2
A lead_status() 0 19 4
A setLastnameAttribute() 0 3 1
A getStudentBirthdateAttribute() 0 7 2
A scopeEnrolled() 0 5 1
A enroll() 0 36 4
A books() 0 3 1

How to fix   Complexity   

Complex Class

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
2
3
namespace App\Models;
4
5
use App\Events\LeadStatusUpdatedEvent;
6
use App\Events\StudentDeleting;
7
use App\Events\StudentUpdated;
8
use Backpack\CRUD\app\Models\Traits\CrudTrait;
9
use Carbon\Carbon;
10
use Illuminate\Database\Eloquent\Builder;
11
use Illuminate\Database\Eloquent\Model;
12
use Illuminate\Support\Facades\App;
13
use Illuminate\Support\Str;
14
use Spatie\Activitylog\Traits\LogsActivity;
15
use Spatie\Image\Manipulations;
16
use Spatie\MediaLibrary\HasMedia;
17
use Spatie\MediaLibrary\InteractsWithMedia;
18
use Spatie\MediaLibrary\MediaCollections\Models\Media;
19
20
/**
21
 * @mixin IdeHelperStudent
22
 */
23
class Student extends Model implements HasMedia
24
{
25
    use CrudTrait;
0 ignored issues
show
introduced by
The trait Backpack\CRUD\app\Models\Traits\CrudTrait requires some properties which are not provided by App\Models\Student: $fakeColumns, $identifiableAttribute, $Type
Loading history...
26
    use InteractsWithMedia;
0 ignored issues
show
introduced by
The trait Spatie\MediaLibrary\InteractsWithMedia requires some properties which are not provided by App\Models\Student: $mediaCollections, $deletePreservingMedia, $fallbackPath, $mediaConversionRegistrations, $mediaConversions, $forceDeleting, $fallbackUrl, $unAttachedMediaLibraryItems, $collection_name
Loading history...
27
    use LogsActivity;
0 ignored issues
show
introduced by
The trait Spatie\Activitylog\Traits\LogsActivity requires some properties which are not provided by App\Models\Student: $enableLoggingModelsEvents, $oldAttributes
Loading history...
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());
0 ignored issues
show
Bug introduced by
array('student_id') of type array<integer,string> is incompatible with the type Illuminate\Database\Query\Expression|string expected by parameter $column of Illuminate\Database\Eloquent\Builder::pluck(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

87
        return $query->whereIn('id', Period::find($periodId)->newStudents()->pluck(/** @scrutinizer ignore-type */ ['student_id'])->toArray());
Loading history...
Bug introduced by
The method toArray() does not exist on Illuminate\Support\Collection. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

87
        return $query->whereIn('id', Period::find($periodId)->newStudents()->pluck(['student_id'])->/** @scrutinizer ignore-call */ toArray());

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug introduced by
array('student_id') of type array<integer,string> is incompatible with the type string expected by parameter $column of Illuminate\Database\Query\Builder::pluck(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

87
        return $query->whereIn('id', Period::find($periodId)->newStudents()->pluck(/** @scrutinizer ignore-type */ ['student_id'])->toArray());
Loading history...
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()
179
    {
180
        return $this->belongsTo(Title::class);
181
    }
182
183
    /** attributes */
184
    public function getFirstnameAttribute(): string
185
    {
186
        if ($this->user) {
187
            return Str::title($this->user->firstname);
0 ignored issues
show
Bug introduced by
The method title() does not exist on Illuminate\Support\Str. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

187
            return Str::/** @scrutinizer ignore-call */ title($this->user->firstname);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
188
        }
189
190
        return '';
191
    }
192
193
    public function getLastnameAttribute(): string
194
    {
195
        if ($this->user) {
196
            return Str::upper($this->user->lastname);
0 ignored issues
show
Bug introduced by
The method upper() does not exist on Illuminate\Support\Str. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

196
            return Str::/** @scrutinizer ignore-call */ upper($this->user->lastname);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
197
        }
198
199
        return '';
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');
0 ignored issues
show
Bug introduced by
Are you sure __('years old') of type array|string can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

219
            return Carbon::parse($this->birthdate)->age.' './** @scrutinizer ignore-type */ __('years old');
Loading history...
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;
0 ignored issues
show
Bug introduced by
The property name does not seem to exist on Illuminate\Database\Eloquent\Collection.
Loading history...
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,
0 ignored issues
show
Bug introduced by
Accessing id on the interface Illuminate\Contracts\Auth\Authenticatable suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
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)
324
    {
325
        $this->user->update(['email' => $value]);
326
    }
327
}
328