Passed
Pull Request — master (#272)
by
unknown
06:45
created

Student   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 312
Duplicated Lines 0 %

Importance

Changes 6
Bugs 3 Features 0
Metric Value
eloc 130
c 6
b 3
f 0
dl 0
loc 312
rs 8.96
wmc 43

28 Methods

Rating   Name   Duplication   Size   Complexity  
A title() 0 3 1
A institution() 0 3 1
A getLastnameAttribute() 0 6 2
A setEmailAttribute() 0 3 1
A real_enrollments() 0 6 1
A enrollments() 0 4 1
A attendance() 0 3 1
A getFirstnameAttribute() 0 6 2
A comments() 0 3 1
A scopeComputedLeadType() 0 31 1
A getEmailAttribute() 0 6 2
A periodAbsences() 0 11 2
A registerMediaConversions() 0 5 1
A contacts() 0 3 1
A user() 0 3 1
A getNameAttribute() 0 6 3
A profession() 0 3 1
A getIsEnrolledAttribute() 0 7 2
A periodAttendance() 0 10 2
A getLeadStatusAttribute() 0 16 5
A getLeadStatusNameAttribute() 0 3 1
A phone() 0 3 1
A setFirstnameAttribute() 0 3 1
A leadType() 0 3 1
A getStudentAgeAttribute() 0 3 1
A setLastnameAttribute() 0 3 1
A getStudentBirthdateAttribute() 0 3 1
A enroll() 0 37 4

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\StudentDeleted;
0 ignored issues
show
Bug introduced by
The type App\Events\StudentDeleted was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
7
use App\Events\StudentDeleting;
8
use App\Events\StudentUpdated;
9
use Backpack\CRUD\app\Models\Traits\CrudTrait;
10
use Carbon\Carbon;
11
use Illuminate\Database\Eloquent\Model;
12
use Illuminate\Support\Str;
13
use Spatie\Activitylog\Traits\LogsActivity;
14
use Spatie\Image\Manipulations;
15
use Illuminate\Support\Facades\App;
16
use Spatie\MediaLibrary\HasMedia;
17
use Spatie\MediaLibrary\InteractsWithMedia;
18
use Spatie\MediaLibrary\MediaCollections\Models\Media;
19
20
/**
21
 * App\Models\Student
22
 *
23
 * @property int $id
24
 * @property string $idnumber
25
 * @property string $address
26
 * @property string|null $zip_code
27
 * @property string|null $city
28
 * @property string|null $state
29
 * @property string|null $country
30
 * @property int|null $genre_id
31
 * @property string $birthdate
32
 * @property string|null $terms_accepted_at
33
 * @property \Illuminate\Support\Carbon|null $created_at
34
 * @property \Illuminate\Support\Carbon|null $updated_at
35
 * @property int|null $lead_type_id
36
 * @property string|null $how_did_you_know_us
37
 * @property int|null $force_update
38
 * @property int|null $profession_id
39
 * @property int|null $institution_id
40
 * @property string|null $account_holder
41
 * @property string|null $iban
42
 * @property string|null $bic
43
 * @property-read \Illuminate\Database\Eloquent\Collection|\Spatie\Activitylog\Models\Activity[] $activities
44
 * @property-read int|null $activities_count
45
 * @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\Attendance[] $attendance
46
 * @property-read int|null $attendance_count
47
 * @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\Comment[] $comments
48
 * @property-read int|null $comments_count
49
 * @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\Contact[] $contacts
50
 * @property-read int|null $contacts_count
51
 * @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\Enrollment[] $enrollments
52
 * @property-read int|null $enrollments_count
53
 * @property string $email
54
 * @property string $firstname
55
 * @property-read mixed $is_enrolled
56
 * @property string $lastname
57
 * @property-read mixed $lead_status
58
 * @property-read mixed $lead_status_name
59
 * @property-read string $name
60
 * @property-read mixed $student_age
61
 * @property-read mixed $student_birthdate
62
 * @property-read \App\Models\Institution|null $institution
63
 * @property-read \App\Models\LeadType|null $leadType
64
 * @property-read \Spatie\MediaLibrary\MediaCollections\Models\Collections\MediaCollection|Media[] $media
65
 * @property-read int|null $media_count
66
 * @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\PhoneNumber[] $phone
67
 * @property-read int|null $phone_count
68
 * @property-read \App\Models\Profession|null $profession
69
 * @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\Enrollment[] $real_enrollments
70
 * @property-read int|null $real_enrollments_count
71
 * @property-read \App\Models\Title $title
72
 * @property-read \App\Models\User $user
73
 * @method static \Illuminate\Database\Eloquent\Builder|Student computedLeadType($leadTypeId)
74
 * @method static \Illuminate\Database\Eloquent\Builder|Student newModelQuery()
75
 * @method static \Illuminate\Database\Eloquent\Builder|Student newQuery()
76
 * @method static \Illuminate\Database\Eloquent\Builder|Student query()
77
 * @method static \Illuminate\Database\Eloquent\Builder|Student whereAccountHolder($value)
78
 * @method static \Illuminate\Database\Eloquent\Builder|Student whereAddress($value)
79
 * @method static \Illuminate\Database\Eloquent\Builder|Student whereBic($value)
80
 * @method static \Illuminate\Database\Eloquent\Builder|Student whereBirthdate($value)
81
 * @method static \Illuminate\Database\Eloquent\Builder|Student whereCity($value)
82
 * @method static \Illuminate\Database\Eloquent\Builder|Student whereCountry($value)
83
 * @method static \Illuminate\Database\Eloquent\Builder|Student whereCreatedAt($value)
84
 * @method static \Illuminate\Database\Eloquent\Builder|Student whereForceUpdate($value)
85
 * @method static \Illuminate\Database\Eloquent\Builder|Student whereGenreId($value)
86
 * @method static \Illuminate\Database\Eloquent\Builder|Student whereHowDidYouKnowUs($value)
87
 * @method static \Illuminate\Database\Eloquent\Builder|Student whereIban($value)
88
 * @method static \Illuminate\Database\Eloquent\Builder|Student whereId($value)
89
 * @method static \Illuminate\Database\Eloquent\Builder|Student whereIdnumber($value)
90
 * @method static \Illuminate\Database\Eloquent\Builder|Student whereInstitutionId($value)
91
 * @method static \Illuminate\Database\Eloquent\Builder|Student whereLeadTypeId($value)
92
 * @method static \Illuminate\Database\Eloquent\Builder|Student whereProfessionId($value)
93
 * @method static \Illuminate\Database\Eloquent\Builder|Student whereState($value)
94
 * @method static \Illuminate\Database\Eloquent\Builder|Student whereTermsAcceptedAt($value)
95
 * @method static \Illuminate\Database\Eloquent\Builder|Student whereUpdatedAt($value)
96
 * @method static \Illuminate\Database\Eloquent\Builder|Student whereZipCode($value)
97
 * @mixin \Eloquent
98
 */
99
class Student extends Model implements HasMedia
100
{
101
    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...
102
    use InteractsWithMedia;
0 ignored issues
show
introduced by
The trait Spatie\MediaLibrary\InteractsWithMedia requires some properties which are not provided by App\Models\Student: $fallbackPath, $mediaConversionRegistrations, $forceDeleting, $fallbackUrl, $collection_name
Loading history...
103
    use LogsActivity;
104
105
    protected $dispatchesEvents = [
106
        'deleting' => StudentDeleting::class,
107
        'updated' => StudentUpdated::class,
108
    ];
109
110
    public $timestamps = true;
111
112
    protected $fillable = [
113
        'id',
114
        'idnumber',
115
        'firstname',
116
        'lastname',
117
        'email',
118
        'address',
119
        'city',
120
        'state',
121
        'country',
122
        'title_id',
123
        'birthdate',
124
        'terms_accepted_at',
125
        'created_at',
126
        'updated_at',
127
        'lead_type_id',
128
        'force_update',
129
        'profession_id',
130
        'institution_id',
131
        'zip_code',
132
        'iban',
133
        'bic',
134
    ];
135
136
    public $incrementing = false;
137
138
    protected $with = ['user', 'phone', 'institution', 'profession', 'title'];
139
140
    protected $appends = ['email', 'name', 'firstname', 'lastname', 'student_age', 'student_birthdate', 'lead_status', 'is_enrolled'];
141
142
    protected static $logUnguarded = true;
143
144
    public function scopeComputedLeadType($query, $leadTypeId)
145
    {
146
        return match ($leadTypeId) {
147
            1 => $query->whereHas('enrollments', function ($query) {
148
                return $query->whereHas('course', function ($q) {
149
                    $q->where('period_id', Period::get_default_period()->id);
150
                });
151
            }),
152
153
            2, 3 => $query->where('lead_type_id', $leadTypeId),
154
155
            4 => $query
156
                ->where('lead_type_id', $leadTypeId)
157
                ->orWhere(function ($query) {
158
                    $query
159
                        ->whereNull('lead_type_id')
160
                        ->whereHas('enrollments', function ($query) {
161
                            return $query
162
                                ->whereHas('course', function ($q) {
163
                                    $q->where('period_id', '!=', Period::get_default_period()->id);
164
                                });
165
                        })
166
                        ->whereDoesntHave('enrollments', function ($query) {
167
                            return $query
168
                                ->whereHas('course', function ($q) {
169
                                    $q->where('period_id', Period::get_default_period()->id);
170
                                });
171
                        });
172
                }),
173
174
            default => $query,
175
        };
176
    }
177
178
    public function registerMediaConversions(Media $media = null): void
179
    {
180
        $this->addMediaConversion('thumb')
181
            ->fit(Manipulations::FIT_MAX, 1200, 1200)
182
            ->optimize();
183
    }
184
185
    /** relations */
186
    public function user()
187
    {
188
        return $this->belongsTo(User::class, 'id', 'id');
189
    }
190
191
    public function attendance()
192
    {
193
        return $this->hasMany(Attendance::class);
194
    }
195
196
    public function periodAbsences(Period $period = null)
197
    {
198
        if ($period == null) {
199
            $period = Period::get_default_period();
200
        }
201
202
        return $this->hasMany(Attendance::class)
203
        ->where('attendance_type_id', 4) // absence
204
        ->whereHas('event', function ($q) use ($period) {
205
            return $q->whereHas('course', function ($c) use ($period) {
206
                return $c->where('period_id', $period->id);
207
            });
208
        });
209
    }
210
211
    public function periodAttendance(Period $period = null)
212
    {
213
        if ($period == null) {
214
            $period = Period::get_default_period();
215
        }
216
217
        return $this->hasMany(Attendance::class)
218
        ->whereHas('event', function ($q) use ($period) {
219
            return $q->whereHas('course', function ($c) use ($period) {
220
                return $c->where('period_id', $period->id);
221
            });
222
        });
223
    }
224
225
    public function contacts()
226
    {
227
        return $this->hasMany(Contact::class, 'student_id');
228
    }
229
230
    public function comments()
231
    {
232
        return $this->morphMany(Comment::class, 'commentable');
233
    }
234
235
    public function phone()
236
    {
237
        return $this->morphMany(PhoneNumber::class, 'phoneable');
238
    }
239
240
    public function enrollments()
241
    {
242
        return $this->hasMany(Enrollment::class)
243
            ->with('course');
244
    }
245
246
    public function leadType()
247
    {
248
        return $this->belongsTo(LeadType::class);
249
    }
250
251
    public function real_enrollments()
252
    {
253
        return $this->hasMany(Enrollment::class)
254
            ->with('course')
255
            ->whereIn('status_id', ['1', '2'])
256
            ->whereDoesntHave('childrenEnrollments');
257
    }
258
259
    public function institution()
260
    {
261
        return $this->belongsTo(Institution::class);
262
    }
263
264
    public function profession()
265
    {
266
        return $this->belongsTo(Profession::class);
267
    }
268
269
    public function title()
270
    {
271
        return $this->belongsTo(Title::class);
272
    }
273
274
    /** attributes */
275
    public function getFirstnameAttribute(): string
276
    {
277
        if ($this->user) {
278
            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

278
            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...
279
        }
280
        return '';
281
    }
282
283
    public function getLastnameAttribute(): string
284
    {
285
        if ($this->user) {
286
            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

286
            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...
287
        }
288
        return '';
289
    }
290
291
    public function getEmailAttribute(): string
292
    {
293
        if ($this->user) {
294
            return $this->user->email;
295
        }
296
        return '';
297
    }
298
299
    public function getNameAttribute(): string
300
    {
301
        if ($this->user) {
302
            return ($this->title ? ($this->title->title . ' ') : '') . $this->firstname.' '.$this->lastname;
303
        }
304
        return '';
305
    }
306
307
    public function getStudentAgeAttribute()
308
    {
309
        return Carbon::parse($this->birthdate)->age ?? '';
310
    }
311
312
    public function getStudentBirthdateAttribute()
313
    {
314
        return Carbon::parse($this->birthdate)->locale(App::getLocale())->isoFormat('LL');
315
    }
316
317
    public function getIsEnrolledAttribute()
318
    {
319
        // if the student is currently enrolled
320
        if ($this->enrollments()->whereHas('course', function ($q) {
321
            return $q->where('period_id', Period::get_default_period()->id);
322
        })->count() > 0) {
323
            return 1;
324
        }
325
    }
326
327
    public function getLeadStatusNameAttribute()
328
    {
329
        return LeadType::find($this->lead_status)->name;
330
    }
331
332
    public function getLeadStatusAttribute()
333
    {
334
        // if the student is currently enrolled, they are CONVERTED
335
        if ($this->is_enrolled) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->is_enrolled of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
336
            return 1;
337
        }
338
339
        // if the student has a special status, return it
340
        if ($this->lead_type_id == 3 || $this->lead_type_id == 2) {
341
            return $this->lead_type_id;
342
        }
343
        // if the student was previously enrolled, they must be potential students
344
        elseif ($this->has('enrollments')) {
345
            return 4;
346
        } else {
347
            return 3;
348
        }
349
        // otherwise, their status cannot be determined and should be left blank
350
    }
351
352
    /** functions */
353
354
    /**
355
     * enroll the student in a course.
356
     * If the course has any children, we also enroll the student in the children courses.
357
     */
358
    public function enroll(Course $course): int
359
    {
360
        // avoid duplicates by retrieving an potential existing enrollment for the same course
361
        $enrollment = Enrollment::firstOrCreate([
362
            'student_id' =>  $this->id,
363
            'course_id' => $course->id,
364
        ],
365
        [
366
            '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...
367
        ]);
368
369
        // if the course has children, enroll in children as well.
370
        if ($course->children_count > 0) {
371
            foreach ($course->children as $children_course) {
372
                Enrollment::firstOrCreate([
373
                    'student_id' =>  $this->id,
374
                    'course_id' => $children_course->id,
375
                    'parent_id' => $enrollment->id,
376
                ],
377
                [
378
                    'responsible_id' => backpack_user()->id ?? 1,
379
                ]);
380
            }
381
        }
382
383
        //$this->update(['lead_type_id' => null]); // fallback to default (converted)
384
385
        // to subscribe the student to mailing lists and so on
386
        $listId = config('mailing-system.mailerlite.activeStudentsListId');
387
        LeadStatusUpdatedEvent::dispatch($this, $listId);
388
389
        foreach ($this->contacts as $contact)
390
        {
391
            LeadStatusUpdatedEvent::dispatch($contact, $listId);
392
        }
393
394
        return $enrollment->id;
395
    }
396
397
    /** SETTERS */
398
    public function setFirstnameAttribute($value)
399
    {
400
        $this->user->update(['firstname' => $value]);
401
    }
402
403
    public function setLastnameAttribute($value)
404
    {
405
        $this->user->update(['lastname' => $value]);
406
    }
407
408
    public function setEmailAttribute($value)
409
    {
410
        $this->user->update(['email' => $value]);
411
    }
412
}
413