From 3e24a3c7ee0fecae6824d7f9b7fbd0910c7eb39f Mon Sep 17 00:00:00 2001 From: horacioskrp Date: Fri, 13 Nov 2026 14:05:13 +0000 Subject: [PATCH 01/12] =?UTF-8?q?refactor(models):=20typage=20g=C3=A9n?= =?UTF-8?q?=C3=A9rique=20des=20relations=20Eloquent=20+=20r=C3=A9duction?= =?UTF-8?q?=20baseline=20PHPStan=20(463=20=E2=86=92=20250)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Models/AbsencePermission.php | 12 + app/Models/AcademicPeriod.php | 6 + app/Models/AcademicYear.php | 2 + app/Models/AccountingTransaction.php | 6 + app/Models/ArchivedDocument.php | 6 + app/Models/Attendance.php | 12 + app/Models/AttendanceRecord.php | 9 + app/Models/AuditLog.php | 3 + app/Models/Backup.php | 6 + app/Models/BulletinTemplate.php | 6 + app/Models/CalendarEvent.php | 6 + app/Models/CashAccount.php | 6 + app/Models/ClassSubject.php | 10 + app/Models/Classroom.php | 8 + app/Models/ClassroomType.php | 8 +- app/Models/DocumentHeader.php | 3 + app/Models/DocumentIssuance.php | 9 + app/Models/DocumentTag.php | 3 + app/Models/DocumentTemplate.php | 6 + app/Models/EmployeeAllowance.php | 6 + app/Models/EmployeeProfile.php | 15 +- app/Models/Enrollment.php | 18 + app/Models/Evaluation.php | 9 + app/Models/EvaluationTemplate.php | 12 + app/Models/EvaluationType.php | 2 + app/Models/FeeStructure.php | 6 + app/Models/Grade.php | 6 + app/Models/GradingConfig.php | 9 +- app/Models/Guardian.php | 6 +- app/Models/Installment.php | 9 + app/Models/Invoice.php | 9 + app/Models/InvoiceItem.php | 3 + app/Models/Mark.php | 9 + app/Models/NoteReclamation.php | 12 + app/Models/OfficialExam.php | 12 + app/Models/OfficialExamRegistration.php | 6 + app/Models/PayRun.php | 9 + app/Models/Payment.php | 18 + app/Models/Payslip.php | 6 + app/Models/Receipt.php | 6 + app/Models/ReportCard.php | 12 + app/Models/SalaryGrade.php | 3 + app/Models/Scholarship.php | 2 + app/Models/School.php | 17 +- app/Models/Student.php | 25 +- app/Models/StudentDocument.php | 6 + app/Models/StudentInformation.php | 3 + app/Models/StudentMedicalInfo.php | 3 + app/Models/StudentParent.php | 3 + app/Models/StudentScholarship.php | 6 + app/Models/Subject.php | 14 +- app/Models/SubjectAssignment.php | 8 + app/Models/TimetableSlot.php | 9 + app/Models/User.php | 12 +- phpstan-baseline.neon | 1088 ++--------------------- 55 files changed, 493 insertions(+), 1033 deletions(-) diff --git a/app/Models/AbsencePermission.php b/app/Models/AbsencePermission.php index b11fd806..ecd60384 100644 --- a/app/Models/AbsencePermission.php +++ b/app/Models/AbsencePermission.php @@ -32,21 +32,33 @@ class AbsencePermission extends Model 'reviewed_at' => 'datetime', ]; + /** + * @return BelongsTo + */ public function student(): BelongsTo { return $this->belongsTo(Student::class); } + /** + * @return BelongsTo + */ public function requestedBy(): BelongsTo { return $this->belongsTo(User::class, 'requested_by'); } + /** + * @return BelongsTo + */ public function reviewedBy(): BelongsTo { return $this->belongsTo(User::class, 'reviewed_by'); } + /** + * @return HasMany + */ public function attendanceRecords(): HasMany { return $this->hasMany(AttendanceRecord::class, 'permission_id'); diff --git a/app/Models/AcademicPeriod.php b/app/Models/AcademicPeriod.php index 12bd7037..77d7674d 100644 --- a/app/Models/AcademicPeriod.php +++ b/app/Models/AcademicPeriod.php @@ -49,6 +49,8 @@ class AcademicPeriod extends Model /** * Get the academic year that owns the period. + * + * @return BelongsTo */ public function academicYear(): BelongsTo { @@ -57,6 +59,8 @@ public function academicYear(): BelongsTo /** * Get the class type this period is scoped to (null = toutes les classes). + * + * @return BelongsTo */ public function classType(): BelongsTo { @@ -65,6 +69,8 @@ public function classType(): BelongsTo /** * Get evaluations attached to this academic period. + * + * @return HasMany */ public function evaluations(): HasMany { diff --git a/app/Models/AcademicYear.php b/app/Models/AcademicYear.php index dc68093f..a1877c35 100644 --- a/app/Models/AcademicYear.php +++ b/app/Models/AcademicYear.php @@ -48,6 +48,8 @@ public function getNameAttribute(): string /** * Get all student scholarships for this academic year. + * + * @return HasMany */ public function studentScholarships(): HasMany { diff --git a/app/Models/AccountingTransaction.php b/app/Models/AccountingTransaction.php index 3d7b9e47..1980a69e 100644 --- a/app/Models/AccountingTransaction.php +++ b/app/Models/AccountingTransaction.php @@ -34,11 +34,17 @@ class AccountingTransaction extends Model /* Relations */ /* ------------------------------------------------------------------ */ + /** + * @return BelongsTo + */ public function cashAccount(): BelongsTo { return $this->belongsTo(CashAccount::class); } + /** + * @return BelongsTo + */ public function createdBy(): BelongsTo { return $this->belongsTo(User::class, 'created_by'); diff --git a/app/Models/ArchivedDocument.php b/app/Models/ArchivedDocument.php index 87a079de..c368913b 100644 --- a/app/Models/ArchivedDocument.php +++ b/app/Models/ArchivedDocument.php @@ -37,11 +37,17 @@ class ArchivedDocument extends Model 'autre' => 'Autre', ]; + /** + * @return BelongsToMany + */ public function tags(): BelongsToMany { return $this->belongsToMany(DocumentTag::class, 'archived_document_tag'); } + /** + * @return BelongsTo + */ public function archivedBy(): BelongsTo { return $this->belongsTo(User::class, 'archived_by'); diff --git a/app/Models/Attendance.php b/app/Models/Attendance.php index 6c1e744f..f57a5ac8 100644 --- a/app/Models/Attendance.php +++ b/app/Models/Attendance.php @@ -25,21 +25,33 @@ class Attendance extends Model 'date' => 'date', ]; + /** + * @return BelongsTo + */ public function classroom(): BelongsTo { return $this->belongsTo(Classroom::class, 'class_id'); } + /** + * @return BelongsTo + */ public function academicPeriod(): BelongsTo { return $this->belongsTo(AcademicPeriod::class); } + /** + * @return BelongsTo + */ public function recordedBy(): BelongsTo { return $this->belongsTo(User::class, 'recorded_by'); } + /** + * @return HasMany + */ public function records(): HasMany { return $this->hasMany(AttendanceRecord::class); diff --git a/app/Models/AttendanceRecord.php b/app/Models/AttendanceRecord.php index 6b9a9980..c45ba1ea 100644 --- a/app/Models/AttendanceRecord.php +++ b/app/Models/AttendanceRecord.php @@ -24,16 +24,25 @@ class AttendanceRecord extends Model 'minutes_late' => 'integer', ]; + /** + * @return BelongsTo + */ public function attendance(): BelongsTo { return $this->belongsTo(Attendance::class); } + /** + * @return BelongsTo + */ public function student(): BelongsTo { return $this->belongsTo(Student::class); } + /** + * @return BelongsTo + */ public function permission(): BelongsTo { return $this->belongsTo(AbsencePermission::class, 'permission_id'); diff --git a/app/Models/AuditLog.php b/app/Models/AuditLog.php index 23d40591..73c7fceb 100644 --- a/app/Models/AuditLog.php +++ b/app/Models/AuditLog.php @@ -31,6 +31,9 @@ class AuditLog extends Model 'new_values' => 'array', ]; + /** + * @return BelongsTo + */ public function user(): BelongsTo { return $this->belongsTo(User::class); diff --git a/app/Models/Backup.php b/app/Models/Backup.php index 213021a1..caae059f 100644 --- a/app/Models/Backup.php +++ b/app/Models/Backup.php @@ -22,11 +22,17 @@ class Backup extends Model 'includes_media' => 'boolean', ]; + /** + * @return BelongsTo + */ public function createdBy(): BelongsTo { return $this->belongsTo(User::class, 'created_by'); } + /** + * @return BelongsTo + */ public function academicYear(): BelongsTo { return $this->belongsTo(AcademicYear::class, 'academic_year_id'); diff --git a/app/Models/BulletinTemplate.php b/app/Models/BulletinTemplate.php index 5d3fc6d8..62db6de7 100644 --- a/app/Models/BulletinTemplate.php +++ b/app/Models/BulletinTemplate.php @@ -45,11 +45,17 @@ class BulletinTemplate extends Model 'moyenne' => 'Moyenne de période', ]; + /** + * @return BelongsTo + */ public function school(): BelongsTo { return $this->belongsTo(School::class); } + /** + * @return BelongsTo + */ public function classroomType(): BelongsTo { return $this->belongsTo(ClassroomType::class, 'classroom_type_id'); diff --git a/app/Models/CalendarEvent.php b/app/Models/CalendarEvent.php index bb562af6..9c5dd8e7 100644 --- a/app/Models/CalendarEvent.php +++ b/app/Models/CalendarEvent.php @@ -40,11 +40,17 @@ class CalendarEvent extends Model 'other' => 'Autre', ]; + /** + * @return BelongsTo + */ public function academicYear(): BelongsTo { return $this->belongsTo(AcademicYear::class); } + /** + * @return BelongsTo + */ public function createdBy(): BelongsTo { return $this->belongsTo(User::class, 'created_by'); diff --git a/app/Models/CashAccount.php b/app/Models/CashAccount.php index 757c4e32..a5e71d32 100644 --- a/app/Models/CashAccount.php +++ b/app/Models/CashAccount.php @@ -28,11 +28,17 @@ class CashAccount extends Model /* Relations */ /* ------------------------------------------------------------------ */ + /** + * @return HasMany + */ public function transactions(): HasMany { return $this->hasMany(AccountingTransaction::class); } + /** + * @return HasMany + */ public function payments(): HasMany { return $this->hasMany(Payment::class); diff --git a/app/Models/ClassSubject.php b/app/Models/ClassSubject.php index fd34eeb1..c5ad7dc3 100644 --- a/app/Models/ClassSubject.php +++ b/app/Models/ClassSubject.php @@ -46,6 +46,8 @@ class ClassSubject extends Model /** * Get the class this assignment belongs to. + * + * @return BelongsTo */ public function class(): BelongsTo { @@ -54,6 +56,8 @@ public function class(): BelongsTo /** * Get the subject for this assignment. + * + * @return BelongsTo */ public function subject(): BelongsTo { @@ -62,6 +66,8 @@ public function subject(): BelongsTo /** * Get the academic year for this class-subject assignment. + * + * @return BelongsTo */ public function academicYear(): BelongsTo { @@ -70,6 +76,8 @@ public function academicYear(): BelongsTo /** * Get all grades for this class-subject combination. + * + * @return HasMany */ public function grades(): HasMany { @@ -78,6 +86,8 @@ public function grades(): HasMany /** * Get all evaluations for this class-subject combination. + * + * @return HasMany */ public function evaluations(): HasMany { diff --git a/app/Models/Classroom.php b/app/Models/Classroom.php index 614d9efe..1b26d65c 100644 --- a/app/Models/Classroom.php +++ b/app/Models/Classroom.php @@ -45,6 +45,8 @@ class Classroom extends Model /** * Get the classroom type for this classroom. + * + * @return BelongsTo */ public function type(): BelongsTo { @@ -61,6 +63,8 @@ public function classroomType(): BelongsTo /** * Get all attendances for this class. + * + * @return HasMany */ public function attendances(): HasMany { @@ -69,6 +73,8 @@ public function attendances(): HasMany /** * Get all class subjects for this class. + * + * @return HasMany */ public function classSubjects(): HasMany { @@ -77,6 +83,8 @@ public function classSubjects(): HasMany /** * Get all evaluations for this class. + * + * @return HasMany */ public function evaluations(): HasMany { diff --git a/app/Models/ClassroomType.php b/app/Models/ClassroomType.php index efc89cd5..0a8c4ab1 100644 --- a/app/Models/ClassroomType.php +++ b/app/Models/ClassroomType.php @@ -55,13 +55,19 @@ public function periodsCount(): int /** * Get all classrooms with this type. + * + * @return HasMany */ public function classrooms(): HasMany { return $this->hasMany(Classroom::class, 'classroom_type_id'); } - /** Écoles qui proposent ce type de classe. */ + /** + * Écoles qui proposent ce type de classe. + * + * @return BelongsToMany + */ public function schools(): BelongsToMany { return $this->belongsToMany(School::class, 'class_type_school'); diff --git a/app/Models/DocumentHeader.php b/app/Models/DocumentHeader.php index 53c56c01..7434e8d1 100644 --- a/app/Models/DocumentHeader.php +++ b/app/Models/DocumentHeader.php @@ -32,6 +32,9 @@ class DocumentHeader extends Model /** Largeur de référence du canevas = largeur utile d'une page A4 portrait (px @96dpi). */ public const CANVAS_WIDTH = 760; + /** + * @return BelongsTo + */ public function school(): BelongsTo { return $this->belongsTo(School::class); diff --git a/app/Models/DocumentIssuance.php b/app/Models/DocumentIssuance.php index 2393a774..8fb86115 100644 --- a/app/Models/DocumentIssuance.php +++ b/app/Models/DocumentIssuance.php @@ -24,16 +24,25 @@ class DocumentIssuance extends Model 'issued_at' => 'datetime', ]; + /** + * @return BelongsTo + */ public function template(): BelongsTo { return $this->belongsTo(DocumentTemplate::class, 'template_id'); } + /** + * @return BelongsTo + */ public function student(): BelongsTo { return $this->belongsTo(Student::class); } + /** + * @return BelongsTo + */ public function issuedBy(): BelongsTo { return $this->belongsTo(User::class, 'issued_by'); diff --git a/app/Models/DocumentTag.php b/app/Models/DocumentTag.php index 1efa59b8..67cbdd0e 100644 --- a/app/Models/DocumentTag.php +++ b/app/Models/DocumentTag.php @@ -13,6 +13,9 @@ class DocumentTag extends Model protected $fillable = ['name', 'slug', 'color']; + /** + * @return BelongsToMany + */ public function documents(): BelongsToMany { return $this->belongsToMany(ArchivedDocument::class, 'archived_document_tag'); diff --git a/app/Models/DocumentTemplate.php b/app/Models/DocumentTemplate.php index fa5e21b9..a0854cda 100644 --- a/app/Models/DocumentTemplate.php +++ b/app/Models/DocumentTemplate.php @@ -82,11 +82,17 @@ class DocumentTemplate extends Model ], ]; + /** + * @return BelongsTo + */ public function school(): BelongsTo { return $this->belongsTo(School::class); } + /** + * @return HasMany + */ public function issuances(): HasMany { return $this->hasMany(DocumentIssuance::class, 'template_id'); diff --git a/app/Models/EmployeeAllowance.php b/app/Models/EmployeeAllowance.php index 9f9f9d77..15fbffc4 100644 --- a/app/Models/EmployeeAllowance.php +++ b/app/Models/EmployeeAllowance.php @@ -36,11 +36,17 @@ class EmployeeAllowance extends Model 'active' => 'boolean', ]; + /** + * @return BelongsTo + */ public function employeeProfile(): BelongsTo { return $this->belongsTo(EmployeeProfile::class); } + /** + * @return BelongsTo + */ public function createdBy(): BelongsTo { return $this->belongsTo(User::class, 'created_by'); diff --git a/app/Models/EmployeeProfile.php b/app/Models/EmployeeProfile.php index 3fc50111..78626ce1 100644 --- a/app/Models/EmployeeProfile.php +++ b/app/Models/EmployeeProfile.php @@ -37,22 +37,35 @@ class EmployeeProfile extends Model 'end_date' => 'date', ]; + /** + * @return BelongsTo + */ public function user(): BelongsTo { return $this->belongsTo(User::class); } + /** + * @return HasMany + */ public function payslips(): HasMany { return $this->hasMany(Payslip::class); } + /** + * @return BelongsTo + */ public function salaryGrade(): BelongsTo { return $this->belongsTo(SalaryGrade::class); } - /** Primes / retenues récurrentes propres à l'employé (tracées). */ + /** + * Primes / retenues récurrentes propres à l'employé (tracées). + * + * @return HasMany + */ public function allowances(): HasMany { return $this->hasMany(EmployeeAllowance::class); diff --git a/app/Models/Enrollment.php b/app/Models/Enrollment.php index ebff4edf..e1450af4 100644 --- a/app/Models/Enrollment.php +++ b/app/Models/Enrollment.php @@ -79,31 +79,49 @@ public function scopeActive(Builder $query): Builder /** Statuts considérés comme « scolarité active » (présents à l'appel). */ public const ACTIVE_ACADEMIC_STATUSES = ['en_cours', 'valide', 'non_valide']; + /** + * @return BelongsTo + */ public function school(): BelongsTo { return $this->belongsTo(School::class); } + /** + * @return BelongsTo + */ public function student(): BelongsTo { return $this->belongsTo(Student::class); } + /** + * @return BelongsTo + */ public function classroom(): BelongsTo { return $this->belongsTo(Classroom::class, 'class_id'); } + /** + * @return BelongsTo + */ public function academicYear(): BelongsTo { return $this->belongsTo(AcademicYear::class, 'academic_year_id'); } + /** + * @return BelongsTo + */ public function enrolledBy(): BelongsTo { return $this->belongsTo(User::class, 'enrolled_by'); } + /** + * @return HasOne + */ public function invoice(): HasOne { return $this->hasOne(Invoice::class); diff --git a/app/Models/Evaluation.php b/app/Models/Evaluation.php index 5e16b4c3..173e6fe1 100644 --- a/app/Models/Evaluation.php +++ b/app/Models/Evaluation.php @@ -32,16 +32,25 @@ class Evaluation extends Model 'locked_at' => 'datetime', ]; + /** + * @return BelongsTo + */ public function template(): BelongsTo { return $this->belongsTo(EvaluationTemplate::class, 'evaluation_template_id'); } + /** + * @return BelongsTo + */ public function classSubject(): BelongsTo { return $this->belongsTo(ClassSubject::class); } + /** + * @return HasMany + */ public function marks(): HasMany { return $this->hasMany(Mark::class); diff --git a/app/Models/EvaluationTemplate.php b/app/Models/EvaluationTemplate.php index 68bdd951..c8a1209a 100644 --- a/app/Models/EvaluationTemplate.php +++ b/app/Models/EvaluationTemplate.php @@ -34,21 +34,33 @@ class EvaluationTemplate extends Model 'date' => 'date', ]; + /** + * @return BelongsTo + */ public function academicPeriod(): BelongsTo { return $this->belongsTo(AcademicPeriod::class); } + /** + * @return BelongsTo + */ public function classType(): BelongsTo { return $this->belongsTo(ClassroomType::class, 'class_type_id'); } + /** + * @return BelongsTo + */ public function evaluationType(): BelongsTo { return $this->belongsTo(EvaluationType::class); } + /** + * @return HasMany + */ public function evaluations(): HasMany { return $this->hasMany(Evaluation::class); diff --git a/app/Models/EvaluationType.php b/app/Models/EvaluationType.php index 1879788c..29babdeb 100644 --- a/app/Models/EvaluationType.php +++ b/app/Models/EvaluationType.php @@ -39,6 +39,8 @@ class EvaluationType extends Model /** * Get all evaluations for this type. + * + * @return HasMany */ public function evaluations(): HasMany { diff --git a/app/Models/FeeStructure.php b/app/Models/FeeStructure.php index 9e6825d1..50802659 100644 --- a/app/Models/FeeStructure.php +++ b/app/Models/FeeStructure.php @@ -38,6 +38,8 @@ class FeeStructure extends Model /** * Get the academic year that owns the fee structure. + * + * @return BelongsTo */ public function academicYear(): BelongsTo { @@ -46,6 +48,8 @@ public function academicYear(): BelongsTo /** * Get the fee category that owns the fee structure. + * + * @return BelongsTo */ public function feeCategory(): BelongsTo { @@ -54,6 +58,8 @@ public function feeCategory(): BelongsTo /** * Get the classroom that owns the fee structure. + * + * @return BelongsTo */ public function classroom(): BelongsTo { diff --git a/app/Models/Grade.php b/app/Models/Grade.php index 495d88bd..89ddc7e4 100644 --- a/app/Models/Grade.php +++ b/app/Models/Grade.php @@ -41,6 +41,8 @@ class Grade extends Model /** * Get the student for this grade. + * + * @return BelongsTo */ public function student(): BelongsTo { @@ -49,6 +51,8 @@ public function student(): BelongsTo /** * Get the class subject for this grade. + * + * @return BelongsTo */ public function classSubject(): BelongsTo { @@ -57,6 +61,8 @@ public function classSubject(): BelongsTo /** * Get the academic period for this grade. + * + * @return BelongsTo */ public function academicPeriod(): BelongsTo { diff --git a/app/Models/GradingConfig.php b/app/Models/GradingConfig.php index 6e932ba7..36b22091 100644 --- a/app/Models/GradingConfig.php +++ b/app/Models/GradingConfig.php @@ -33,12 +33,19 @@ class GradingConfig extends Model 'mentions' => 'array', ]; + /** + * @return BelongsTo + */ public function school(): BelongsTo { return $this->belongsTo(School::class); } - /** Type de classe ciblé (null = configuration par défaut de l'école). */ + /** + * Type de classe ciblé (null = configuration par défaut de l'école). + * + * @return BelongsTo + */ public function classroomType(): BelongsTo { return $this->belongsTo(ClassroomType::class, 'classroom_type_id'); diff --git a/app/Models/Guardian.php b/app/Models/Guardian.php index b68a76a2..c8d8d883 100644 --- a/app/Models/Guardian.php +++ b/app/Models/Guardian.php @@ -34,7 +34,11 @@ class Guardian extends Authenticatable 'password' => 'hashed', ]; - /** Enfants liés à ce tuteur. */ + /** + * Enfants liés à ce tuteur. + * + * @return BelongsToMany + */ public function children(): BelongsToMany { return $this->belongsToMany(Student::class, 'guardian_student')->withPivot('relationship'); diff --git a/app/Models/Installment.php b/app/Models/Installment.php index 1aca32fb..096864a2 100644 --- a/app/Models/Installment.php +++ b/app/Models/Installment.php @@ -31,16 +31,25 @@ class Installment extends Model /* Relations */ /* ------------------------------------------------------------------ */ + /** + * @return BelongsTo + */ public function feeStructure(): BelongsTo { return $this->belongsTo(FeeStructure::class); } + /** + * @return BelongsTo + */ public function academicPeriod(): BelongsTo { return $this->belongsTo(AcademicPeriod::class); } + /** + * @return HasMany + */ public function payments(): HasMany { return $this->hasMany(Payment::class); diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php index 6d557b00..4d04c238 100644 --- a/app/Models/Invoice.php +++ b/app/Models/Invoice.php @@ -40,16 +40,25 @@ class Invoice extends Model /* Relations */ /* ------------------------------------------------------------------ */ + /** + * @return BelongsTo + */ public function enrollment(): BelongsTo { return $this->belongsTo(Enrollment::class); } + /** + * @return HasMany + */ public function items(): HasMany { return $this->hasMany(InvoiceItem::class)->orderBy('sort_order'); } + /** + * @return HasMany + */ public function payments(): HasMany { return $this->hasMany(Payment::class)->latest('paid_at'); diff --git a/app/Models/InvoiceItem.php b/app/Models/InvoiceItem.php index 8ae749fd..2a996004 100644 --- a/app/Models/InvoiceItem.php +++ b/app/Models/InvoiceItem.php @@ -24,6 +24,9 @@ class InvoiceItem extends Model 'sort_order' => 'integer', ]; + /** + * @return BelongsTo + */ public function invoice(): BelongsTo { return $this->belongsTo(Invoice::class); diff --git a/app/Models/Mark.php b/app/Models/Mark.php index ee3259f4..09e56158 100644 --- a/app/Models/Mark.php +++ b/app/Models/Mark.php @@ -30,16 +30,25 @@ class Mark extends Model 'absent' => 'boolean', ]; + /** + * @return BelongsTo + */ public function evaluation(): BelongsTo { return $this->belongsTo(Evaluation::class); } + /** + * @return BelongsTo + */ public function student(): BelongsTo { return $this->belongsTo(Student::class); } + /** + * @return BelongsTo + */ public function createdBy(): BelongsTo { return $this->belongsTo(User::class, 'created_by'); diff --git a/app/Models/NoteReclamation.php b/app/Models/NoteReclamation.php index b40e3ca2..426d7f84 100644 --- a/app/Models/NoteReclamation.php +++ b/app/Models/NoteReclamation.php @@ -31,21 +31,33 @@ class NoteReclamation extends Model 'reviewed_at' => 'datetime', ]; + /** + * @return BelongsTo + */ public function evaluation(): BelongsTo { return $this->belongsTo(Evaluation::class); } + /** + * @return BelongsTo + */ public function student(): BelongsTo { return $this->belongsTo(Student::class); } + /** + * @return BelongsTo + */ public function requestedBy(): BelongsTo { return $this->belongsTo(User::class, 'requested_by'); } + /** + * @return BelongsTo + */ public function reviewedBy(): BelongsTo { return $this->belongsTo(User::class, 'reviewed_by'); diff --git a/app/Models/OfficialExam.php b/app/Models/OfficialExam.php index 48f6d061..9e57478c 100644 --- a/app/Models/OfficialExam.php +++ b/app/Models/OfficialExam.php @@ -37,21 +37,33 @@ class OfficialExam extends Model 'termine' => 'Terminé', ]; + /** + * @return BelongsTo + */ public function school(): BelongsTo { return $this->belongsTo(School::class); } + /** + * @return BelongsTo + */ public function academicYear(): BelongsTo { return $this->belongsTo(AcademicYear::class); } + /** + * @return BelongsTo + */ public function classroom(): BelongsTo { return $this->belongsTo(Classroom::class, 'class_id'); } + /** + * @return HasMany + */ public function registrations(): HasMany { return $this->hasMany(OfficialExamRegistration::class); diff --git a/app/Models/OfficialExamRegistration.php b/app/Models/OfficialExamRegistration.php index 6d548198..e3dd3814 100644 --- a/app/Models/OfficialExamRegistration.php +++ b/app/Models/OfficialExamRegistration.php @@ -32,11 +32,17 @@ class OfficialExamRegistration extends Model 'tres_bien' => 'Très bien', ]; + /** + * @return BelongsTo + */ public function officialExam(): BelongsTo { return $this->belongsTo(OfficialExam::class); } + /** + * @return BelongsTo + */ public function student(): BelongsTo { return $this->belongsTo(Student::class); diff --git a/app/Models/PayRun.php b/app/Models/PayRun.php index ea61a65a..76908247 100644 --- a/app/Models/PayRun.php +++ b/app/Models/PayRun.php @@ -46,16 +46,25 @@ class PayRun extends Model 'paid_at' => 'datetime', ]; + /** + * @return HasMany + */ public function payslips(): HasMany { return $this->hasMany(Payslip::class); } + /** + * @return BelongsTo + */ public function cashAccount(): BelongsTo { return $this->belongsTo(CashAccount::class); } + /** + * @return BelongsTo + */ public function createdBy(): BelongsTo { return $this->belongsTo(User::class, 'created_by'); diff --git a/app/Models/Payment.php b/app/Models/Payment.php index 077c25e8..13784785 100644 --- a/app/Models/Payment.php +++ b/app/Models/Payment.php @@ -34,31 +34,49 @@ class Payment extends Model /* Relations */ /* ------------------------------------------------------------------ */ + /** + * @return BelongsTo + */ public function invoice(): BelongsTo { return $this->belongsTo(Invoice::class); } + /** + * @return BelongsTo + */ public function installment(): BelongsTo { return $this->belongsTo(Installment::class); } + /** + * @return HasOne + */ public function receipt(): HasOne { return $this->hasOne(Receipt::class); } + /** + * @return BelongsTo + */ public function createdBy(): BelongsTo { return $this->belongsTo(User::class, 'created_by'); } + /** + * @return BelongsTo + */ public function cashAccount(): BelongsTo { return $this->belongsTo(CashAccount::class); } + /** + * @return HasOne + */ public function accountingTransaction(): HasOne { return $this->hasOne(AccountingTransaction::class, 'reference_id'); diff --git a/app/Models/Payslip.php b/app/Models/Payslip.php index cc216f88..a21f24e6 100644 --- a/app/Models/Payslip.php +++ b/app/Models/Payslip.php @@ -29,11 +29,17 @@ class Payslip extends Model 'payload' => 'array', ]; + /** + * @return BelongsTo + */ public function payRun(): BelongsTo { return $this->belongsTo(PayRun::class); } + /** + * @return BelongsTo + */ public function employeeProfile(): BelongsTo { return $this->belongsTo(EmployeeProfile::class); diff --git a/app/Models/Receipt.php b/app/Models/Receipt.php index 5050f738..a6094a71 100644 --- a/app/Models/Receipt.php +++ b/app/Models/Receipt.php @@ -23,11 +23,17 @@ class Receipt extends Model 'printed_at' => 'datetime', ]; + /** + * @return BelongsTo + */ public function payment(): BelongsTo { return $this->belongsTo(Payment::class); } + /** + * @return BelongsTo + */ public function printedBy(): BelongsTo { return $this->belongsTo(User::class, 'printed_by'); diff --git a/app/Models/ReportCard.php b/app/Models/ReportCard.php index f5d4cc31..1eb8daf1 100644 --- a/app/Models/ReportCard.php +++ b/app/Models/ReportCard.php @@ -32,21 +32,33 @@ class ReportCard extends Model 'locked_at' => 'datetime', ]; + /** + * @return BelongsTo + */ public function student(): BelongsTo { return $this->belongsTo(Student::class); } + /** + * @return BelongsTo + */ public function academicPeriod(): BelongsTo { return $this->belongsTo(AcademicPeriod::class); } + /** + * @return BelongsTo + */ public function classroom(): BelongsTo { return $this->belongsTo(Classroom::class, 'class_id'); } + /** + * @return BelongsTo + */ public function generatedBy(): BelongsTo { return $this->belongsTo(User::class, 'generated_by'); diff --git a/app/Models/SalaryGrade.php b/app/Models/SalaryGrade.php index 0337d8af..12b5a680 100644 --- a/app/Models/SalaryGrade.php +++ b/app/Models/SalaryGrade.php @@ -27,6 +27,9 @@ class SalaryGrade extends Model 'sort_order' => 'integer', ]; + /** + * @return HasMany + */ public function employeeProfiles(): HasMany { return $this->hasMany(EmployeeProfile::class); diff --git a/app/Models/Scholarship.php b/app/Models/Scholarship.php index 86adea61..692e34b2 100644 --- a/app/Models/Scholarship.php +++ b/app/Models/Scholarship.php @@ -38,6 +38,8 @@ class Scholarship extends Model /** * Get all student scholarships for this scholarship. + * + * @return HasMany */ public function studentScholarships(): HasMany { diff --git a/app/Models/School.php b/app/Models/School.php index f410b075..39a88fad 100644 --- a/app/Models/School.php +++ b/app/Models/School.php @@ -75,24 +75,37 @@ public function getLogoUrlAttribute(): ?string /** * Get all subjects for the school. + * + * @return HasMany */ public function subjects(): HasMany { return $this->hasMany(Subject::class); } + /** + * @return HasMany + */ public function gradingConfigs(): HasMany { return $this->hasMany(GradingConfig::class); } - /** En-tête personnalisé des documents (certificats, attestations, bulletins). */ + /** + * En-tête personnalisé des documents (certificats, attestations, bulletins). + * + * @return HasOne + */ public function documentHeader(): HasOne { return $this->hasOne(DocumentHeader::class); } - /** Types de classe proposés par l'école. */ + /** + * Types de classe proposés par l'école. + * + * @return BelongsToMany + */ public function classTypes(): BelongsToMany { return $this->belongsToMany(ClassroomType::class, 'class_type_school'); diff --git a/app/Models/Student.php b/app/Models/Student.php index 5a198be0..eae22c0e 100644 --- a/app/Models/Student.php +++ b/app/Models/Student.php @@ -68,13 +68,18 @@ class Student extends Model implements AuthenticatableContract 'reset_token', ]; - /** Tuteurs liés à cet élève. */ + /** Tuteurs liés à cet élève. * @return BelongsToMany + */ public function guardians(): BelongsToMany { return $this->belongsToMany(Guardian::class, 'guardian_student')->withPivot('relationship'); } - /** Inscriptions de l'élève. */ + /** + * Inscriptions de l'élève. + * + * @return HasMany + */ public function enrollments(): HasMany { return $this->hasMany(Enrollment::class); @@ -82,6 +87,8 @@ public function enrollments(): HasMany /** * Get the user account for this student. + * + * @return BelongsTo */ public function user(): BelongsTo { @@ -90,6 +97,8 @@ public function user(): BelongsTo /** * Get all attendances for this student. + * + * @return HasMany */ public function attendances(): HasMany { @@ -98,6 +107,8 @@ public function attendances(): HasMany /** * Get all grades for this student. + * + * @return HasMany */ public function grades(): HasMany { @@ -106,6 +117,8 @@ public function grades(): HasMany /** * Informations administratives de l'élève. + * + * @return HasOne */ public function information(): HasOne { @@ -114,6 +127,8 @@ public function information(): HasOne /** * Informations parentales de l'élève. + * + * @return HasOne */ public function parentInfo(): HasOne { @@ -122,6 +137,8 @@ public function parentInfo(): HasOne /** * Informations médicales de l'élève. + * + * @return HasOne */ public function medicalInfo(): HasOne { @@ -130,6 +147,8 @@ public function medicalInfo(): HasOne /** * Get all scholarships for this student. + * + * @return HasMany */ public function scholarships(): HasMany { @@ -138,6 +157,8 @@ public function scholarships(): HasMany /** * Get all documents (pièces justificatives) for this student. + * + * @return HasMany */ public function documents(): HasMany { diff --git a/app/Models/StudentDocument.php b/app/Models/StudentDocument.php index 49338d37..4bc56554 100644 --- a/app/Models/StudentDocument.php +++ b/app/Models/StudentDocument.php @@ -24,11 +24,17 @@ class StudentDocument extends Model 'size' => 'integer', ]; + /** + * @return BelongsTo + */ public function student(): BelongsTo { return $this->belongsTo(Student::class); } + /** + * @return BelongsTo + */ public function uploadedBy(): BelongsTo { return $this->belongsTo(User::class, 'uploaded_by'); diff --git a/app/Models/StudentInformation.php b/app/Models/StudentInformation.php index 99292eb4..a3574b5e 100644 --- a/app/Models/StudentInformation.php +++ b/app/Models/StudentInformation.php @@ -39,6 +39,9 @@ class StudentInformation extends Model 'birth_certificate_issue_date' => 'date', ]; + /** + * @return BelongsTo + */ public function student(): BelongsTo { return $this->belongsTo(Student::class); diff --git a/app/Models/StudentMedicalInfo.php b/app/Models/StudentMedicalInfo.php index f3fe2363..d86e45ac 100644 --- a/app/Models/StudentMedicalInfo.php +++ b/app/Models/StudentMedicalInfo.php @@ -34,6 +34,9 @@ class StudentMedicalInfo extends Model 'emergency_contact_phone', ]; + /** + * @return BelongsTo + */ public function student(): BelongsTo { return $this->belongsTo(Student::class); diff --git a/app/Models/StudentParent.php b/app/Models/StudentParent.php index da2271d0..09b811ce 100644 --- a/app/Models/StudentParent.php +++ b/app/Models/StudentParent.php @@ -38,6 +38,9 @@ class StudentParent extends Model 'email', ]; + /** + * @return BelongsTo + */ public function student(): BelongsTo { return $this->belongsTo(Student::class); diff --git a/app/Models/StudentScholarship.php b/app/Models/StudentScholarship.php index 20e74a03..78a8e190 100644 --- a/app/Models/StudentScholarship.php +++ b/app/Models/StudentScholarship.php @@ -28,6 +28,8 @@ class StudentScholarship extends Model /** * Get the student that owns the student scholarship. + * + * @return BelongsTo */ public function student(): BelongsTo { @@ -36,6 +38,8 @@ public function student(): BelongsTo /** * Get the scholarship that owns the student scholarship. + * + * @return BelongsTo */ public function scholarship(): BelongsTo { @@ -44,6 +48,8 @@ public function scholarship(): BelongsTo /** * Get the academic year that owns the student scholarship. + * + * @return BelongsTo */ public function academicYear(): BelongsTo { diff --git a/app/Models/Subject.php b/app/Models/Subject.php index 1fdfb5a2..1306ef7c 100644 --- a/app/Models/Subject.php +++ b/app/Models/Subject.php @@ -35,19 +35,29 @@ class Subject extends Model /** * Get all class subjects for this subject. + * + * @return HasMany */ public function classSubjects(): HasMany { return $this->hasMany(ClassSubject::class); } - /** Matière parente (si cette matière est une sous-matière). */ + /** + * Matière parente (si cette matière est une sous-matière). + * + * @return BelongsTo + */ public function parent(): BelongsTo { return $this->belongsTo(self::class, 'parent_id'); } - /** Sous-matières. */ + /** + * Sous-matières. + * + * @return HasMany + */ public function children(): HasMany { return $this->hasMany(self::class, 'parent_id'); diff --git a/app/Models/SubjectAssignment.php b/app/Models/SubjectAssignment.php index 55e2e6fc..179a491e 100644 --- a/app/Models/SubjectAssignment.php +++ b/app/Models/SubjectAssignment.php @@ -40,6 +40,8 @@ class SubjectAssignment extends Model /** * Get the subject for this assignment. + * + * @return BelongsTo */ public function subject(): BelongsTo { @@ -48,6 +50,8 @@ public function subject(): BelongsTo /** * Get the teacher (user) for this assignment. + * + * @return BelongsTo */ public function teacher(): BelongsTo { @@ -56,6 +60,8 @@ public function teacher(): BelongsTo /** * Get the academic year for this assignment. + * + * @return BelongsTo */ public function academicYear(): BelongsTo { @@ -64,6 +70,8 @@ public function academicYear(): BelongsTo /** * Get the class (classroom) for this assignment. + * + * @return BelongsTo */ public function classroom(): BelongsTo { diff --git a/app/Models/TimetableSlot.php b/app/Models/TimetableSlot.php index abb414be..8f9053d4 100644 --- a/app/Models/TimetableSlot.php +++ b/app/Models/TimetableSlot.php @@ -28,16 +28,25 @@ class TimetableSlot extends Model 6 => 'Samedi', ]; + /** + * @return BelongsTo + */ public function classroom(): BelongsTo { return $this->belongsTo(Classroom::class, 'class_id'); } + /** + * @return BelongsTo + */ public function subject(): BelongsTo { return $this->belongsTo(Subject::class); } + /** + * @return BelongsTo + */ public function teacher(): BelongsTo { return $this->belongsTo(User::class, 'teacher_id'); diff --git a/app/Models/User.php b/app/Models/User.php index 747b736a..b3482759 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -112,13 +112,21 @@ public function employeeProfile(): HasOne return $this->hasOne(EmployeeProfile::class); } - /** Affectations matières où l'utilisateur est l'enseignant. */ + /** + * Affectations matières où l'utilisateur est l'enseignant. + * + * @return HasMany + */ public function subjectAssignments(): HasMany { return $this->hasMany(SubjectAssignment::class, 'teacher_id'); } - /** Créneaux d'emploi du temps où l'utilisateur est l'enseignant. */ + /** + * Créneaux d'emploi du temps où l'utilisateur est l'enseignant. + * + * @return HasMany + */ public function timetableSlots(): HasMany { return $this->hasMany(TimetableSlot::class, 'teacher_id'); diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index ceedc4aa..715005a8 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -6,66 +6,12 @@ parameters: count: 2 path: app/Console/Commands/GenerateStudentMatricule.php - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$natricule\.$#' - identifier: property.notFound - count: 2 - path: app/Console/Commands/GenerateStudentMatricule.php - - message: '#^Call to an undefined method App\\Models\\Student\:\:generateRegistrationNumber\(\)\.$#' identifier: method.notFound count: 2 path: app/Console/Commands/GenerateStudentMatricule.php - - - message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Model\:\:generateMatricule\(\)\.$#' - identifier: method.notFound - count: 2 - path: app/Console/Commands/GenerateStudentMatricule.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$firstname\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Administration/AuditLogController.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$lastname\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Administration/AuditLogController.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$firstname\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Administration/GuardianController.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$lastname\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Administration/GuardianController.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$matricule\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Administration/GuardianController.php - - - - message: '#^Parameter \#1 \$callback of method Illuminate\\Database\\Eloquent\\Collection\\:\:map\(\) contains unresolvable type\.$#' - identifier: argument.unresolvableType - count: 1 - path: app/Http/Controllers/Administration/GuardianController.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$name\.$#' - identifier: property.notFound - count: 2 - path: app/Http/Controllers/Administration/SubjectAssignmentController.php - - message: '#^Using nullsafe property access "\?\-\>name" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' identifier: nullsafe.neverNull @@ -126,24 +72,6 @@ parameters: count: 1 path: app/Http/Controllers/Api/V1/ChildrenController.php - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$classroom_type_id\.$#' - identifier: property.notFound - count: 2 - path: app/Http/Controllers/Api/V1/DashboardController.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$name\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Api/V1/DashboardController.php - - - - message: '#^Parameter \#1 \$class of method App\\Services\\GradingService\:\:classRanking\(\) expects App\\Models\\Classroom, Illuminate\\Database\\Eloquent\\Model given\.$#' - identifier: argument.type - count: 1 - path: app/Http/Controllers/Api/V1/DashboardController.php - - message: '#^Using nullsafe method call on non\-nullable type Carbon\\CarbonImmutable\. Use \-\> instead\.$#' identifier: nullsafe.neverNull @@ -151,49 +79,7 @@ parameters: path: app/Http/Controllers/Api/V1/DashboardController.php - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$classroom_type_id\.$#' - identifier: property.notFound - count: 2 - path: app/Http/Controllers/Api/V1/GradeController.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$comments\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Api/V1/GradeController.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$name\.$#' - identifier: property.notFound - count: 2 - path: app/Http/Controllers/Api/V1/GradeController.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$score\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Api/V1/GradeController.php - - - - message: '#^Parameter \#1 \$callback of method Illuminate\\Database\\Eloquent\\Collection\\:\:map\(\) contains unresolvable type\.$#' - identifier: argument.unresolvableType - count: 1 - path: app/Http/Controllers/Api/V1/GradeController.php - - - - message: '#^Parameter \#1 \$class of method App\\Services\\GradingService\:\:classRanking\(\) expects App\\Models\\Classroom, Illuminate\\Database\\Eloquent\\Model given\.$#' - identifier: argument.type - count: 1 - path: app/Http/Controllers/Api/V1/GradeController.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$color\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Archives/ArchiveController.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$id\.$#' + message: '#^Access to an undefined property App\\Models\\User\:\:\$name\.$#' identifier: property.notFound count: 1 path: app/Http/Controllers/Archives/ArchiveController.php @@ -201,7 +87,7 @@ parameters: - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$name\.$#' identifier: property.notFound - count: 4 + count: 2 path: app/Http/Controllers/Archives/ArchiveController.php - @@ -210,12 +96,6 @@ parameters: count: 1 path: app/Http/Controllers/Archives/ArchiveController.php - - - message: '#^Parameter \#1 \$callback of method Illuminate\\Database\\Eloquent\\Collection\\:\:map\(\) contains unresolvable type\.$#' - identifier: argument.unresolvableType - count: 1 - path: app/Http/Controllers/Archives/ArchiveController.php - - message: '#^Parameter \#1 \$callback of method Illuminate\\Pagination\\AbstractPaginator\\:\:through\(\) contains unresolvable type\.$#' identifier: argument.unresolvableType @@ -235,119 +115,11 @@ parameters: path: app/Http/Controllers/Comptabilite/CashAccountController.php - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$amount\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Comptabilite/InvoiceController.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$currency\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Comptabilite/InvoiceController.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$enrollment\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Comptabilite/InvoiceController.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$invoice\.$#' - identifier: property.notFound - count: 4 - path: app/Http/Controllers/Comptabilite/InvoiceController.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$paid_at\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Comptabilite/InvoiceController.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$total\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Comptabilite/InvoiceController.php - - - - message: '#^Parameter \#1 \$invoice of method App\\Services\\InvoiceService\:\:recordPayment\(\) expects App\\Models\\Invoice, Illuminate\\Database\\Eloquent\\Model given\.$#' - identifier: argument.type - count: 1 - path: app/Http/Controllers/Comptabilite/InvoiceController.php - - - - message: '#^Parameter \#1 \$school of method App\\Http\\Controllers\\Comptabilite\\InvoiceController\:\:officialHeader\(\) expects App\\Models\\School\|null, Illuminate\\Database\\Eloquent\\Model\|null given\.$#' - identifier: argument.type + message: '#^Using nullsafe method call on non\-nullable type Carbon\\CarbonImmutable\. Use \-\> instead\.$#' + identifier: nullsafe.neverNull count: 1 path: app/Http/Controllers/Comptabilite/InvoiceController.php - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$amount_paid\.$#' - identifier: property.notFound - count: 2 - path: app/Http/Controllers/Comptabilite/SituationController.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$amount_remaining\.$#' - identifier: property.notFound - count: 2 - path: app/Http/Controllers/Comptabilite/SituationController.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$firstname\.$#' - identifier: property.notFound - count: 2 - path: app/Http/Controllers/Comptabilite/SituationController.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$id\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Comptabilite/SituationController.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$invoice_number\.$#' - identifier: property.notFound - count: 2 - path: app/Http/Controllers/Comptabilite/SituationController.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$lastname\.$#' - identifier: property.notFound - count: 2 - path: app/Http/Controllers/Comptabilite/SituationController.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$matricule\.$#' - identifier: property.notFound - count: 2 - path: app/Http/Controllers/Comptabilite/SituationController.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$name\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Comptabilite/SituationController.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$status\.$#' - identifier: property.notFound - count: 2 - path: app/Http/Controllers/Comptabilite/SituationController.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$total\.$#' - identifier: property.notFound - count: 2 - path: app/Http/Controllers/Comptabilite/SituationController.php - - - - message: '#^Parameter \#1 \$callback of method Illuminate\\Database\\Eloquent\\Collection\\:\:map\(\) contains unresolvable type\.$#' - identifier: argument.unresolvableType - count: 1 - path: app/Http/Controllers/Comptabilite/SituationController.php - - message: '#^Using nullsafe property access "\?\-\>amount_paid" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' identifier: nullsafe.neverNull @@ -364,340 +136,118 @@ parameters: message: '#^Using nullsafe property access "\?\-\>firstname" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' identifier: nullsafe.neverNull count: 1 - path: app/Http/Controllers/Comptabilite/SituationController.php - - - - message: '#^Using nullsafe property access "\?\-\>invoice_number" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' - identifier: nullsafe.neverNull - count: 1 - path: app/Http/Controllers/Comptabilite/SituationController.php - - - - message: '#^Using nullsafe property access "\?\-\>lastname" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' - identifier: nullsafe.neverNull - count: 1 - path: app/Http/Controllers/Comptabilite/SituationController.php - - - - message: '#^Using nullsafe property access "\?\-\>matricule" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' - identifier: nullsafe.neverNull - count: 1 - path: app/Http/Controllers/Comptabilite/SituationController.php - - - - message: '#^Using nullsafe property access "\?\-\>name" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' - identifier: nullsafe.neverNull - count: 1 - path: app/Http/Controllers/Comptabilite/SituationController.php - - - - message: '#^Using nullsafe property access "\?\-\>status" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' - identifier: nullsafe.neverNull - count: 1 - path: app/Http/Controllers/Comptabilite/SituationController.php - - - - message: '#^Using nullsafe property access "\?\-\>total" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' - identifier: nullsafe.neverNull - count: 2 - path: app/Http/Controllers/Comptabilite/SituationController.php - - - - message: '#^Access to an undefined property App\\Models\\ReportCard\:\:\$avg_general\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Dashboard/DashboardController.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$class\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Dashboard/DashboardController.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$code\.$#' - identifier: property.notFound - count: 2 - path: app/Http/Controllers/Dashboard/DashboardController.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$enrollment\.$#' - identifier: property.notFound - count: 2 - path: app/Http/Controllers/Dashboard/DashboardController.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$firstname\.$#' - identifier: property.notFound - count: 2 - path: app/Http/Controllers/Dashboard/DashboardController.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$lastname\.$#' - identifier: property.notFound - count: 2 - path: app/Http/Controllers/Dashboard/DashboardController.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$matricule\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Dashboard/DashboardController.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$name\.$#' - identifier: property.notFound - count: 8 - path: app/Http/Controllers/Dashboard/DashboardController.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$subject\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Dashboard/DashboardController.php - - - - message: '#^Parameter \#1 \$callback of method Illuminate\\Database\\Eloquent\\Collection\\:\:map\(\) contains unresolvable type\.$#' - identifier: argument.unresolvableType - count: 1 - path: app/Http/Controllers/Dashboard/DashboardController.php - - - - message: '#^Parameter \#1 \$callback of method Illuminate\\Database\\Eloquent\\Collection\\:\:map\(\) contains unresolvable type\.$#' - identifier: argument.unresolvableType - count: 1 - path: app/Http/Controllers/Dashboard/DashboardController.php - - - - message: '#^Parameter \#1 \$callback of method Illuminate\\Database\\Eloquent\\Collection\\:\:map\(\) contains unresolvable type\.$#' - identifier: argument.unresolvableType - count: 1 - path: app/Http/Controllers/Dashboard/DashboardController.php - - - - message: '#^Parameter \#1 \$callback of method Illuminate\\Database\\Eloquent\\Collection\\:\:map\(\) contains unresolvable type\.$#' - identifier: argument.unresolvableType - count: 1 - path: app/Http/Controllers/Dashboard/DashboardController.php - - - - message: '#^Parameter \#1 \$callback of method Illuminate\\Database\\Eloquent\\Collection\\:\:map\(\) contains unresolvable type\.$#' - identifier: argument.unresolvableType - count: 1 - path: app/Http/Controllers/Dashboard/DashboardController.php - - - - message: '#^Using nullsafe method call on non\-nullable type Carbon\\CarbonImmutable\. Use \-\> instead\.$#' - identifier: nullsafe.neverNull - count: 4 - path: app/Http/Controllers/Dashboard/DashboardController.php - - - - message: '#^Using nullsafe property access "\?\-\>code" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' - identifier: nullsafe.neverNull - count: 2 - path: app/Http/Controllers/Dashboard/DashboardController.php - - - - message: '#^Using nullsafe property access "\?\-\>name" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' - identifier: nullsafe.neverNull - count: 9 - path: app/Http/Controllers/Dashboard/DashboardController.php - - - - message: '#^Access to an undefined property App\\Models\\Enrollment\:\:\$billed\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Eleves/EnrollmentController.php - - - - message: '#^Access to an undefined property App\\Models\\Enrollment\:\:\$collected\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Eleves/EnrollmentController.php - - - - message: '#^Access to an undefined property App\\Models\\Enrollment\:\:\$remaining\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Eleves/EnrollmentController.php - - - - message: '#^Access to an undefined property App\\Models\\Enrollment\:\:\$unpaid_amount\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Eleves/EnrollmentController.php - - - - message: '#^Access to an undefined property App\\Models\\Enrollment\:\:\$unpaid_count\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Eleves/EnrollmentController.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$amount_paid\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Eleves/EnrollmentController.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$amount_remaining\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Eleves/EnrollmentController.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$status\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Eleves/EnrollmentController.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$total\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Eleves/EnrollmentController.php - - - - message: '#^Parameter \#1 \$school of method App\\Services\\DocumentRenderer\:\:headerHtml\(\) expects App\\Models\\School, Illuminate\\Database\\Eloquent\\Model given\.$#' - identifier: argument.type - count: 1 - path: app/Http/Controllers/Eleves/EnrollmentController.php - - - - message: '#^Parameter \#1 \$school of method App\\Services\\DocumentRenderer\:\:resolveVariables\(\) expects App\\Models\\School, Illuminate\\Database\\Eloquent\\Model given\.$#' - identifier: argument.type - count: 1 - path: app/Http/Controllers/Eleves/EnrollmentController.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$firstname\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Eleves/PromotionController.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$lastname\.$#' - identifier: property.notFound - count: 2 - path: app/Http/Controllers/Eleves/PromotionController.php + path: app/Http/Controllers/Comptabilite/SituationController.php - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$matricule\.$#' - identifier: property.notFound + message: '#^Using nullsafe property access "\?\-\>invoice_number" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' + identifier: nullsafe.neverNull count: 1 - path: app/Http/Controllers/Eleves/PromotionController.php + path: app/Http/Controllers/Comptabilite/SituationController.php - - message: '#^Parameter \#1 \$callback of method Illuminate\\Database\\Eloquent\\Collection\\:\:map\(\) contains unresolvable type\.$#' - identifier: argument.unresolvableType + message: '#^Using nullsafe property access "\?\-\>lastname" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' + identifier: nullsafe.neverNull count: 1 - path: app/Http/Controllers/Eleves/PromotionController.php + path: app/Http/Controllers/Comptabilite/SituationController.php - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$firstname\.$#' - identifier: property.notFound + message: '#^Using nullsafe property access "\?\-\>matricule" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' + identifier: nullsafe.neverNull count: 1 - path: app/Http/Controllers/Eleves/RosterController.php + path: app/Http/Controllers/Comptabilite/SituationController.php - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$gender\.$#' - identifier: property.notFound + message: '#^Using nullsafe property access "\?\-\>name" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' + identifier: nullsafe.neverNull count: 1 - path: app/Http/Controllers/Eleves/RosterController.php + path: app/Http/Controllers/Comptabilite/SituationController.php - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$lastname\.$#' - identifier: property.notFound - count: 3 - path: app/Http/Controllers/Eleves/RosterController.php + message: '#^Using nullsafe property access "\?\-\>status" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' + identifier: nullsafe.neverNull + count: 1 + path: app/Http/Controllers/Comptabilite/SituationController.php - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$matricule\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Eleves/RosterController.php + message: '#^Using nullsafe property access "\?\-\>total" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' + identifier: nullsafe.neverNull + count: 2 + path: app/Http/Controllers/Comptabilite/SituationController.php - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$status\.$#' + message: '#^Access to an undefined property App\\Models\\ReportCard\:\:\$avg_general\.$#' identifier: property.notFound count: 1 - path: app/Http/Controllers/Eleves/RosterController.php + path: app/Http/Controllers/Dashboard/DashboardController.php - - message: '#^Parameter \#1 \$callback of method Illuminate\\Database\\Eloquent\\Collection\\:\:map\(\) contains unresolvable type\.$#' - identifier: argument.unresolvableType + message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$name\.$#' + identifier: property.notFound count: 1 - path: app/Http/Controllers/Eleves/RosterController.php + path: app/Http/Controllers/Dashboard/DashboardController.php - - message: '#^Using nullsafe property access "\?\-\>id" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' + message: '#^Using nullsafe method call on non\-nullable type Carbon\\CarbonImmutable\. Use \-\> instead\.$#' identifier: nullsafe.neverNull - count: 1 - path: app/Http/Controllers/Eleves/RosterController.php + count: 4 + path: app/Http/Controllers/Dashboard/DashboardController.php - - message: '#^Using nullsafe property access "\?\-\>status" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' + message: '#^Using nullsafe property access "\?\-\>code" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' identifier: nullsafe.neverNull - count: 1 - path: app/Http/Controllers/Eleves/RosterController.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$code\.$#' - identifier: property.notFound count: 2 - path: app/Http/Controllers/Eleves/StudentController.php + path: app/Http/Controllers/Dashboard/DashboardController.php - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$created_at\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Eleves/StudentController.php + message: '#^Using nullsafe property access "\?\-\>name" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' + identifier: nullsafe.neverNull + count: 9 + path: app/Http/Controllers/Dashboard/DashboardController.php - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$id\.$#' + message: '#^Access to an undefined property App\\Models\\Enrollment\:\:\$billed\.$#' identifier: property.notFound count: 1 - path: app/Http/Controllers/Eleves/StudentController.php + path: app/Http/Controllers/Eleves/EnrollmentController.php - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$mime\.$#' + message: '#^Access to an undefined property App\\Models\\Enrollment\:\:\$collected\.$#' identifier: property.notFound count: 1 - path: app/Http/Controllers/Eleves/StudentController.php + path: app/Http/Controllers/Eleves/EnrollmentController.php - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$name\.$#' + message: '#^Access to an undefined property App\\Models\\Enrollment\:\:\$remaining\.$#' identifier: property.notFound - count: 6 - path: app/Http/Controllers/Eleves/StudentController.php + count: 1 + path: app/Http/Controllers/Eleves/EnrollmentController.php - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$original_name\.$#' + message: '#^Access to an undefined property App\\Models\\Enrollment\:\:\$unpaid_amount\.$#' identifier: property.notFound count: 1 - path: app/Http/Controllers/Eleves/StudentController.php + path: app/Http/Controllers/Eleves/EnrollmentController.php - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$size\.$#' + message: '#^Access to an undefined property App\\Models\\Enrollment\:\:\$unpaid_count\.$#' identifier: property.notFound count: 1 - path: app/Http/Controllers/Eleves/StudentController.php + path: app/Http/Controllers/Eleves/EnrollmentController.php - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$status\.$#' - identifier: property.notFound + message: '#^Using nullsafe property access "\?\-\>id" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' + identifier: nullsafe.neverNull count: 1 - path: app/Http/Controllers/Eleves/StudentController.php + path: app/Http/Controllers/Eleves/RosterController.php - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$uploadedBy\.$#' - identifier: property.notFound + message: '#^Using nullsafe property access "\?\-\>status" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' + identifier: nullsafe.neverNull count: 1 - path: app/Http/Controllers/Eleves/StudentController.php + path: app/Http/Controllers/Eleves/RosterController.php - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$year\.$#' + message: '#^Access to an undefined property App\\Models\\User\:\:\$name\.$#' identifier: property.notFound count: 2 path: app/Http/Controllers/Eleves/StudentController.php @@ -721,13 +271,7 @@ parameters: path: app/Http/Controllers/Eleves/StudentController.php - - message: '#^Parameter \#1 \$callback of method Illuminate\\Database\\Eloquent\\Collection\\:\:map\(\) contains unresolvable type\.$#' - identifier: argument.unresolvableType - count: 1 - path: app/Http/Controllers/Eleves/StudentController.php - - - - message: '#^Parameter \#1 \$callback of method Illuminate\\Database\\Eloquent\\Collection\\:\:map\(\) contains unresolvable type\.$#' + message: '#^Parameter \#1 \$callback of method Illuminate\\Database\\Eloquent\\Collection\\:\:map\(\) contains unresolvable type\.$#' identifier: argument.unresolvableType count: 1 path: app/Http/Controllers/Eleves/StudentController.php @@ -819,7 +363,7 @@ parameters: - message: '#^Access to an undefined property App\\Models\\User\:\:\$name\.$#' identifier: property.notFound - count: 2 + count: 3 path: app/Http/Controllers/Eleves/TimetableController.php - @@ -828,12 +372,6 @@ parameters: count: 2 path: app/Http/Controllers/Eleves/TimetableController.php - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$name\.$#' - identifier: property.notFound - count: 2 - path: app/Http/Controllers/Eleves/TimetableController.php - - message: '#^Method App\\Http\\Controllers\\Eleves\\TimetableController\:\:buildScheduleGrid\(\) should return array\{timeRanges\: Illuminate\\Support\\Collection\, grid\: array\\>\} but returns array\{timeRanges\: Illuminate\\Support\\Collection\, grid\: array\, App\\Models\\TimetableSlot\>\>\}\.$#' identifier: return.type @@ -858,18 +396,6 @@ parameters: count: 1 path: app/Http/Controllers/Eleves/TimetableController.php - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$class_id\.$#' - identifier: property.notFound - count: 3 - path: app/Http/Controllers/Examens/MarkController.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$max_score\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Examens/MarkController.php - - message: '#^Using nullsafe property access "\?\-\>absent" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' identifier: nullsafe.neverNull @@ -888,72 +414,12 @@ parameters: count: 1 path: app/Http/Controllers/Examens/OfficialExamController.php - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$average\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Examens/OfficialExamController.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$id\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Examens/OfficialExamController.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$mention\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Examens/OfficialExamController.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$name\.$#' - identifier: property.notFound - count: 2 - path: app/Http/Controllers/Examens/OfficialExamController.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$registration_number\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Examens/OfficialExamController.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$serie\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Examens/OfficialExamController.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$status\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Examens/OfficialExamController.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$student\.$#' - identifier: property.notFound - count: 2 - path: app/Http/Controllers/Examens/OfficialExamController.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$student_id\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Examens/OfficialExamController.php - - message: '#^Parameter \#1 \$callback of method Illuminate\\Database\\Eloquent\\Collection\\:\:map\(\) contains unresolvable type\.$#' identifier: argument.unresolvableType count: 1 path: app/Http/Controllers/Examens/OfficialExamController.php - - - message: '#^Parameter \#1 \$callback of method Illuminate\\Database\\Eloquent\\Collection\\:\:map\(\) contains unresolvable type\.$#' - identifier: argument.unresolvableType - count: 1 - path: app/Http/Controllers/Examens/OfficialExamController.php - - message: '#^Using nullsafe property access "\?\-\>id" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' identifier: nullsafe.neverNull @@ -972,42 +438,6 @@ parameters: count: 1 path: app/Http/Controllers/Notes/BulletinController.php - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$classroom_type_id\.$#' - identifier: property.notFound - count: 2 - path: app/Http/Controllers/Notes/GradeController.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$comments\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Notes/GradeController.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$score\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Notes/GradeController.php - - - - message: '#^Parameter \#1 \$callback of method Illuminate\\Database\\Eloquent\\Collection\\:\:map\(\) contains unresolvable type\.$#' - identifier: argument.unresolvableType - count: 1 - path: app/Http/Controllers/Notes/GradeController.php - - - - message: '#^Parameter \#1 \$class of method App\\Services\\GradingService\:\:classRanking\(\) expects App\\Models\\Classroom, Illuminate\\Database\\Eloquent\\Model given\.$#' - identifier: argument.type - count: 1 - path: app/Http/Controllers/Notes/GradeController.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$template\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Notes/NoteReclamationController.php - - message: '#^Method App\\Http\\Controllers\\Notes\\NoteReclamationController\:\:create\(\) should return Inertia\\Response but returns Illuminate\\Http\\RedirectResponse\.$#' identifier: return.type @@ -1021,13 +451,7 @@ parameters: path: app/Http/Controllers/Notes/NoteReclamationController.php - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$reference\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Paie/PayslipController.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$name\.$#' + message: '#^Access to an undefined property App\\Models\\User\:\:\$name\.$#' identifier: property.notFound count: 1 path: app/Http/Controllers/Parametres/BackupController.php @@ -1062,60 +486,6 @@ parameters: count: 1 path: app/Http/Controllers/Parametres/BulletinTemplateController.php - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$academicYear\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Parametres/ClassroomController.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$coefficient\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Parametres/ClassroomController.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$id\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Parametres/ClassroomController.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$subject\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Parametres/ClassroomController.php - - - - message: '#^Parameter \#1 \$callback of method Illuminate\\Database\\Eloquent\\Collection\<\(int\|string\),Illuminate\\Database\\Eloquent\\Collection\\>\:\:map\(\) contains unresolvable type\.$#' - identifier: argument.unresolvableType - count: 1 - path: app/Http/Controllers/Parametres/ClassroomController.php - - - - message: '#^Parameter \#1 \$callback of method Illuminate\\Database\\Eloquent\\Collection\\:\:map\(\) contains unresolvable type\.$#' - identifier: argument.unresolvableType - count: 1 - path: app/Http/Controllers/Parametres/ClassroomController.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$layout\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Parametres/DocumentHeaderController.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$preset\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Parametres/DocumentHeaderController.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$watermark\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Parametres/DocumentHeaderController.php - - message: '#^Parameter \#1 \$boolean of function abort_unless expects bool, App\\Models\\School\|null given\.$#' identifier: argument.type @@ -1129,27 +499,9 @@ parameters: path: app/Http/Controllers/Parametres/DocumentHeaderController.php - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$firstname\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Parametres/DocumentTemplateController.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$lastname\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Parametres/DocumentTemplateController.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$matricule\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Parametres/DocumentTemplateController.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$name\.$#' + message: '#^Access to an undefined property App\\Models\\User\:\:\$name\.$#' identifier: property.notFound - count: 2 + count: 1 path: app/Http/Controllers/Parametres/DocumentTemplateController.php - @@ -1158,12 +510,6 @@ parameters: count: 1 path: app/Http/Controllers/Parametres/DocumentTemplateController.php - - - message: '#^Parameter \#1 \$school of method App\\Services\\DocumentRenderer\:\:resolveVariables\(\) expects App\\Models\\School, Illuminate\\Database\\Eloquent\\Model given\.$#' - identifier: argument.type - count: 2 - path: app/Http/Controllers/Parametres/DocumentTemplateController.php - - message: '#^Using nullsafe method call on non\-nullable type Carbon\\CarbonImmutable\. Use \-\> instead\.$#' identifier: nullsafe.neverNull @@ -1194,30 +540,6 @@ parameters: count: 1 path: app/Http/Controllers/Rh/EmployeeAllowanceController.php - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$id\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Rh/SalaryGradeController.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$name\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Rh/SalaryGradeController.php - - - - message: '#^Parameter \#1 \$callback of method Illuminate\\Database\\Eloquent\\Collection\\:\:map\(\) contains unresolvable type\.$#' - identifier: argument.unresolvableType - count: 1 - path: app/Http/Controllers/Rh/SalaryGradeController.php - - - - message: '#^Parameter \#1 \$callback of method Illuminate\\Support\\Collection\\|string\|null\>\>\:\:sortBy\(\) contains unresolvable type\.$#' - identifier: argument.unresolvableType - count: 1 - path: app/Http/Controllers/Rh/SalaryGradeController.php - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$employee_number\.$#' identifier: property.notFound @@ -1404,36 +726,6 @@ parameters: count: 1 path: app/Models/CashAccount.php - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$marks\.$#' - identifier: property.notFound - count: 1 - path: app/Models/ClassSubject.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$template\.$#' - identifier: property.notFound - count: 1 - path: app/Models/ClassSubject.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$base_amount\.$#' - identifier: property.notFound - count: 1 - path: app/Models/EmployeeProfile.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$firstname\.$#' - identifier: property.notFound - count: 1 - path: app/Models/EmployeeProfile.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$lastname\.$#' - identifier: property.notFound - count: 1 - path: app/Models/EmployeeProfile.php - - message: '#^Using nullsafe property access "\?\-\>base_amount" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' identifier: nullsafe.neverNull @@ -1506,12 +798,6 @@ parameters: count: 3 path: app/Models/ReportCard.php - - - message: '#^Method App\\Models\\School\:\:activeGradingConfig\(\) should return App\\Models\\GradingConfig\|null but returns Illuminate\\Database\\Eloquent\\Model\|null\.$#' - identifier: return.type - count: 1 - path: app/Models/School.php - - message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Model\:\:auditExcluded\(\)\.$#' identifier: method.notFound @@ -1578,18 +864,6 @@ parameters: count: 1 path: app/Observers/UserObserver.php - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$class_id\.$#' - identifier: property.notFound - count: 1 - path: app/Policies/EvaluationPolicy.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$subject_id\.$#' - identifier: property.notFound - count: 1 - path: app/Policies/EvaluationPolicy.php - - message: '#^Result of && is always false\.$#' identifier: booleanAnd.alwaysFalse @@ -1602,30 +876,6 @@ parameters: count: 1 path: app/Rules/UniqueMatricule.php - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$enrollment\.$#' - identifier: property.notFound - count: 2 - path: app/Services/AccountingService.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$firstname\.$#' - identifier: property.notFound - count: 1 - path: app/Services/AccountingService.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$lastname\.$#' - identifier: property.notFound - count: 1 - path: app/Services/AccountingService.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$name\.$#' - identifier: property.notFound - count: 1 - path: app/Services/AccountingService.php - - message: '#^Using nullsafe property access "\?\-\>name" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' identifier: nullsafe.neverNull @@ -1650,48 +900,18 @@ parameters: count: 1 path: app/Services/BackupService.php - - - message: '#^Parameter \#2 \$student of method App\\Services\\DocumentRenderer\:\:resolveVariables\(\) expects App\\Models\\Student\|null, Illuminate\\Database\\Eloquent\\Model\|null given\.$#' - identifier: argument.type - count: 1 - path: app/Services/BulletinRenderer.php - - message: '#^Strict comparison using \!\=\= between mixed and null will always evaluate to true\.$#' identifier: notIdentical.alwaysTrue count: 1 path: app/Services/BulletinRenderer.php - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$documentHeader\.$#' - identifier: property.notFound - count: 1 - path: app/Services/DocumentRenderer.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$watermark\.$#' - identifier: property.notFound - count: 1 - path: app/Services/DocumentRenderer.php - - message: '#^Match arm comparison between ''female'' and ''female'' is always true\.$#' identifier: match.alwaysTrue count: 1 path: app/Services/DocumentRenderer.php - - - message: '#^Parameter \#1 \$school of method App\\Services\\DocumentRenderer\:\:logoDataUri\(\) expects App\\Models\\School\|null, Illuminate\\Database\\Eloquent\\Model\|null given\.$#' - identifier: argument.type - count: 2 - path: app/Services/DocumentRenderer.php - - - - message: '#^Parameter \#1 \$school of method App\\Services\\DocumentRenderer\:\:renderWatermark\(\) expects App\\Models\\School\|null, Illuminate\\Database\\Eloquent\\Model\|null given\.$#' - identifier: argument.type - count: 1 - path: app/Services/DocumentRenderer.php - - message: '#^Ternary operator condition is always true\.$#' identifier: ternary.alwaysTrue @@ -1704,66 +924,6 @@ parameters: count: 1 path: app/Services/DocumentRenderer.php - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$absent\.$#' - identifier: property.notFound - count: 1 - path: app/Services/GradingService.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$academic_period_id\.$#' - identifier: property.notFound - count: 1 - path: app/Services/GradingService.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$coefficient\.$#' - identifier: property.notFound - count: 1 - path: app/Services/GradingService.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$evaluationType\.$#' - identifier: property.notFound - count: 1 - path: app/Services/GradingService.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$evaluation_type_id\.$#' - identifier: property.notFound - count: 1 - path: app/Services/GradingService.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$marks\.$#' - identifier: property.notFound - count: 1 - path: app/Services/GradingService.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$max_score\.$#' - identifier: property.notFound - count: 1 - path: app/Services/GradingService.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$score\.$#' - identifier: property.notFound - count: 1 - path: app/Services/GradingService.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$student_id\.$#' - identifier: property.notFound - count: 1 - path: app/Services/GradingService.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$template\.$#' - identifier: property.notFound - count: 2 - path: app/Services/GradingService.php - - message: '#^Parameter \#1 \$rows of method App\\Services\\GradingService\:\:assignRanks\(\) expects Illuminate\\Support\\Collection\, Illuminate\\Support\\Collection\ given\.$#' identifier: argument.type @@ -1771,98 +931,26 @@ parameters: path: app/Services/GradingService.php - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$name\.$#' - identifier: property.notFound - count: 1 - path: app/Services/InvoiceService.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$type\.$#' - identifier: property.notFound - count: 1 - path: app/Services/InvoiceService.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$value\.$#' - identifier: property.notFound - count: 2 - path: app/Services/InvoiceService.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$d\.$#' - identifier: property.notFound - count: 1 - path: app/Services/PayrollService.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$g\.$#' - identifier: property.notFound - count: 1 - path: app/Services/PayrollService.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$id\.$#' - identifier: property.notFound - count: 1 - path: app/Services/PayrollService.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$label\.$#' - identifier: property.notFound - count: 1 - path: app/Services/PayrollService.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$n\.$#' + message: '#^Access to an undefined property App\\Models\\Payslip\:\:\$d\.$#' identifier: property.notFound count: 1 path: app/Services/PayrollService.php - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$reason\.$#' + message: '#^Access to an undefined property App\\Models\\Payslip\:\:\$g\.$#' identifier: property.notFound count: 1 path: app/Services/PayrollService.php - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$status\.$#' + message: '#^Access to an undefined property App\\Models\\Payslip\:\:\$n\.$#' identifier: property.notFound count: 1 path: app/Services/PayrollService.php - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$type\.$#' - identifier: property.notFound - count: 1 - path: app/Services/PayrollService.php - - - - message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Model\:\:appliesTo\(\)\.$#' - identifier: method.notFound - count: 1 - path: app/Services/PayrollService.php - - - - message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Model\:\:computeAmount\(\)\.$#' - identifier: method.notFound - count: 1 - path: app/Services/PayrollService.php - - - - message: '#^Parameter \#1 \$payslip of method App\\Services\\AccountingService\:\:cancelPayrollTransaction\(\) expects App\\Models\\Payslip, Illuminate\\Database\\Eloquent\\Model given\.$#' - identifier: argument.type - count: 2 - path: app/Services/PayrollService.php - - - - message: '#^Parameter \#1 \$payslip of method App\\Services\\AccountingService\:\:recordPayrollTransaction\(\) expects App\\Models\\Payslip, Illuminate\\Database\\Eloquent\\Model given\.$#' - identifier: argument.type - count: 1 - path: app/Services/PayrollService.php - - - - message: '#^Parameter \#1 \$run of method App\\Services\\PayrollService\:\:refreshTotals\(\) expects App\\Models\\PayRun, Illuminate\\Database\\Eloquent\\Model\|null given\.$#' - identifier: argument.type + message: '#^Offset ''amount'' on array\{code\: mixed, label\: mixed, type\: mixed, amount\: float, reason\?\: string\|null, origin\: non\-falsy\-string\} on left side of \?\? always exists and is not nullable\.$#' + identifier: nullCoalesce.offset count: 1 path: app/Services/PayrollService.php @@ -1872,30 +960,6 @@ parameters: count: 1 path: app/Services/PayrollService.php - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$firstname\.$#' - identifier: property.notFound - count: 1 - path: app/Services/ReportCardBuilder.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$lastname\.$#' - identifier: property.notFound - count: 1 - path: app/Services/ReportCardBuilder.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$name\.$#' - identifier: property.notFound - count: 3 - path: app/Services/ReportCardBuilder.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$parent\.$#' - identifier: property.notFound - count: 2 - path: app/Services/ReportCardBuilder.php - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$period_system\.$#' identifier: property.notFound @@ -2010,24 +1074,6 @@ parameters: count: 3 path: database/seeders/DefaultUsersSeeder.php - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$firstname\.$#' - identifier: property.notFound - count: 1 - path: database/seeders/DemoSeeder.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$lastname\.$#' - identifier: property.notFound - count: 1 - path: database/seeders/DemoSeeder.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$name\.$#' - identifier: property.notFound - count: 1 - path: database/seeders/DemoSeeder.php - - message: '#^Using nullsafe method call on non\-nullable type Illuminate\\Console\\Command\. Use \-\> instead\.$#' identifier: nullsafe.neverNull From 5d877d00d3fe6e1e011f29822c3f9b95b16d077d Mon Sep 17 00:00:00 2001 From: horacioskrp Date: Fri, 13 Nov 2026 14:07:48 +0000 Subject: [PATCH 02/12] =?UTF-8?q?fix(command):=20GenerateStudentMatricule?= =?UTF-8?q?=20=E2=80=94=20re-pointe=20le=20code=20mort=20registration=5Fnu?= =?UTF-8?q?mber=20vers=20matricule=20=C3=A9l=C3=A8ve?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Commands/GenerateStudentMatricule.php | 53 ++++++++----------- phpstan-baseline.neon | 12 ----- 2 files changed, 21 insertions(+), 44 deletions(-) diff --git a/app/Console/Commands/GenerateStudentMatricule.php b/app/Console/Commands/GenerateStudentMatricule.php index e8f82061..8ead8a84 100644 --- a/app/Console/Commands/GenerateStudentMatricule.php +++ b/app/Console/Commands/GenerateStudentMatricule.php @@ -3,8 +3,8 @@ namespace App\Console\Commands; use App\Models\Student; -use App\Services\MatriculeService; use Illuminate\Console\Command; +use Illuminate\Database\Eloquent\Collection; class GenerateStudentMatricule extends Command { @@ -13,10 +13,10 @@ class GenerateStudentMatricule extends Command * * @var string */ - protected $signature = 'matricule:generate-student + protected $signature = 'matricule:generate-student {--class-id= : ID de la classe} {--all : Générer pour tous les élèves} - {--registration : Générer aussi les numéros d\'enregistrement} + {--matricule : Générer aussi les matricules des élèves} {--force : Forcer la régénération}'; /** @@ -31,10 +31,9 @@ class GenerateStudentMatricule extends Command */ public function handle(): int { - $matriculeService = app(MatriculeService::class); + $withStudentMatricule = (bool) $this->option('matricule'); if ($this->option('all')) { - // Générer pour tous les élèves $students = Student::all(); if ($students->isEmpty()) { @@ -44,28 +43,7 @@ public function handle(): int } $this->info("Génération des matricules pour {$students->count()} élèves..."); - - $progressBar = $this->output->createProgressBar($students->count()); - $progressBar->start(); - - foreach ($students as $student) { - // Générer matricule de l'utilisateur - if (! $student->user->natricule) { - $student->user->natricule = $student->user->generateMatricule(); - $student->user->save(); - } - - // Générer numéro d'enregistrement - if (! $student->registration_number) { - $student->registration_number = $student->generateRegistrationNumber(); - $student->save(); - } - - $progressBar->advance(); - } - - $progressBar->finish(); - $this->newLine(); + $this->backfillMatricules($students, $withStudentMatricule); $this->info('✅ Matricules générés avec succès!'); return 0; @@ -88,18 +66,32 @@ public function handle(): int } $this->info("Génération pour {$students->count()} élèves..."); + $this->backfillMatricules($students, $withStudentMatricule); + $this->info('✅ Matricules générés avec succès!'); + return 0; + } + + /** + * Génère les matricules manquants pour un lot d'élèves. + * + * @param Collection $students + */ + private function backfillMatricules(Collection $students, bool $withStudentMatricule): void + { $progressBar = $this->output->createProgressBar($students->count()); $progressBar->start(); foreach ($students as $student) { + // Matricule du compte utilisateur associé. if (! $student->user->natricule) { $student->user->natricule = $student->user->generateMatricule(); $student->user->save(); } - if (! $student->registration_number) { - $student->registration_number = $student->generateRegistrationNumber(); + // Matricule de l'élève (optionnel, via --matricule). + if ($withStudentMatricule && empty($student->matricule)) { + $student->matricule = $student->generateMatricule(); $student->save(); } @@ -108,8 +100,5 @@ public function handle(): int $progressBar->finish(); $this->newLine(); - $this->info('✅ Matricules générés avec succès!'); - - return 0; } } diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 715005a8..9ac9dd31 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,17 +1,5 @@ parameters: ignoreErrors: - - - message: '#^Access to an undefined property App\\Models\\Student\:\:\$registration_number\.$#' - identifier: property.notFound - count: 2 - path: app/Console/Commands/GenerateStudentMatricule.php - - - - message: '#^Call to an undefined method App\\Models\\Student\:\:generateRegistrationNumber\(\)\.$#' - identifier: method.notFound - count: 2 - path: app/Console/Commands/GenerateStudentMatricule.php - - message: '#^Using nullsafe property access "\?\-\>name" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' identifier: nullsafe.neverNull From ca52f8385eecfe7a4665c31079ad4f40c52a2f1a Mon Sep 17 00:00:00 2001 From: horacioskrp Date: Fri, 13 Nov 2026 14:14:18 +0000 Subject: [PATCH 03/12] =?UTF-8?q?refactor(phpstan):=20supprime=20les=20acc?= =?UTF-8?q?=C3=A8s=20nullsafe=20superflus=20(=3F->=20->=20->)=20=E2=80=94?= =?UTF-8?q?=2082=20erreurs=20(246=20=E2=86=92=20162)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SubjectAssignmentController.php | 8 +- .../Api/V1/DashboardController.php | 2 +- .../Controllers/CalendarEventController.php | 2 +- .../Comptabilite/InvoiceController.php | 2 +- .../Comptabilite/SituationController.php | 24 +- .../Dashboard/DashboardController.php | 30 +- .../Controllers/Eleves/RosterController.php | 4 +- .../Controllers/Eleves/StudentController.php | 8 +- .../Eleves/TimetableController.php | 2 +- .../Controllers/Examens/MarkController.php | 4 +- .../Examens/OfficialExamController.php | 2 +- .../Notes/NoteReclamationController.php | 2 +- .../Parametres/BulletinTemplateController.php | 4 +- .../Parametres/DocumentHeaderController.php | 2 +- .../Parametres/DocumentTemplateController.php | 2 +- .../Presences/AttendanceController.php | 2 +- app/Http/Middleware/SetLocale.php | 2 +- app/Models/EmployeeProfile.php | 4 +- app/Models/User.php | 2 +- app/Observers/UserObserver.php | 2 +- app/Services/AccountingService.php | 2 +- app/Services/DocumentRenderer.php | 2 +- app/Services/ReportCardBuilder.php | 8 +- database/seeders/DatabaseSeeder.php | 2 +- database/seeders/DefaultUsersSeeder.php | 6 +- database/seeders/DemoSeeder.php | 18 +- .../seeders/RolesAndPermissionsSeeder.php | 2 +- database/seeders/StudentTestSeeder.php | 8 +- phpstan-baseline.neon | 312 ------------------ 29 files changed, 79 insertions(+), 391 deletions(-) diff --git a/app/Http/Controllers/Administration/SubjectAssignmentController.php b/app/Http/Controllers/Administration/SubjectAssignmentController.php index 08112f6e..707d88bf 100644 --- a/app/Http/Controllers/Administration/SubjectAssignmentController.php +++ b/app/Http/Controllers/Administration/SubjectAssignmentController.php @@ -91,8 +91,8 @@ public function statistics(Request $request): Response $assignments = $selected ? $this->teacherAssignments($teacherId, $yearFilter)->map(fn ($a) => [ 'id' => $a->id, - 'subject' => $a->subject?->name ?? '—', - 'classroom' => $a->classroom?->name ?? '—', + 'subject' => $a->subject->name ?? '—', + 'classroom' => $a->classroom->name ?? '—', 'active' => (bool) $a->active, ]) : collect(); @@ -143,7 +143,7 @@ public function exportStatistics(Request $request) 'assignments' => $this->teacherAssignments($teacher->id, $yearId), ])->setPaper('a4', 'portrait'); - return $pdf->stream('affectations-'.Str::slug(trim($teacher->firstname.' '.$teacher->lastname).'-'.($year?->year ?? '')).'.pdf'); + return $pdf->stream('affectations-'.Str::slug(trim($teacher->firstname.' '.$teacher->lastname).'-'.($year->year ?? '')).'.pdf'); } /** Identifiant de l'année académique active (la plus récente marquée active). */ @@ -170,7 +170,7 @@ private function teacherAssignments(string $teacherId, string $yearId): Collecti ->where('teacher_id', $teacherId) ->where('academic_year_id', $yearId) ->get() - ->sortBy(fn ($a) => ($a->subject?->name ?? '').' '.($a->classroom?->name ?? '')) + ->sortBy(fn ($a) => ($a->subject->name ?? '').' '.($a->classroom->name ?? '')) ->values(); } diff --git a/app/Http/Controllers/Api/V1/DashboardController.php b/app/Http/Controllers/Api/V1/DashboardController.php index 90a3101e..7ca42517 100644 --- a/app/Http/Controllers/Api/V1/DashboardController.php +++ b/app/Http/Controllers/Api/V1/DashboardController.php @@ -45,7 +45,7 @@ public function index(Request $request): JsonResponse 'id' => $e->id, 'title' => $e->title, 'type' => $e->type, - 'start_date' => $e->start_date?->format('Y-m-d'), + 'start_date' => $e->start_date->format('Y-m-d'), ]); return response()->json([ diff --git a/app/Http/Controllers/CalendarEventController.php b/app/Http/Controllers/CalendarEventController.php index 60c48a39..7a8bce3d 100644 --- a/app/Http/Controllers/CalendarEventController.php +++ b/app/Http/Controllers/CalendarEventController.php @@ -28,7 +28,7 @@ public function index(Request $request): Response 'title' => $e->title, 'description' => $e->description, 'type' => $e->type, - 'start_date' => $e->start_date?->format('Y-m-d'), + 'start_date' => $e->start_date->format('Y-m-d'), 'end_date' => $e->end_date?->format('Y-m-d'), 'all_day' => $e->all_day, 'start_time' => $e->start_time, diff --git a/app/Http/Controllers/Comptabilite/InvoiceController.php b/app/Http/Controllers/Comptabilite/InvoiceController.php index e870f9cf..33378cb0 100644 --- a/app/Http/Controllers/Comptabilite/InvoiceController.php +++ b/app/Http/Controllers/Comptabilite/InvoiceController.php @@ -169,7 +169,7 @@ public function verifyReceipt(Request $request): Response 'valid' => true, 'receipt_number' => $receipt->receipt_number, 'amount' => (float) $receipt->payment->amount, - 'paid_at' => $receipt->payment->paid_at?->format('d/m/Y'), + 'paid_at' => $receipt->payment->paid_at->format('d/m/Y'), 'student' => $receipt->payment->invoice?->enrollment?->student ? $receipt->payment->invoice->enrollment->student->lastname.' '.$receipt->payment->invoice->enrollment->student->firstname : null, diff --git a/app/Http/Controllers/Comptabilite/SituationController.php b/app/Http/Controllers/Comptabilite/SituationController.php index aa2d8643..6a0a856c 100644 --- a/app/Http/Controllers/Comptabilite/SituationController.php +++ b/app/Http/Controllers/Comptabilite/SituationController.php @@ -93,10 +93,10 @@ classes.code AS cl 'lastname' => $e->student?->lastname, 'matricule' => $e->student?->matricule, 'invoice_number' => $e->invoice?->invoice_number, - 'total' => (float) ($e->invoice?->total ?? 0), - 'amount_paid' => (float) ($e->invoice?->amount_paid ?? 0), - 'amount_remaining' => (float) ($e->invoice?->amount_remaining ?? 0), - 'status' => $e->invoice?->status ?? 'ISSUED', + 'total' => (float) ($e->invoice->total ?? 0), + 'amount_paid' => (float) ($e->invoice->amount_paid ?? 0), + 'amount_remaining' => (float) ($e->invoice->amount_remaining ?? 0), + 'status' => $e->invoice->status ?? 'ISSUED', ]); } @@ -153,14 +153,14 @@ public function export(Request $request) foreach ($enrollments as $e) { $inv = $e->invoice; fputcsv($out, [ - $e->student?->matricule ?? '—', - $e->student?->lastname ?? '—', - $e->student?->firstname ?? '—', - $e->classroom?->name ?? '—', - $inv?->invoice_number ?? '—', - number_format((float) ($inv?->total ?? 0), 0, ',', ' '), - number_format((float) ($inv?->amount_paid ?? 0), 0, ',', ' '), - number_format((float) ($inv?->amount_remaining ?? 0), 0, ',', ' '), + $e->student->matricule ?? '—', + $e->student->lastname ?? '—', + $e->student->firstname ?? '—', + $e->classroom->name ?? '—', + $inv->invoice_number ?? '—', + number_format((float) ($inv->total ?? 0), 0, ',', ' '), + number_format((float) ($inv->amount_paid ?? 0), 0, ',', ' '), + number_format((float) ($inv->amount_remaining ?? 0), 0, ',', ' '), $this->statusLabel($inv?->status), ], ';'); } diff --git a/app/Http/Controllers/Dashboard/DashboardController.php b/app/Http/Controllers/Dashboard/DashboardController.php index f683f97a..25545ed9 100644 --- a/app/Http/Controllers/Dashboard/DashboardController.php +++ b/app/Http/Controllers/Dashboard/DashboardController.php @@ -115,12 +115,12 @@ public function index(Request $request): Response 'id' => $p->id, 'amount' => (float) $p->amount, 'payment_method' => $p->payment_method, - 'paid_at' => $p->paid_at?->toDateString(), + 'paid_at' => $p->paid_at->toDateString(), 'cash_account' => $p->cashAccount?->name, 'student_name' => $p->invoice?->enrollment?->student ? $p->invoice->enrollment->student->firstname.' '.$p->invoice->enrollment->student->lastname : '—', - 'class_name' => $p->invoice?->enrollment?->classroom?->name ?? '—', + 'class_name' => $p->invoice?->enrollment?->classroom->name ?? '—', ]); /* Élèves avec aucun paiement (status ISSUED) — top 5 restant le + élevé */ @@ -193,14 +193,14 @@ public function index(Request $request): Response ->map(fn ($e) => [ 'id' => $e->id, 'enrollment_code' => $e->enrollment_code, - 'enrollment_date' => $e->enrollment_date?->toDateString(), + 'enrollment_date' => $e->enrollment_date->toDateString(), 'status' => $e->status, 'student_name' => $e->student ? $e->student->firstname.' '.$e->student->lastname : '—', 'matricule' => $e->student?->matricule, - 'class_name' => $e->classroom?->name ?? '—', - 'class_code' => $e->classroom?->code ?? '—', + 'class_name' => $e->classroom->name ?? '—', + 'class_code' => $e->classroom->code ?? '—', ]); /* Répartition des inscriptions par classe (pour le graphe) */ @@ -265,8 +265,8 @@ public function index(Request $request): Response 'id' => $p->id, 'student_name' => $p->student ? $p->student->lastname.' '.$p->student->firstname : '—', 'reason' => $p->reason, - 'start_date' => $p->start_date?->format('d/m/Y'), - 'end_date' => $p->end_date?->format('d/m/Y'), + 'start_date' => $p->start_date->format('d/m/Y'), + 'end_date' => $p->end_date->format('d/m/Y'), ]); /* Prochains examens officiels */ @@ -332,10 +332,10 @@ public function index(Request $request): Response $myAssignments = $assignments->map(fn ($a) => [ 'id' => $a->id, - 'subject' => $a->subject?->name ?? '—', + 'subject' => $a->subject->name ?? '—', 'class_id' => $a->class_id, - 'class_name' => $a->classroom?->name ?? '—', - 'class_code' => $a->classroom?->code ?? '—', + 'class_name' => $a->classroom->name ?? '—', + 'class_code' => $a->classroom->code ?? '—', ]); /* Emploi du temps du jour (1=lundi … 6=samedi) */ @@ -350,8 +350,8 @@ public function index(Request $request): Response 'id' => $s->id, 'start_time' => substr((string) $s->start_time, 0, 5), 'end_time' => substr((string) $s->end_time, 0, 5), - 'subject' => $s->subject?->name ?? '—', - 'class_name' => $s->classroom?->name ?? '—', + 'subject' => $s->subject->name ?? '—', + 'class_name' => $s->classroom->name ?? '—', 'room' => $s->room, ]); @@ -378,9 +378,9 @@ public function index(Request $request): Response ->get() ->map(fn ($e) => [ 'id' => $e->id, - 'name' => $e->template?->name ?? 'Évaluation', - 'subject' => $e->classSubject?->subject?->name ?? '—', - 'class_name' => $e->classSubject?->class?->name ?? '—', + 'name' => $e->template->name ?? 'Évaluation', + 'subject' => $e->classSubject?->subject->name ?? '—', + 'class_name' => $e->classSubject?->class->name ?? '—', 'date' => $e->date?->format('d/m/Y'), ]); diff --git a/app/Http/Controllers/Eleves/RosterController.php b/app/Http/Controllers/Eleves/RosterController.php index b84b80a0..92a2d041 100644 --- a/app/Http/Controllers/Eleves/RosterController.php +++ b/app/Http/Controllers/Eleves/RosterController.php @@ -30,7 +30,7 @@ public function index(Request $request): Response // Année par défaut : celle demandée, sinon l'année active if ($yearId === '') { - $yearId = $activeYear?->id ?? ''; + $yearId = $activeYear->id ?? ''; } $classrooms = Classroom::where('active', true)->orderBy('name')->get(['id', 'name', 'code']); @@ -59,7 +59,7 @@ public function index(Request $request): Response 'student_name' => $e->student ? $e->student->lastname.' '.$e->student->firstname : '—', 'matricule' => $e->student?->matricule, 'gender' => $e->student?->gender, - 'payment_status' => $e->invoice?->status ?? 'NONE', + 'payment_status' => $e->invoice->status ?? 'NONE', 'academic_status' => $e->academic_status ?? 'en_cours', 'status_reason' => $e->status_reason, ])->values(); diff --git a/app/Http/Controllers/Eleves/StudentController.php b/app/Http/Controllers/Eleves/StudentController.php index 555f6b56..0b7ebfbd 100644 --- a/app/Http/Controllers/Eleves/StudentController.php +++ b/app/Http/Controllers/Eleves/StudentController.php @@ -219,7 +219,7 @@ public function show(Student $student): Response 'reference_number' => $i->reference_number, 'template_name' => $i->template?->name, 'issued_by' => $i->issuedBy?->name, - 'issued_at' => $i->issued_at?->format('d/m/Y H:i'), + 'issued_at' => $i->issued_at->format('d/m/Y H:i'), ]); // Inscription de l'année active (pour la réaffectation de classe) @@ -244,7 +244,7 @@ public function show(Student $student): Response 'class_id' => $currentEnrollment->class_id, 'class_name' => $currentEnrollment->classroom?->name, 'class_code' => $currentEnrollment->classroom?->code, - 'year' => $activeYear?->year, + 'year' => $activeYear->year, ] : null, 'classrooms' => Classroom::where('active', true)->orderBy('name')->get(['id', 'name', 'code']), 'documents' => $student->documents()->with('uploadedBy:id,firstname,lastname')->latest()->get()->map(fn ($d) => [ @@ -305,9 +305,9 @@ public function history(Student $student): Response 'code' => $enrollment->classroom?->code, ], 'academic_year' => $enrollment->academicYear?->year, - 'enrollment_date' => $enrollment->enrollment_date?->format('Y-m-d'), + 'enrollment_date' => $enrollment->enrollment_date->format('Y-m-d'), 'enrollment_code' => $enrollment->enrollment_code, - 'status' => $enrollment->invoice?->status ?? 'NONE', + 'status' => $enrollment->invoice->status ?? 'NONE', 'academic_status' => $enrollment->academic_status ?? 'en_cours', 'academic_status_label' => Enrollment::ACADEMIC_STATUSES[$enrollment->academic_status ?? 'en_cours'] ?? '—', 'status_reason' => $enrollment->status_reason, diff --git a/app/Http/Controllers/Eleves/TimetableController.php b/app/Http/Controllers/Eleves/TimetableController.php index a0802ebe..a20b864e 100644 --- a/app/Http/Controllers/Eleves/TimetableController.php +++ b/app/Http/Controllers/Eleves/TimetableController.php @@ -125,7 +125,7 @@ public function teacherExport(Request $request) return $this->schedulePdf( $this->teacherSlots($teacher->id, $activeYearId), title: 'EMPLOI DU TEMPS — ENSEIGNANT', - subtitle: $teacher->name.' — Année : '.($year?->year ?? '—'), + subtitle: $teacher->name.' — Année : '.($year->year ?? '—'), filename: 'emploi-du-temps-'.Str::slug($teacher->name), ); } diff --git a/app/Http/Controllers/Examens/MarkController.php b/app/Http/Controllers/Examens/MarkController.php index 43ff2381..900a57e1 100644 --- a/app/Http/Controllers/Examens/MarkController.php +++ b/app/Http/Controllers/Examens/MarkController.php @@ -61,7 +61,7 @@ public function index(Evaluation $evaluation): Response 'student' => $enrollment->student, 'mark_id' => $mark?->id, 'score' => $mark?->score !== null ? (float) $mark->score : null, - 'absent' => $mark?->absent ?? false, + 'absent' => $mark->absent ?? false, 'comments' => $mark?->comments, ]; })->values(); @@ -97,7 +97,7 @@ public function store(Request $request, Evaluation $evaluation) return back()->withErrors(['locked' => $decision->message()]); } - $maxScore = (float) ($evaluation->template?->max_score ?? 20); + $maxScore = (float) ($evaluation->template->max_score ?? 20); // L'élève noté doit être inscrit (activement) dans la classe de l'évaluation : // `exists:students,id` seul laisserait noter un élève d'une autre classe. diff --git a/app/Http/Controllers/Examens/OfficialExamController.php b/app/Http/Controllers/Examens/OfficialExamController.php index 33400cd8..7507d459 100644 --- a/app/Http/Controllers/Examens/OfficialExamController.php +++ b/app/Http/Controllers/Examens/OfficialExamController.php @@ -26,7 +26,7 @@ public function index(Request $request): Response $years = AcademicYear::orderByDesc('start_date')->get(['id', 'year', 'active']); // Année par défaut = année académique active - $yearId = $request->string('academic_year_id')->toString() ?: ($activeYear?->id ?? ''); + $yearId = $request->string('academic_year_id')->toString() ?: ($activeYear->id ?? ''); $type = $request->string('type')->toString(); $session = $request->string('session')->toString(); $status = $request->string('status')->toString(); diff --git a/app/Http/Controllers/Notes/NoteReclamationController.php b/app/Http/Controllers/Notes/NoteReclamationController.php index 251c1b41..dc446abf 100644 --- a/app/Http/Controllers/Notes/NoteReclamationController.php +++ b/app/Http/Controllers/Notes/NoteReclamationController.php @@ -152,7 +152,7 @@ public function review(ReviewNoteReclamationRequest $request, NoteReclamation $n $data = $request->validated(); $evaluation = $noteReclamation->evaluation()->with('template:id,max_score')->first(); - $maxScore = (float) ($evaluation?->template?->max_score ?? 9999); + $maxScore = (float) ($evaluation?->template->max_score ?? 9999); if ($data['status'] === 'approved' && isset($data['corrected_score']) && $data['corrected_score'] > $maxScore) { return back()->withErrors(['corrected_score' => "La note corrigée ne peut pas dépasser {$maxScore}."]); diff --git a/app/Http/Controllers/Parametres/BulletinTemplateController.php b/app/Http/Controllers/Parametres/BulletinTemplateController.php index c105df61..0a362ca1 100644 --- a/app/Http/Controllers/Parametres/BulletinTemplateController.php +++ b/app/Http/Controllers/Parametres/BulletinTemplateController.php @@ -21,8 +21,8 @@ public function edit(Request $request): Response $template = BulletinTemplate::where('school_id', $school->id) ->whereNull('classroom_type_id')->first(); - $columns = $template?->columns ?? BulletinTemplate::defaultColumns(); - $options = $template?->options ?? BulletinTemplate::defaultOptions(); + $columns = $template->columns ?? BulletinTemplate::defaultColumns(); + $options = $template->options ?? BulletinTemplate::defaultOptions(); return Inertia::render('Parametres/BulletinTemplates/Edit', [ 'columns' => $columns, diff --git a/app/Http/Controllers/Parametres/DocumentHeaderController.php b/app/Http/Controllers/Parametres/DocumentHeaderController.php index 040c7a34..f1d1bc8d 100644 --- a/app/Http/Controllers/Parametres/DocumentHeaderController.php +++ b/app/Http/Controllers/Parametres/DocumentHeaderController.php @@ -29,7 +29,7 @@ public function edit(Request $request): Response return Inertia::render('Parametres/Documents/HeaderDesigner', [ 'header' => $config, - 'preset' => $header?->preset ?? 'ministeriel', + 'preset' => $header->preset ?? 'ministeriel', 'presets' => DocumentHeader::PRESETS, 'default' => DocumentHeader::defaultLayout($school), 'watermarkImageUrl' => $this->mediaUrl($config['watermark']['image_path'] ?? null), diff --git a/app/Http/Controllers/Parametres/DocumentTemplateController.php b/app/Http/Controllers/Parametres/DocumentTemplateController.php index f089a667..33e61a22 100644 --- a/app/Http/Controllers/Parametres/DocumentTemplateController.php +++ b/app/Http/Controllers/Parametres/DocumentTemplateController.php @@ -83,7 +83,7 @@ public function registry(Request $request): Response 'student_name' => $i->student ? $i->student->lastname.' '.$i->student->firstname : '—', 'student_matricule' => $i->student?->matricule, 'issued_by' => $i->issuedBy?->name, - 'issued_at' => $i->issued_at?->format('d/m/Y H:i'), + 'issued_at' => $i->issued_at->format('d/m/Y H:i'), ]); return Inertia::render('Parametres/Documents/Registry', [ diff --git a/app/Http/Controllers/Presences/AttendanceController.php b/app/Http/Controllers/Presences/AttendanceController.php index e128571c..3a429a50 100644 --- a/app/Http/Controllers/Presences/AttendanceController.php +++ b/app/Http/Controllers/Presences/AttendanceController.php @@ -77,7 +77,7 @@ public function index(Request $request): Response 'student_id' => $enrollment->student_id, 'student' => $enrollment->student, 'record_id' => $record?->id, - 'status' => $record?->status ?? ($permission ? 'excused' : 'present'), + 'status' => $record->status ?? ($permission ? 'excused' : 'present'), 'minutes_late' => $record?->minutes_late, 'comment' => $record?->comment, 'has_permission' => (bool) $permission, diff --git a/app/Http/Middleware/SetLocale.php b/app/Http/Middleware/SetLocale.php index 128e2485..24cf854e 100644 --- a/app/Http/Middleware/SetLocale.php +++ b/app/Http/Middleware/SetLocale.php @@ -16,7 +16,7 @@ class SetLocale */ public function handle(Request $request, Closure $next): Response { - $candidate = $request->user()?->locale + $candidate = $request->user()->locale ?? $request->session()->get('locale') ?? config('app.locale'); diff --git a/app/Models/EmployeeProfile.php b/app/Models/EmployeeProfile.php index 78626ce1..b55f2475 100644 --- a/app/Models/EmployeeProfile.php +++ b/app/Models/EmployeeProfile.php @@ -74,7 +74,7 @@ public function allowances(): HasMany /** Nom complet de l'employé (via le compte utilisateur). */ public function fullName(): string { - return trim(($this->user?->firstname ?? '').' '.($this->user?->lastname ?? '')); + return trim(($this->user->firstname ?? '').' '.($this->user->lastname ?? '')); } /** @@ -83,6 +83,6 @@ public function fullName(): string */ public function effectiveBaseSalary(): float { - return (float) ($this->salaryGrade?->base_amount ?? $this->base_salary); + return (float) ($this->salaryGrade->base_amount ?? $this->base_salary); } } diff --git a/app/Models/User.php b/app/Models/User.php index b3482759..c8916375 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -165,7 +165,7 @@ public function generateMatricule(): string $matriculeService = app(MatriculeService::class); // Obtenir le rôle principal de l'utilisateur - $role = $this->roles?->first()?->name; + $role = $this->roles->first()?->name; if ($role) { return $matriculeService->generateUserMatricule($role); diff --git a/app/Observers/UserObserver.php b/app/Observers/UserObserver.php index 2909403f..622c85cf 100644 --- a/app/Observers/UserObserver.php +++ b/app/Observers/UserObserver.php @@ -18,7 +18,7 @@ public function created(User $user): void $user->natricule, 'user', $user->id, - $user->roles->first()?->name ?? 'unknown' + $user->roles->first()->name ?? 'unknown' ); } } diff --git a/app/Services/AccountingService.php b/app/Services/AccountingService.php index 31b08aba..fc54177c 100644 --- a/app/Services/AccountingService.php +++ b/app/Services/AccountingService.php @@ -112,7 +112,7 @@ public function recordScholarshipTransaction( $studentName = $student ? "{$student->firstname} {$student->lastname}" : 'Élève inconnu'; - $scholarshipName = $scholarship?->name ?? 'Bourse'; + $scholarshipName = $scholarship->name ?? 'Bourse'; return AccountingTransaction::create([ 'type' => 'EXPENSE', diff --git a/app/Services/DocumentRenderer.php b/app/Services/DocumentRenderer.php index 309a3bef..03b60d2d 100644 --- a/app/Services/DocumentRenderer.php +++ b/app/Services/DocumentRenderer.php @@ -198,7 +198,7 @@ public function watermarkHtml(?School $school, array $variables): string protected function renderHeader(DocumentTemplate $template, array $variables): string { $header = $template->school?->documentHeader; - $preset = $header?->preset ?? 'ministeriel'; + $preset = $header->preset ?? 'ministeriel'; // Préréglage « personnalisé » : canevas glisser-déposer (si des éléments existent). // Sinon on rend le gabarit officiel ministériel (défaut). diff --git a/app/Services/ReportCardBuilder.php b/app/Services/ReportCardBuilder.php index 555a1686..99da9705 100644 --- a/app/Services/ReportCardBuilder.php +++ b/app/Services/ReportCardBuilder.php @@ -99,7 +99,7 @@ public function build( 'lowest' => $values->isNotEmpty() ? $values->min() : null, 'average' => $values->isNotEmpty() ? round($values->avg(), 2) : null, ]; - $periodSystem = $class->classroomType?->period_system ?? 'trimestre'; + $periodSystem = $class->classroomType->period_system ?? 'trimestre'; $retards = $this->retardsByStudent($class->id, $period); // Récapitulatif inter-périodes + annuel (classements précalculés une fois, depuis l'index). @@ -162,7 +162,7 @@ public function build( } $lines[] = [ - 'subject' => $cs->subject?->name ?? '', + 'subject' => $cs->subject->name ?? '', 'parent' => $cs->subject?->parent?->name, 'group' => $cs->group ?? 'obligatoire', 'coefficient' => $coeff, @@ -257,7 +257,7 @@ public function classSubjects(Classroom $class, ?string $yearId): Collection ->with(['subject:id,name,code,parent_id', 'subject.parent:id,name']) ->get() // Regroupe les sous-matières sous leur matière parente. - ->sortBy(fn ($cs) => ($cs->subject?->parent?->name ?? $cs->subject?->name).'~'.($cs->subject?->name)) + ->sortBy(fn ($cs) => ($cs->subject?->parent->name ?? $cs->subject->name).'~'.($cs->subject->name)) ->values(); } @@ -282,7 +282,7 @@ private function teachersBySubject(string $classId, ?string $yearId): array ->when($yearId, fn ($q) => $q->where('academic_year_id', $yearId)) ->with('teacher:id,firstname,lastname') ->get() - ->mapWithKeys(fn ($a) => [$a->subject_id => trim(($a->teacher?->firstname ?? '').' '.($a->teacher?->lastname ?? ''))]) + ->mapWithKeys(fn ($a) => [$a->subject_id => trim(($a->teacher->firstname ?? '').' '.($a->teacher->lastname ?? ''))]) ->toArray(); } diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index 5925260f..96c617b1 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -23,7 +23,7 @@ public function run(): void // 2) Données de démonstration — jamais en production if (app()->environment('production')) { - $this->command?->warn( + $this->command->warn( 'Données de démonstration ignorées (environnement de production). ' .'Seules les données de référence ont été installées.' ); diff --git a/database/seeders/DefaultUsersSeeder.php b/database/seeders/DefaultUsersSeeder.php index cc684958..4df59493 100644 --- a/database/seeders/DefaultUsersSeeder.php +++ b/database/seeders/DefaultUsersSeeder.php @@ -18,7 +18,7 @@ public function run(): void { // Garde-fou : ces comptes utilisent un mot de passe connu, jamais en production. if (app()->environment('production')) { - $this->command?->warn('DefaultUsersSeeder ignoré : comptes de démonstration interdits en production.'); + $this->command->warn('DefaultUsersSeeder ignoré : comptes de démonstration interdits en production.'); return; } @@ -71,10 +71,10 @@ public function run(): void $user->save(); } - $this->command?->info("• {$email} ({$role})"); + $this->command->info("• {$email} ({$role})"); } - $this->command?->warn( + $this->command->warn( 'Comptes de démonstration créés — mot de passe : "'.self::PASSWORD.'". ' .'Un changement de mot de passe sera exigé à la première connexion.' ); diff --git a/database/seeders/DemoSeeder.php b/database/seeders/DemoSeeder.php index f4d851ca..0cc82d6d 100644 --- a/database/seeders/DemoSeeder.php +++ b/database/seeders/DemoSeeder.php @@ -162,7 +162,7 @@ class DemoSeeder extends Seeder public function run(): void { if (app()->environment('production')) { - $this->command?->warn('DemoSeeder ignoré : données de démonstration interdites en production.'); + $this->command->warn('DemoSeeder ignoré : données de démonstration interdites en production.'); return; } @@ -195,8 +195,8 @@ public function run(): void $this->step('Bulletins', fn () => $this->seedReportCards()); $this->step('Années passées (comparaisons)', fn () => $this->seedHistory()); - $this->command?->newLine(); - $this->command?->info('Jeu de démonstration prêt.'); + $this->command->newLine(); + $this->command->info('Jeu de démonstration prêt.'); } /* ------------------------------------------------------------------ */ @@ -214,7 +214,7 @@ private function resolveContext(): bool ?? AcademicYear::query()->latest('start_date')->first(); if (! $school || ! $year) { - $this->command?->error('DemoSeeder : école ou année académique manquante. Lancez db:seed au préalable.'); + $this->command->error('DemoSeeder : école ou année académique manquante. Lancez db:seed au préalable.'); return false; } @@ -228,7 +228,7 @@ private function resolveContext(): bool ->get(); if ($this->periods->isEmpty()) { - $this->command?->error('DemoSeeder : aucune période académique pour l\'année active.'); + $this->command->error('DemoSeeder : aucune période académique pour l\'année active.'); return false; } @@ -240,7 +240,7 @@ private function resolveContext(): bool ->get(); if ($this->classes->isEmpty()) { - $this->command?->error('DemoSeeder : aucune classe active (ClassroomSeeder).'); + $this->command->error('DemoSeeder : aucune classe active (ClassroomSeeder).'); return false; } @@ -251,7 +251,7 @@ private function resolveContext(): bool /** Cycle d'une classe, tel que référencé par self::CURRICULUM. */ private function cycleOf(Classroom $class): string { - $type = Str::lower($class->type?->name ?? ''); + $type = Str::lower($class->type->name ?? ''); return match (true) { str_contains($type, 'primaire'), str_contains($type, 'maternelle') => 'primaire', @@ -272,7 +272,7 @@ private function targetSize(Classroom $class): int private function step(string $label, callable $work): void { - $this->command?->line(" - {$label}..."); + $this->command->line(" - {$label}..."); $work(); } @@ -295,7 +295,7 @@ private function localites(): array $path = database_path('data/togo-localites.json'); if (! is_file($path)) { - $this->command?->warn('togo-localites.json introuvable : les élèves seront créés sans origine géographique.'); + $this->command->warn('togo-localites.json introuvable : les élèves seront créés sans origine géographique.'); return $this->localites = []; } diff --git a/database/seeders/RolesAndPermissionsSeeder.php b/database/seeders/RolesAndPermissionsSeeder.php index 0f9630d1..fd8ca28d 100644 --- a/database/seeders/RolesAndPermissionsSeeder.php +++ b/database/seeders/RolesAndPermissionsSeeder.php @@ -141,7 +141,7 @@ public function run(): void ], [Permissions::VIEW_STUDENT_PARENTS_INFO]); Role::firstOrCreate(['name' => Roles::SECRETARIAT])->syncPermissions($secretary); - $this->command?->info('Rôles et permissions synchronisés ('.count(Permissions::all()).' permissions).'); + $this->command->info('Rôles et permissions synchronisés ('.count(Permissions::all()).' permissions).'); } /** diff --git a/database/seeders/StudentTestSeeder.php b/database/seeders/StudentTestSeeder.php index b367ac1f..8a87fc36 100644 --- a/database/seeders/StudentTestSeeder.php +++ b/database/seeders/StudentTestSeeder.php @@ -34,14 +34,14 @@ public function run(): void { // Garde-fou : données fictives interdites en production. if (app()->environment('production')) { - $this->command?->warn('StudentTestSeeder ignoré : données fictives interdites en production.'); + $this->command->warn('StudentTestSeeder ignoré : données fictives interdites en production.'); return; } // Idempotent : on ne régénère pas de faux élèves si la base en contient déjà. if (Student::query()->exists()) { - $this->command?->warn('StudentTestSeeder ignoré : des élèves existent déjà.'); + $this->command->warn('StudentTestSeeder ignoré : des élèves existent déjà.'); return; } @@ -56,7 +56,7 @@ public function run(): void $canEnroll = $school && $academicYear && $classroomIds !== []; if (! $canEnroll) { - $this->command?->warn( + $this->command->warn( 'Élèves créés sans inscription : école, année académique active ou classes manquantes.' ); } @@ -153,7 +153,7 @@ public function run(): void } } - $this->command?->info( + $this->command->info( self::COUNT.' élèves de test créés'.($canEnroll ? ' et inscrits.' : ' (sans inscription).') ); } diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 9ac9dd31..5eedc9c6 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,17 +1,5 @@ parameters: ignoreErrors: - - - message: '#^Using nullsafe property access "\?\-\>name" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' - identifier: nullsafe.neverNull - count: 4 - path: app/Http/Controllers/Administration/SubjectAssignmentController.php - - - - message: '#^Using nullsafe property access "\?\-\>year" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' - identifier: nullsafe.neverNull - count: 1 - path: app/Http/Controllers/Administration/SubjectAssignmentController.php - - message: '#^Instanceof between App\\Models\\User\|null and App\\Models\\Guardian will always evaluate to false\.$#' identifier: instanceof.alwaysFalse @@ -60,12 +48,6 @@ parameters: count: 1 path: app/Http/Controllers/Api/V1/ChildrenController.php - - - message: '#^Using nullsafe method call on non\-nullable type Carbon\\CarbonImmutable\. Use \-\> instead\.$#' - identifier: nullsafe.neverNull - count: 1 - path: app/Http/Controllers/Api/V1/DashboardController.php - - message: '#^Access to an undefined property App\\Models\\User\:\:\$name\.$#' identifier: property.notFound @@ -90,78 +72,12 @@ parameters: count: 1 path: app/Http/Controllers/Archives/ArchiveController.php - - - message: '#^Using nullsafe method call on non\-nullable type Carbon\\CarbonImmutable\. Use \-\> instead\.$#' - identifier: nullsafe.neverNull - count: 1 - path: app/Http/Controllers/CalendarEventController.php - - message: '#^Method Illuminate\\Database\\Eloquent\\Builder\\:\:withSum\(\) invoked with 3 parameters, 2 required\.$#' identifier: arguments.count count: 1 path: app/Http/Controllers/Comptabilite/CashAccountController.php - - - message: '#^Using nullsafe method call on non\-nullable type Carbon\\CarbonImmutable\. Use \-\> instead\.$#' - identifier: nullsafe.neverNull - count: 1 - path: app/Http/Controllers/Comptabilite/InvoiceController.php - - - - message: '#^Using nullsafe property access "\?\-\>amount_paid" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' - identifier: nullsafe.neverNull - count: 2 - path: app/Http/Controllers/Comptabilite/SituationController.php - - - - message: '#^Using nullsafe property access "\?\-\>amount_remaining" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' - identifier: nullsafe.neverNull - count: 2 - path: app/Http/Controllers/Comptabilite/SituationController.php - - - - message: '#^Using nullsafe property access "\?\-\>firstname" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' - identifier: nullsafe.neverNull - count: 1 - path: app/Http/Controllers/Comptabilite/SituationController.php - - - - message: '#^Using nullsafe property access "\?\-\>invoice_number" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' - identifier: nullsafe.neverNull - count: 1 - path: app/Http/Controllers/Comptabilite/SituationController.php - - - - message: '#^Using nullsafe property access "\?\-\>lastname" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' - identifier: nullsafe.neverNull - count: 1 - path: app/Http/Controllers/Comptabilite/SituationController.php - - - - message: '#^Using nullsafe property access "\?\-\>matricule" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' - identifier: nullsafe.neverNull - count: 1 - path: app/Http/Controllers/Comptabilite/SituationController.php - - - - message: '#^Using nullsafe property access "\?\-\>name" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' - identifier: nullsafe.neverNull - count: 1 - path: app/Http/Controllers/Comptabilite/SituationController.php - - - - message: '#^Using nullsafe property access "\?\-\>status" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' - identifier: nullsafe.neverNull - count: 1 - path: app/Http/Controllers/Comptabilite/SituationController.php - - - - message: '#^Using nullsafe property access "\?\-\>total" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' - identifier: nullsafe.neverNull - count: 2 - path: app/Http/Controllers/Comptabilite/SituationController.php - - message: '#^Access to an undefined property App\\Models\\ReportCard\:\:\$avg_general\.$#' identifier: property.notFound @@ -174,24 +90,6 @@ parameters: count: 1 path: app/Http/Controllers/Dashboard/DashboardController.php - - - message: '#^Using nullsafe method call on non\-nullable type Carbon\\CarbonImmutable\. Use \-\> instead\.$#' - identifier: nullsafe.neverNull - count: 4 - path: app/Http/Controllers/Dashboard/DashboardController.php - - - - message: '#^Using nullsafe property access "\?\-\>code" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' - identifier: nullsafe.neverNull - count: 2 - path: app/Http/Controllers/Dashboard/DashboardController.php - - - - message: '#^Using nullsafe property access "\?\-\>name" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' - identifier: nullsafe.neverNull - count: 9 - path: app/Http/Controllers/Dashboard/DashboardController.php - - message: '#^Access to an undefined property App\\Models\\Enrollment\:\:\$billed\.$#' identifier: property.notFound @@ -222,18 +120,6 @@ parameters: count: 1 path: app/Http/Controllers/Eleves/EnrollmentController.php - - - message: '#^Using nullsafe property access "\?\-\>id" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' - identifier: nullsafe.neverNull - count: 1 - path: app/Http/Controllers/Eleves/RosterController.php - - - - message: '#^Using nullsafe property access "\?\-\>status" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' - identifier: nullsafe.neverNull - count: 1 - path: app/Http/Controllers/Eleves/RosterController.php - - message: '#^Access to an undefined property App\\Models\\User\:\:\$name\.$#' identifier: property.notFound @@ -264,24 +150,6 @@ parameters: count: 1 path: app/Http/Controllers/Eleves/StudentController.php - - - message: '#^Using nullsafe method call on non\-nullable type Carbon\\CarbonImmutable\. Use \-\> instead\.$#' - identifier: nullsafe.neverNull - count: 2 - path: app/Http/Controllers/Eleves/StudentController.php - - - - message: '#^Using nullsafe property access "\?\-\>status" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' - identifier: nullsafe.neverNull - count: 1 - path: app/Http/Controllers/Eleves/StudentController.php - - - - message: '#^Using nullsafe property access on non\-nullable type App\\Models\\AcademicYear\. Use \-\> instead\.$#' - identifier: nullsafe.neverNull - count: 1 - path: app/Http/Controllers/Eleves/StudentController.php - - message: '#^Access to an undefined property App\\Models\\Enrollment\:\:\$birth_date\.$#' identifier: property.notFound @@ -378,24 +246,6 @@ parameters: count: 1 path: app/Http/Controllers/Eleves/TimetableController.php - - - message: '#^Using nullsafe property access "\?\-\>year" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' - identifier: nullsafe.neverNull - count: 1 - path: app/Http/Controllers/Eleves/TimetableController.php - - - - message: '#^Using nullsafe property access "\?\-\>absent" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' - identifier: nullsafe.neverNull - count: 1 - path: app/Http/Controllers/Examens/MarkController.php - - - - message: '#^Using nullsafe property access "\?\-\>max_score" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' - identifier: nullsafe.neverNull - count: 1 - path: app/Http/Controllers/Examens/MarkController.php - - message: '#^Access to an undefined property App\\Models\\OfficialExam\:\:\$admis_count\.$#' identifier: property.notFound @@ -408,12 +258,6 @@ parameters: count: 1 path: app/Http/Controllers/Examens/OfficialExamController.php - - - message: '#^Using nullsafe property access "\?\-\>id" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' - identifier: nullsafe.neverNull - count: 1 - path: app/Http/Controllers/Examens/OfficialExamController.php - - message: '#^Parameter \#1 \$rows of method App\\Services\\GradingService\:\:rank\(\) expects Illuminate\\Support\\Collection\, Illuminate\\Support\\Collection\ given\.$#' identifier: argument.type @@ -432,12 +276,6 @@ parameters: count: 1 path: app/Http/Controllers/Notes/NoteReclamationController.php - - - message: '#^Using nullsafe property access "\?\-\>max_score" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' - identifier: nullsafe.neverNull - count: 1 - path: app/Http/Controllers/Notes/NoteReclamationController.php - - message: '#^Access to an undefined property App\\Models\\User\:\:\$name\.$#' identifier: property.notFound @@ -462,30 +300,12 @@ parameters: count: 1 path: app/Http/Controllers/Parametres/BulletinTemplateController.php - - - message: '#^Using nullsafe property access "\?\-\>columns" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' - identifier: nullsafe.neverNull - count: 1 - path: app/Http/Controllers/Parametres/BulletinTemplateController.php - - - - message: '#^Using nullsafe property access "\?\-\>options" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' - identifier: nullsafe.neverNull - count: 1 - path: app/Http/Controllers/Parametres/BulletinTemplateController.php - - message: '#^Parameter \#1 \$boolean of function abort_unless expects bool, App\\Models\\School\|null given\.$#' identifier: argument.type count: 1 path: app/Http/Controllers/Parametres/DocumentHeaderController.php - - - message: '#^Using nullsafe property access "\?\-\>preset" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' - identifier: nullsafe.neverNull - count: 1 - path: app/Http/Controllers/Parametres/DocumentHeaderController.php - - message: '#^Access to an undefined property App\\Models\\User\:\:\$name\.$#' identifier: property.notFound @@ -498,12 +318,6 @@ parameters: count: 1 path: app/Http/Controllers/Parametres/DocumentTemplateController.php - - - message: '#^Using nullsafe method call on non\-nullable type Carbon\\CarbonImmutable\. Use \-\> instead\.$#' - identifier: nullsafe.neverNull - count: 1 - path: app/Http/Controllers/Parametres/DocumentTemplateController.php - - message: '#^Access to an undefined property App\\Models\\FeeStructure\:\:\$installments\.$#' identifier: property.notFound @@ -516,12 +330,6 @@ parameters: count: 2 path: app/Http/Controllers/Parametres/FeeStructureController.php - - - message: '#^Using nullsafe property access "\?\-\>status" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' - identifier: nullsafe.neverNull - count: 1 - path: app/Http/Controllers/Presences/AttendanceController.php - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$id\.$#' identifier: property.notFound @@ -546,12 +354,6 @@ parameters: count: 1 path: app/Http/Controllers/Rh/UserPayrollController.php - - - message: '#^Using nullsafe property access "\?\-\>locale" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' - identifier: nullsafe.neverNull - count: 1 - path: app/Http/Middleware/SetLocale.php - - message: '#^Access to an undefined property App\\Http\\Resources\\Api\\CalendarEventResource\:\:\$all_day\.$#' identifier: property.notFound @@ -714,24 +516,6 @@ parameters: count: 1 path: app/Models/CashAccount.php - - - message: '#^Using nullsafe property access "\?\-\>base_amount" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' - identifier: nullsafe.neverNull - count: 1 - path: app/Models/EmployeeProfile.php - - - - message: '#^Using nullsafe property access "\?\-\>firstname" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' - identifier: nullsafe.neverNull - count: 1 - path: app/Models/EmployeeProfile.php - - - - message: '#^Using nullsafe property access "\?\-\>lastname" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' - identifier: nullsafe.neverNull - count: 1 - path: app/Models/EmployeeProfile.php - - message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Model\:\:auditExcluded\(\)\.$#' identifier: method.notFound @@ -834,24 +618,6 @@ parameters: count: 1 path: app/Models/User.php - - - message: '#^Using nullsafe method call on non\-nullable type Illuminate\\Database\\Eloquent\\Collection\\. Use \-\> instead\.$#' - identifier: nullsafe.neverNull - count: 1 - path: app/Models/User.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$name\.$#' - identifier: property.notFound - count: 1 - path: app/Observers/UserObserver.php - - - - message: '#^Using nullsafe property access "\?\-\>name" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' - identifier: nullsafe.neverNull - count: 1 - path: app/Observers/UserObserver.php - - message: '#^Result of && is always false\.$#' identifier: booleanAnd.alwaysFalse @@ -864,12 +630,6 @@ parameters: count: 1 path: app/Rules/UniqueMatricule.php - - - message: '#^Using nullsafe property access "\?\-\>name" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' - identifier: nullsafe.neverNull - count: 1 - path: app/Services/AccountingService.php - - message: '#^Offset 1 on array\{list\, list\\} on left side of \?\? always exists and is not nullable\.$#' identifier: nullCoalesce.offset @@ -906,12 +666,6 @@ parameters: count: 1 path: app/Services/DocumentRenderer.php - - - message: '#^Using nullsafe property access "\?\-\>preset" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' - identifier: nullsafe.neverNull - count: 1 - path: app/Services/DocumentRenderer.php - - message: '#^Parameter \#1 \$rows of method App\\Services\\GradingService\:\:assignRanks\(\) expects Illuminate\\Support\\Collection\, Illuminate\\Support\\Collection\ given\.$#' identifier: argument.type @@ -948,12 +702,6 @@ parameters: count: 1 path: app/Services/PayrollService.php - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$period_system\.$#' - identifier: property.notFound - count: 1 - path: app/Services/ReportCardBuilder.php - - message: '#^Offset ''by_type'' on array\{classe\: float\|null, compo\: float\|null, moy\: float\|null, by_type\: array\\} on left side of \?\? always exists and is not nullable\.$#' identifier: nullCoalesce.offset @@ -978,30 +726,6 @@ parameters: count: 1 path: app/Services/ReportCardBuilder.php - - - message: '#^Using nullsafe property access "\?\-\>firstname" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' - identifier: nullsafe.neverNull - count: 1 - path: app/Services/ReportCardBuilder.php - - - - message: '#^Using nullsafe property access "\?\-\>lastname" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' - identifier: nullsafe.neverNull - count: 1 - path: app/Services/ReportCardBuilder.php - - - - message: '#^Using nullsafe property access "\?\-\>name" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' - identifier: nullsafe.neverNull - count: 2 - path: app/Services/ReportCardBuilder.php - - - - message: '#^Using nullsafe property access "\?\-\>period_system" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' - identifier: nullsafe.neverNull - count: 1 - path: app/Services/ReportCardBuilder.php - - message: '#^Called ''count'' on Laravel collection, but could have been retrieved as a query\.$#' identifier: larastan.noUnnecessaryCollectionCall @@ -1050,44 +774,8 @@ parameters: count: 1 path: database/migrations/2026_06_14_000011_add_locked_at_to_evaluations_table.php - - - message: '#^Using nullsafe method call on non\-nullable type Illuminate\\Console\\Command\. Use \-\> instead\.$#' - identifier: nullsafe.neverNull - count: 1 - path: database/seeders/DatabaseSeeder.php - - - - message: '#^Using nullsafe method call on non\-nullable type Illuminate\\Console\\Command\. Use \-\> instead\.$#' - identifier: nullsafe.neverNull - count: 3 - path: database/seeders/DefaultUsersSeeder.php - - - - message: '#^Using nullsafe method call on non\-nullable type Illuminate\\Console\\Command\. Use \-\> instead\.$#' - identifier: nullsafe.neverNull - count: 8 - path: database/seeders/DemoSeeder.php - - - - message: '#^Using nullsafe property access "\?\-\>name" on left side of \?\? is unnecessary\. Use \-\> instead\.$#' - identifier: nullsafe.neverNull - count: 1 - path: database/seeders/DemoSeeder.php - - - - message: '#^Using nullsafe method call on non\-nullable type Illuminate\\Console\\Command\. Use \-\> instead\.$#' - identifier: nullsafe.neverNull - count: 1 - path: database/seeders/RolesAndPermissionsSeeder.php - - message: '#^Method Database\\Seeders\\StudentTestSeeder\:\:resolveAcademicYear\(\) never returns null so it can be removed from the return type\.$#' identifier: return.unusedType count: 1 path: database/seeders/StudentTestSeeder.php - - - - message: '#^Using nullsafe method call on non\-nullable type Illuminate\\Console\\Command\. Use \-\> instead\.$#' - identifier: nullsafe.neverNull - count: 4 - path: database/seeders/StudentTestSeeder.php From 5771ab8d5be5f8f71a852a407be585a2ff89b251 Mon Sep 17 00:00:00 2001 From: horacioskrp Date: Fri, 13 Nov 2026 14:18:31 +0000 Subject: [PATCH 04/12] =?UTF-8?q?fix(phpstan):=20typage=20self=20dans=20le?= =?UTF-8?q?=20trait=20Auditable=20+=20type=20UUID=20dans=20UniqueMatricule?= =?UTF-8?q?=20(162=20=E2=86=92=20124)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Concerns/Auditable.php | 7 +- app/Rules/UniqueMatricule.php | 4 +- phpstan-baseline.neon | 120 ---------------------------------- 3 files changed, 5 insertions(+), 126 deletions(-) diff --git a/app/Concerns/Auditable.php b/app/Concerns/Auditable.php index 5cbb81f2..ae1a097b 100644 --- a/app/Concerns/Auditable.php +++ b/app/Concerns/Auditable.php @@ -3,7 +3,6 @@ namespace App\Concerns; use App\Models\AuditLog; -use Illuminate\Database\Eloquent\Model; /** * Enregistre automatiquement les créations / modifications / suppressions @@ -16,9 +15,9 @@ trait Auditable { public static function bootAuditable(): void { - static::created(fn (Model $model) => $model->recordAudit('created', null, $model->auditableAttributes())); + static::created(fn (self $model) => $model->recordAudit('created', null, $model->auditableAttributes())); - static::updated(function (Model $model): void { + static::updated(function (self $model): void { $changed = array_keys($model->getChanges()); $keys = array_diff($changed, $model->auditExcluded()); if ($keys === []) { @@ -31,7 +30,7 @@ public static function bootAuditable(): void $model->recordAudit('updated', $old, $new); }); - static::deleted(fn (Model $model) => $model->recordAudit('deleted', $model->auditableAttributes(), null)); + static::deleted(fn (self $model) => $model->recordAudit('deleted', $model->auditableAttributes(), null)); } /** Champs exclus du journal (secrets et bruit). */ diff --git a/app/Rules/UniqueMatricule.php b/app/Rules/UniqueMatricule.php index c9c7de95..b186f315 100644 --- a/app/Rules/UniqueMatricule.php +++ b/app/Rules/UniqueMatricule.php @@ -11,9 +11,9 @@ class UniqueMatricule implements ValidationRule { protected MatriculeService $matriculeService; - protected ?int $ignoreId = null; + protected ?string $ignoreId = null; - public function __construct(?int $ignoreId = null) + public function __construct(?string $ignoreId = null) { $this->matriculeService = app(MatriculeService::class); $this->ignoreId = $ignoreId; diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 5eedc9c6..70340452 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -492,144 +492,24 @@ parameters: count: 2 path: app/Models/AcademicPeriod.php - - - message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Model\:\:auditExcluded\(\)\.$#' - identifier: method.notFound - count: 1 - path: app/Models/CalendarEvent.php - - - - message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Model\:\:auditableAttributes\(\)\.$#' - identifier: method.notFound - count: 2 - path: app/Models/CalendarEvent.php - - - - message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Model\:\:recordAudit\(\)\.$#' - identifier: method.notFound - count: 3 - path: app/Models/CalendarEvent.php - - message: '#^Match arm comparison between ''BANK'' and ''BANK'' is always true\.$#' identifier: match.alwaysTrue count: 1 path: app/Models/CashAccount.php - - - message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Model\:\:auditExcluded\(\)\.$#' - identifier: method.notFound - count: 1 - path: app/Models/Enrollment.php - - - - message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Model\:\:auditableAttributes\(\)\.$#' - identifier: method.notFound - count: 2 - path: app/Models/Enrollment.php - - - - message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Model\:\:recordAudit\(\)\.$#' - identifier: method.notFound - count: 3 - path: app/Models/Enrollment.php - - - - message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Model\:\:auditExcluded\(\)\.$#' - identifier: method.notFound - count: 1 - path: app/Models/Grade.php - - - - message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Model\:\:auditableAttributes\(\)\.$#' - identifier: method.notFound - count: 2 - path: app/Models/Grade.php - - - - message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Model\:\:recordAudit\(\)\.$#' - identifier: method.notFound - count: 3 - path: app/Models/Grade.php - - - - message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Model\:\:auditExcluded\(\)\.$#' - identifier: method.notFound - count: 1 - path: app/Models/ReportCard.php - - - - message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Model\:\:auditableAttributes\(\)\.$#' - identifier: method.notFound - count: 2 - path: app/Models/ReportCard.php - - - - message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Model\:\:recordAudit\(\)\.$#' - identifier: method.notFound - count: 3 - path: app/Models/ReportCard.php - - - - message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Model\:\:auditExcluded\(\)\.$#' - identifier: method.notFound - count: 1 - path: app/Models/Student.php - - - - message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Model\:\:auditableAttributes\(\)\.$#' - identifier: method.notFound - count: 2 - path: app/Models/Student.php - - - - message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Model\:\:recordAudit\(\)\.$#' - identifier: method.notFound - count: 3 - path: app/Models/Student.php - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$name\.$#' identifier: property.notFound count: 1 path: app/Models/User.php - - - message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Model\:\:auditExcluded\(\)\.$#' - identifier: method.notFound - count: 1 - path: app/Models/User.php - - - - message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Model\:\:auditableAttributes\(\)\.$#' - identifier: method.notFound - count: 2 - path: app/Models/User.php - - - - message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Model\:\:recordAudit\(\)\.$#' - identifier: method.notFound - count: 3 - path: app/Models/User.php - - message: '#^Cannot access property \$natricule on class\-string\|object\.$#' identifier: property.nonObject count: 1 path: app/Models/User.php - - - message: '#^Result of && is always false\.$#' - identifier: booleanAnd.alwaysFalse - count: 1 - path: app/Rules/UniqueMatricule.php - - - - message: '#^Strict comparison using \=\=\= between string and int\|null will always evaluate to false\.$#' - identifier: identical.alwaysFalse - count: 1 - path: app/Rules/UniqueMatricule.php - - message: '#^Offset 1 on array\{list\, list\\} on left side of \?\? always exists and is not nullable\.$#' identifier: nullCoalesce.offset From 8d52d6fc6fd846f29aff2de6ee34d58e5b7dd9c5 Mon Sep 17 00:00:00 2001 From: horacioskrp Date: Fri, 13 Nov 2026 14:21:38 +0000 Subject: [PATCH 05/12] =?UTF-8?q?fix(phpstan):=20@mixin=20sur=20les=20reso?= =?UTF-8?q?urces=20API=20+=20type=20accessor=20name=20(124=20=E2=86=92=208?= =?UTF-8?q?7)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Resources/Api/CalendarEventResource.php | 4 + app/Http/Resources/Api/ReportCardResource.php | 4 + app/Http/Resources/Api/StudentResource.php | 4 + app/Models/User.php | 4 +- phpstan-baseline.neon | 202 +----------------- 5 files changed, 17 insertions(+), 201 deletions(-) diff --git a/app/Http/Resources/Api/CalendarEventResource.php b/app/Http/Resources/Api/CalendarEventResource.php index cc3b310a..6a17f04f 100644 --- a/app/Http/Resources/Api/CalendarEventResource.php +++ b/app/Http/Resources/Api/CalendarEventResource.php @@ -2,9 +2,13 @@ namespace App\Http\Resources\Api; +use App\Models\CalendarEvent; use Illuminate\Http\Request; use Illuminate\Http\Resources\Json\JsonResource; +/** + * @mixin CalendarEvent + */ class CalendarEventResource extends JsonResource { public function toArray(Request $request): array diff --git a/app/Http/Resources/Api/ReportCardResource.php b/app/Http/Resources/Api/ReportCardResource.php index 72db4cbb..03014029 100644 --- a/app/Http/Resources/Api/ReportCardResource.php +++ b/app/Http/Resources/Api/ReportCardResource.php @@ -2,9 +2,13 @@ namespace App\Http\Resources\Api; +use App\Models\ReportCard; use Illuminate\Http\Request; use Illuminate\Http\Resources\Json\JsonResource; +/** + * @mixin ReportCard + */ class ReportCardResource extends JsonResource { public function toArray(Request $request): array diff --git a/app/Http/Resources/Api/StudentResource.php b/app/Http/Resources/Api/StudentResource.php index c44af472..74558f1a 100644 --- a/app/Http/Resources/Api/StudentResource.php +++ b/app/Http/Resources/Api/StudentResource.php @@ -2,9 +2,13 @@ namespace App\Http\Resources\Api; +use App\Models\Student; use Illuminate\Http\Request; use Illuminate\Http\Resources\Json\JsonResource; +/** + * @mixin Student + */ class StudentResource extends JsonResource { public function toArray(Request $request): array diff --git a/app/Models/User.php b/app/Models/User.php index c8916375..d5585de6 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -98,11 +98,13 @@ protected function casts(): array /** * Get the user's full name + * + * @return Attribute */ protected function name(): Attribute { return Attribute::make( - get: fn () => "{$this->firstname} {$this->lastname}", + get: fn (): string => "{$this->firstname} {$this->lastname}", ); } diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 70340452..a1a980ee 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -48,12 +48,6 @@ parameters: count: 1 path: app/Http/Controllers/Api/V1/ChildrenController.php - - - message: '#^Access to an undefined property App\\Models\\User\:\:\$name\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Archives/ArchiveController.php - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$name\.$#' identifier: property.notFound @@ -66,12 +60,6 @@ parameters: count: 1 path: app/Http/Controllers/Archives/ArchiveController.php - - - message: '#^Parameter \#1 \$callback of method Illuminate\\Pagination\\AbstractPaginator\\:\:through\(\) contains unresolvable type\.$#' - identifier: argument.unresolvableType - count: 1 - path: app/Http/Controllers/Archives/ArchiveController.php - - message: '#^Method Illuminate\\Database\\Eloquent\\Builder\\:\:withSum\(\) invoked with 3 parameters, 2 required\.$#' identifier: arguments.count @@ -120,12 +108,6 @@ parameters: count: 1 path: app/Http/Controllers/Eleves/EnrollmentController.php - - - message: '#^Access to an undefined property App\\Models\\User\:\:\$name\.$#' - identifier: property.notFound - count: 2 - path: app/Http/Controllers/Eleves/StudentController.php - - message: '#^Parameter \#1 \$boolean of function abort_unless expects bool, App\\Models\\AcademicYear\|null given\.$#' identifier: argument.type @@ -138,18 +120,6 @@ parameters: count: 1 path: app/Http/Controllers/Eleves/StudentController.php - - - message: '#^Parameter \#1 \$callback of method Illuminate\\Database\\Eloquent\\Collection\\:\:map\(\) contains unresolvable type\.$#' - identifier: argument.unresolvableType - count: 1 - path: app/Http/Controllers/Eleves/StudentController.php - - - - message: '#^Parameter \#1 \$callback of method Illuminate\\Database\\Eloquent\\Collection\\:\:map\(\) contains unresolvable type\.$#' - identifier: argument.unresolvableType - count: 1 - path: app/Http/Controllers/Eleves/StudentController.php - - message: '#^Access to an undefined property App\\Models\\Enrollment\:\:\$birth_date\.$#' identifier: property.notFound @@ -216,36 +186,12 @@ parameters: count: 1 path: app/Http/Controllers/Eleves/StudentStatsController.php - - - message: '#^Access to an undefined property App\\Models\\User\:\:\$name\.$#' - identifier: property.notFound - count: 3 - path: app/Http/Controllers/Eleves/TimetableController.php - - - - message: '#^Access to an undefined property App\\Models\\User\|Illuminate\\Database\\Eloquent\\Collection\\:\:\$name\.$#' - identifier: property.notFound - count: 2 - path: app/Http/Controllers/Eleves/TimetableController.php - - message: '#^Method App\\Http\\Controllers\\Eleves\\TimetableController\:\:buildScheduleGrid\(\) should return array\{timeRanges\: Illuminate\\Support\\Collection\, grid\: array\\>\} but returns array\{timeRanges\: Illuminate\\Support\\Collection\, grid\: array\, App\\Models\\TimetableSlot\>\>\}\.$#' identifier: return.type count: 1 path: app/Http/Controllers/Eleves/TimetableController.php - - - message: '#^Parameter \#1 \$callback of method Illuminate\\Database\\Eloquent\\Collection\\:\:map\(\) contains unresolvable type\.$#' - identifier: argument.unresolvableType - count: 1 - path: app/Http/Controllers/Eleves/TimetableController.php - - - - message: '#^Parameter \#1 \$callback of method Illuminate\\Database\\Eloquent\\Collection\\:\:map\(\) contains unresolvable type\.$#' - identifier: argument.unresolvableType - count: 1 - path: app/Http/Controllers/Eleves/TimetableController.php - - message: '#^Access to an undefined property App\\Models\\OfficialExam\:\:\$admis_count\.$#' identifier: property.notFound @@ -276,24 +222,12 @@ parameters: count: 1 path: app/Http/Controllers/Notes/NoteReclamationController.php - - - message: '#^Access to an undefined property App\\Models\\User\:\:\$name\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Parametres/BackupController.php - - message: '#^Offset ''media'' on array\{format\: string, tables\: int, rows\?\: int\} in isset\(\) does not exist\.$#' identifier: isset.offset count: 1 path: app/Http/Controllers/Parametres/BackupController.php - - - message: '#^Parameter \#1 \$callback of method Illuminate\\Database\\Eloquent\\Collection\\:\:map\(\) contains unresolvable type\.$#' - identifier: argument.unresolvableType - count: 1 - path: app/Http/Controllers/Parametres/BackupController.php - - message: '#^Parameter \#1 \$boolean of function abort_unless expects bool, App\\Models\\School\|null given\.$#' identifier: argument.type @@ -306,18 +240,6 @@ parameters: count: 1 path: app/Http/Controllers/Parametres/DocumentHeaderController.php - - - message: '#^Access to an undefined property App\\Models\\User\:\:\$name\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Parametres/DocumentTemplateController.php - - - - message: '#^Parameter \#1 \$callback of method Illuminate\\Pagination\\AbstractPaginator\\:\:through\(\) contains unresolvable type\.$#' - identifier: argument.unresolvableType - count: 1 - path: app/Http/Controllers/Parametres/DocumentTemplateController.php - - message: '#^Access to an undefined property App\\Models\\FeeStructure\:\:\$installments\.$#' identifier: property.notFound @@ -355,137 +277,17 @@ parameters: path: app/Http/Controllers/Rh/UserPayrollController.php - - message: '#^Access to an undefined property App\\Http\\Resources\\Api\\CalendarEventResource\:\:\$all_day\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Resources/Api/CalendarEventResource.php - - - - message: '#^Access to an undefined property App\\Http\\Resources\\Api\\CalendarEventResource\:\:\$end_date\.$#' - identifier: property.notFound + message: '#^Using nullsafe method call on non\-nullable type Carbon\\CarbonImmutable\. Use \-\> instead\.$#' + identifier: nullsafe.neverNull count: 1 path: app/Http/Resources/Api/CalendarEventResource.php - - - message: '#^Access to an undefined property App\\Http\\Resources\\Api\\CalendarEventResource\:\:\$end_time\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Resources/Api/CalendarEventResource.php - - - - message: '#^Access to an undefined property App\\Http\\Resources\\Api\\CalendarEventResource\:\:\$id\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Resources/Api/CalendarEventResource.php - - - - message: '#^Access to an undefined property App\\Http\\Resources\\Api\\CalendarEventResource\:\:\$start_date\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Resources/Api/CalendarEventResource.php - - - - message: '#^Access to an undefined property App\\Http\\Resources\\Api\\CalendarEventResource\:\:\$start_time\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Resources/Api/CalendarEventResource.php - - - - message: '#^Access to an undefined property App\\Http\\Resources\\Api\\CalendarEventResource\:\:\$title\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Resources/Api/CalendarEventResource.php - - - - message: '#^Access to an undefined property App\\Http\\Resources\\Api\\CalendarEventResource\:\:\$type\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Resources/Api/CalendarEventResource.php - - - - message: '#^Access to an undefined property App\\Http\\Resources\\Api\\ReportCardResource\:\:\$average\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Resources/Api/ReportCardResource.php - - - - message: '#^Access to an undefined property App\\Http\\Resources\\Api\\ReportCardResource\:\:\$id\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Resources/Api/ReportCardResource.php - - - - message: '#^Access to an undefined property App\\Http\\Resources\\Api\\ReportCardResource\:\:\$locked_at\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Resources/Api/ReportCardResource.php - - - - message: '#^Access to an undefined property App\\Http\\Resources\\Api\\ReportCardResource\:\:\$mention\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Resources/Api/ReportCardResource.php - - - - message: '#^Access to an undefined property App\\Http\\Resources\\Api\\ReportCardResource\:\:\$rank\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Resources/Api/ReportCardResource.php - - - - message: '#^Access to an undefined property App\\Http\\Resources\\Api\\ReportCardResource\:\:\$reference\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Resources/Api/ReportCardResource.php - - - - message: '#^Access to an undefined property App\\Http\\Resources\\Api\\StudentResource\:\:\$enrollments\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Resources/Api/StudentResource.php - - - - message: '#^Access to an undefined property App\\Http\\Resources\\Api\\StudentResource\:\:\$firstname\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Resources/Api/StudentResource.php - - - - message: '#^Access to an undefined property App\\Http\\Resources\\Api\\StudentResource\:\:\$gender\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Resources/Api/StudentResource.php - - - - message: '#^Access to an undefined property App\\Http\\Resources\\Api\\StudentResource\:\:\$id\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Resources/Api/StudentResource.php - - - - message: '#^Access to an undefined property App\\Http\\Resources\\Api\\StudentResource\:\:\$lastname\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Resources/Api/StudentResource.php - - - - message: '#^Access to an undefined property App\\Http\\Resources\\Api\\StudentResource\:\:\$matricule\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Resources/Api/StudentResource.php - - message: '#^Access to an undefined property App\\Http\\Resources\\Api\\StudentResource\:\:\$pivot\.$#' identifier: property.notFound count: 1 path: app/Http/Resources/Api/StudentResource.php - - - message: '#^Call to an undefined method App\\Http\\Resources\\Api\\StudentResource\:\:relationLoaded\(\)\.$#' - identifier: method.notFound - count: 1 - path: app/Http/Resources/Api/StudentResource.php - - message: '#^Method App\\Models\\AcademicPeriod\:\:forClassType\(\) should return Illuminate\\Support\\Collection\ but returns Illuminate\\Database\\Eloquent\\Collection\\.$#' identifier: return.type From 87f21e17937b18344d0c3896a7b83ca59adf33eb Mon Sep 17 00:00:00 2001 From: horacioskrp Date: Fri, 13 Nov 2026 14:24:50 +0000 Subject: [PATCH 06/12] =?UTF-8?q?fix(phpstan):=20relation=20installments?= =?UTF-8?q?=20typ=C3=A9e,=20types=20de=20retour=20forClassType=20&=20creat?= =?UTF-8?q?e=20(87=20=E2=86=92=2081)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Notes/NoteReclamationController.php | 2 +- app/Models/AcademicPeriod.php | 4 ++-- app/Models/FeeStructure.php | 5 +++- phpstan-baseline.neon | 24 ------------------- 4 files changed, 7 insertions(+), 28 deletions(-) diff --git a/app/Http/Controllers/Notes/NoteReclamationController.php b/app/Http/Controllers/Notes/NoteReclamationController.php index dc446abf..43db1d88 100644 --- a/app/Http/Controllers/Notes/NoteReclamationController.php +++ b/app/Http/Controllers/Notes/NoteReclamationController.php @@ -55,7 +55,7 @@ public function index(Request $request): Response ]); } - public function create(Request $request): Response + public function create(Request $request): Response|RedirectResponse { abort_unless($request->user()->can('create_note_reclamations'), 403); diff --git a/app/Models/AcademicPeriod.php b/app/Models/AcademicPeriod.php index 77d7674d..02cacda9 100644 --- a/app/Models/AcademicPeriod.php +++ b/app/Models/AcademicPeriod.php @@ -16,12 +16,12 @@ namespace App\Models; +use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Concerns\HasUuids; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasMany; -use Illuminate\Support\Collection; class AcademicPeriod extends Model { @@ -81,7 +81,7 @@ public function evaluations(): HasMany * Périodes applicables à un type de classe pour une année donnée. * Priorité aux périodes spécifiques au type ; sinon, périodes globales (class_type_id null). * - * @return Collection + * @return Collection */ public static function forClassType(?string $academicYearId, ?string $classTypeId) { diff --git a/app/Models/FeeStructure.php b/app/Models/FeeStructure.php index 50802659..58785a37 100644 --- a/app/Models/FeeStructure.php +++ b/app/Models/FeeStructure.php @@ -20,6 +20,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\Relations\HasMany; class FeeStructure extends Model { @@ -68,8 +69,10 @@ public function classroom(): BelongsTo /** * Get the installments for the fee structure. + * + * @return HasMany */ - public function installments() + public function installments(): HasMany { return $this->hasMany(Installment::class); } diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index a1a980ee..d5c53e4b 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -216,12 +216,6 @@ parameters: count: 1 path: app/Http/Controllers/Notes/BulletinController.php - - - message: '#^Method App\\Http\\Controllers\\Notes\\NoteReclamationController\:\:create\(\) should return Inertia\\Response but returns Illuminate\\Http\\RedirectResponse\.$#' - identifier: return.type - count: 1 - path: app/Http/Controllers/Notes/NoteReclamationController.php - - message: '#^Offset ''media'' on array\{format\: string, tables\: int, rows\?\: int\} in isset\(\) does not exist\.$#' identifier: isset.offset @@ -240,18 +234,6 @@ parameters: count: 1 path: app/Http/Controllers/Parametres/DocumentHeaderController.php - - - message: '#^Access to an undefined property App\\Models\\FeeStructure\:\:\$installments\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Parametres/FeeStructureController.php - - - - message: '#^Relation ''installments'' is not found in App\\Models\\FeeStructure model\.$#' - identifier: larastan.relationExistence - count: 2 - path: app/Http/Controllers/Parametres/FeeStructureController.php - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$id\.$#' identifier: property.notFound @@ -288,12 +270,6 @@ parameters: count: 1 path: app/Http/Resources/Api/StudentResource.php - - - message: '#^Method App\\Models\\AcademicPeriod\:\:forClassType\(\) should return Illuminate\\Support\\Collection\ but returns Illuminate\\Database\\Eloquent\\Collection\\.$#' - identifier: return.type - count: 2 - path: app/Models/AcademicPeriod.php - - message: '#^Match arm comparison between ''BANK'' and ''BANK'' is always true\.$#' identifier: match.alwaysTrue From ec86535139662601ca7074f1d69f2b20bd64b7bd Mon Sep 17 00:00:00 2001 From: horacioskrp Date: Fri, 13 Nov 2026 14:26:54 +0000 Subject: [PATCH 07/12] =?UTF-8?q?fix(phpstan):=20shape=20student=5Fid=20st?= =?UTF-8?q?ring=20+=20abort=5Funless=20explicite=20(81=20=E2=86=92=2070)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/Eleves/StudentController.php | 4 +- .../Parametres/BulletinTemplateController.php | 2 +- .../Parametres/DocumentHeaderController.php | 2 +- app/Services/GradingService.php | 4 +- phpstan-baseline.neon | 42 ------------------- 5 files changed, 6 insertions(+), 48 deletions(-) diff --git a/app/Http/Controllers/Eleves/StudentController.php b/app/Http/Controllers/Eleves/StudentController.php index 0b7ebfbd..f7893beb 100644 --- a/app/Http/Controllers/Eleves/StudentController.php +++ b/app/Http/Controllers/Eleves/StudentController.php @@ -271,13 +271,13 @@ public function changeClass(Request $request, Student $student): RedirectRespons ]); $activeYear = AcademicYear::where('active', true)->first(['id']); - abort_unless($activeYear, 422, 'Aucune année académique active.'); + abort_unless($activeYear !== null, 422, 'Aucune année académique active.'); $enrollment = Enrollment::where('student_id', $student->id) ->where('academic_year_id', $activeYear->id) ->first(); - abort_unless($enrollment, 422, 'Cet élève n\'a pas d\'inscription pour l\'année active.'); + abort_unless($enrollment !== null, 422, 'Cet élève n\'a pas d\'inscription pour l\'année active.'); $enrollment->update(['class_id' => $validated['class_id']]); diff --git a/app/Http/Controllers/Parametres/BulletinTemplateController.php b/app/Http/Controllers/Parametres/BulletinTemplateController.php index 0a362ca1..024894bd 100644 --- a/app/Http/Controllers/Parametres/BulletinTemplateController.php +++ b/app/Http/Controllers/Parametres/BulletinTemplateController.php @@ -87,7 +87,7 @@ private function presets(): array private function activeSchool(): School { $school = School::query()->first(); - abort_unless($school, 404, 'Aucune école configurée.'); + abort_unless($school !== null, 404, 'Aucune école configurée.'); return $school; } diff --git a/app/Http/Controllers/Parametres/DocumentHeaderController.php b/app/Http/Controllers/Parametres/DocumentHeaderController.php index f1d1bc8d..57268cda 100644 --- a/app/Http/Controllers/Parametres/DocumentHeaderController.php +++ b/app/Http/Controllers/Parametres/DocumentHeaderController.php @@ -97,7 +97,7 @@ private function sanitizeLayout(array $layout): array private function activeSchool(): School { $school = School::query()->first(); - abort_unless($school, 404, 'Aucune école configurée.'); + abort_unless($school !== null, 404, 'Aucune école configurée.'); return $school; } diff --git a/app/Services/GradingService.php b/app/Services/GradingService.php index f546dd30..5590bef3 100644 --- a/app/Services/GradingService.php +++ b/app/Services/GradingService.php @@ -424,7 +424,7 @@ public function periodAverageFromEvaluations(string $studentId, string $periodId /** * Attribue des rangs à une liste [{student_id, average}]. * - * @param Collection $rows + * @param Collection $rows * @return Collection */ public function rank(Collection $rows): Collection @@ -469,7 +469,7 @@ private function activeStudentIds(string $classId): Collection /** * Attribue les rangs (1, 1, 3…). Les élèves sans moyenne ne sont pas classés (rang null). * - * @param Collection $rows + * @param Collection $rows * @return Collection */ private function assignRanks(Collection $rows): Collection diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index d5c53e4b..80fd78c9 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -108,18 +108,6 @@ parameters: count: 1 path: app/Http/Controllers/Eleves/EnrollmentController.php - - - message: '#^Parameter \#1 \$boolean of function abort_unless expects bool, App\\Models\\AcademicYear\|null given\.$#' - identifier: argument.type - count: 1 - path: app/Http/Controllers/Eleves/StudentController.php - - - - message: '#^Parameter \#1 \$boolean of function abort_unless expects bool, App\\Models\\Enrollment\|null given\.$#' - identifier: argument.type - count: 1 - path: app/Http/Controllers/Eleves/StudentController.php - - message: '#^Access to an undefined property App\\Models\\Enrollment\:\:\$birth_date\.$#' identifier: property.notFound @@ -204,12 +192,6 @@ parameters: count: 1 path: app/Http/Controllers/Examens/OfficialExamController.php - - - message: '#^Parameter \#1 \$rows of method App\\Services\\GradingService\:\:rank\(\) expects Illuminate\\Support\\Collection\, Illuminate\\Support\\Collection\ given\.$#' - identifier: argument.type - count: 1 - path: app/Http/Controllers/Notes/BulletinController.php - - message: '#^Parameter \#2 \$type of static method App\\Models\\GradingConfig\:\:resolveOrDefault\(\) expects App\\Models\\ClassroomType\|null, Illuminate\\Database\\Eloquent\\Model\|null given\.$#' identifier: argument.type @@ -222,18 +204,6 @@ parameters: count: 1 path: app/Http/Controllers/Parametres/BackupController.php - - - message: '#^Parameter \#1 \$boolean of function abort_unless expects bool, App\\Models\\School\|null given\.$#' - identifier: argument.type - count: 1 - path: app/Http/Controllers/Parametres/BulletinTemplateController.php - - - - message: '#^Parameter \#1 \$boolean of function abort_unless expects bool, App\\Models\\School\|null given\.$#' - identifier: argument.type - count: 1 - path: app/Http/Controllers/Parametres/DocumentHeaderController.php - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$id\.$#' identifier: property.notFound @@ -324,12 +294,6 @@ parameters: count: 1 path: app/Services/DocumentRenderer.php - - - message: '#^Parameter \#1 \$rows of method App\\Services\\GradingService\:\:assignRanks\(\) expects Illuminate\\Support\\Collection\, Illuminate\\Support\\Collection\ given\.$#' - identifier: argument.type - count: 2 - path: app/Services/GradingService.php - - message: '#^Access to an undefined property App\\Models\\Payslip\:\:\$d\.$#' identifier: property.notFound @@ -366,12 +330,6 @@ parameters: count: 1 path: app/Services/ReportCardBuilder.php - - - message: '#^Parameter \#1 \$rows of method App\\Services\\GradingService\:\:rank\(\) expects Illuminate\\Support\\Collection\, Illuminate\\Support\\Collection\ given\.$#' - identifier: argument.type - count: 4 - path: app/Services/ReportCardBuilder.php - - message: '#^Parameter \#2 \$type of static method App\\Models\\BulletinTemplate\:\:resolveOrDefault\(\) expects App\\Models\\ClassroomType\|null, Illuminate\\Database\\Eloquent\\Model\|null given\.$#' identifier: argument.type From f7fe1c2ee5785ba1125d38af21e85446835476b5 Mon Sep 17 00:00:00 2001 From: horacioskrp Date: Fri, 13 Nov 2026 14:29:28 +0000 Subject: [PATCH 08/12] =?UTF-8?q?fix(phpstan):=20annote=20relations=20manq?= =?UTF-8?q?u=C3=A9es=20employeeProfile=20&=20classroomType=20(70=20?= =?UTF-8?q?=E2=86=92=2063)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Models/Classroom.php | 2 ++ app/Models/User.php | 6 +++++- phpstan-baseline.neon | 42 ---------------------------------------- 3 files changed, 7 insertions(+), 43 deletions(-) diff --git a/app/Models/Classroom.php b/app/Models/Classroom.php index 1b26d65c..43d98075 100644 --- a/app/Models/Classroom.php +++ b/app/Models/Classroom.php @@ -55,6 +55,8 @@ public function type(): BelongsTo /** * Alias for type() relationship. + * + * @return BelongsTo */ public function classroomType(): BelongsTo { diff --git a/app/Models/User.php b/app/Models/User.php index d5585de6..c70b3d22 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -108,7 +108,11 @@ protected function name(): Attribute ); } - /** Profil RH (paie) de l'utilisateur, s'il est employé. */ + /** + * Profil RH (paie) de l'utilisateur, s'il est employé. + * + * @return HasOne + */ public function employeeProfile(): HasOne { return $this->hasOne(EmployeeProfile::class); diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 80fd78c9..4bdaf61d 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -192,42 +192,12 @@ parameters: count: 1 path: app/Http/Controllers/Examens/OfficialExamController.php - - - message: '#^Parameter \#2 \$type of static method App\\Models\\GradingConfig\:\:resolveOrDefault\(\) expects App\\Models\\ClassroomType\|null, Illuminate\\Database\\Eloquent\\Model\|null given\.$#' - identifier: argument.type - count: 1 - path: app/Http/Controllers/Notes/BulletinController.php - - message: '#^Offset ''media'' on array\{format\: string, tables\: int, rows\?\: int\} in isset\(\) does not exist\.$#' identifier: isset.offset count: 1 path: app/Http/Controllers/Parametres/BackupController.php - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$id\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Rh/EmployeeAllowanceController.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$employee_number\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Rh/UserPayrollController.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$id\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Rh/UserPayrollController.php - - - - message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Model\:\:payslips\(\)\.$#' - identifier: method.notFound - count: 1 - path: app/Http/Controllers/Rh/UserPayrollController.php - - message: '#^Using nullsafe method call on non\-nullable type Carbon\\CarbonImmutable\. Use \-\> instead\.$#' identifier: nullsafe.neverNull @@ -330,18 +300,6 @@ parameters: count: 1 path: app/Services/ReportCardBuilder.php - - - message: '#^Parameter \#2 \$type of static method App\\Models\\BulletinTemplate\:\:resolveOrDefault\(\) expects App\\Models\\ClassroomType\|null, Illuminate\\Database\\Eloquent\\Model\|null given\.$#' - identifier: argument.type - count: 1 - path: app/Services/ReportCardBuilder.php - - - - message: '#^Parameter \#2 \$type of static method App\\Models\\GradingConfig\:\:resolveOrDefault\(\) expects App\\Models\\ClassroomType\|null, Illuminate\\Database\\Eloquent\\Model\|null given\.$#' - identifier: argument.type - count: 1 - path: app/Services/ReportCardBuilder.php - - message: '#^Called ''count'' on Laravel collection, but could have been retrieved as a query\.$#' identifier: larastan.noUnnecessaryCollectionCall From b3b1cc6f43c36db42aa41522beaa90dc9fd123fd Mon Sep 17 00:00:00 2001 From: horacioskrp Date: Fri, 13 Nov 2026 14:31:01 +0000 Subject: [PATCH 09/12] =?UTF-8?q?refactor(phpstan):=20retire=20les=20=3F?= =?UTF-8?q?=3F=20d=C3=A9faut=20morts=20sur=20offsets=20toujours=20pr=C3=A9?= =?UTF-8?q?sents=20(63=20=E2=86=92=2056)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Services/BackupService.php | 2 +- app/Services/PayrollService.php | 2 +- app/Services/ReportCardBuilder.php | 2 +- app/Services/StatisticsService.php | 8 +++--- phpstan-baseline.neon | 42 ------------------------------ 5 files changed, 7 insertions(+), 49 deletions(-) diff --git a/app/Services/BackupService.php b/app/Services/BackupService.php index 25f16e8c..0319dc22 100644 --- a/app/Services/BackupService.php +++ b/app/Services/BackupService.php @@ -512,7 +512,7 @@ private function restoreTableBatched(string $table, array $rows): int private function restoreSql(string $contents): array { preg_match_all('/INSERT\s+INTO\s+"([^"]+)"/i', $contents, $matches); - $tables = array_values(array_unique($matches[1] ?? [])); + $tables = array_values(array_unique($matches[1])); try { DB::transaction(function () use ($contents, $tables): void { diff --git a/app/Services/PayrollService.php b/app/Services/PayrollService.php index d2f6b0ce..fcd0ea5b 100644 --- a/app/Services/PayrollService.php +++ b/app/Services/PayrollService.php @@ -137,7 +137,7 @@ private function buildLines(EmployeeProfile $employee, $components, int $month, $grossEarnings = 0.0; foreach ($lines as $l) { if (($l['type'] ?? '') !== SalaryComponent::DEDUCTION) { - $grossEarnings += (float) ($l['amount'] ?? 0); + $grossEarnings += (float) ($l['amount']); } } $statutory = $this->statutory($grossEarnings, $settings); diff --git a/app/Services/ReportCardBuilder.php b/app/Services/ReportCardBuilder.php index 99da9705..08c9e064 100644 --- a/app/Services/ReportCardBuilder.php +++ b/app/Services/ReportCardBuilder.php @@ -171,7 +171,7 @@ public function build( 'moyenne' => $cell['moy'], 'points' => $points, 'definitive' => $points, - 'by_type' => $cell['by_type'] ?? [], + 'by_type' => $cell['by_type'], 'rang' => $subjectRanks[$cs->id]->get($student->id)['rank'] ?? null, 'appreciation' => $comments[$student->id.'|'.$cs->id] ?? '', 'teacher' => $teachers[$cs->subject_id] ?? '', diff --git a/app/Services/StatisticsService.php b/app/Services/StatisticsService.php index 9d515c80..abe65952 100644 --- a/app/Services/StatisticsService.php +++ b/app/Services/StatisticsService.php @@ -334,10 +334,10 @@ public function successStats(array $filters): array 'moyenne_generale' => $rcStats->avg_moy !== null ? round((float) $rcStats->avg_moy, 2) : null, 'pass_rate' => $rcTotal > 0 ? round(((int) $rcStats->pass_count) / $rcTotal * 100, 1) : 0.0, 'mentions' => [ - 'passable' => (int) ($mentions['passable'] ?? 0), - 'assez_bien' => (int) ($mentions['assez_bien'] ?? 0), - 'bien' => (int) ($mentions['bien'] ?? 0), - 'tres_bien' => (int) ($mentions['tres_bien'] ?? 0), + 'passable' => (int) ($mentions['passable']), + 'assez_bien' => (int) ($mentions['assez_bien']), + 'bien' => (int) ($mentions['bien']), + 'tres_bien' => (int) ($mentions['tres_bien']), ], 'exams' => $exams, 'exams_summary' => [ diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 4bdaf61d..0d6a72a5 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -228,12 +228,6 @@ parameters: count: 1 path: app/Models/User.php - - - message: '#^Offset 1 on array\{list\, list\\} on left side of \?\? always exists and is not nullable\.$#' - identifier: nullCoalesce.offset - count: 1 - path: app/Services/BackupService.php - - message: '#^Parameter &\$temps by\-ref type of method App\\Services\\BackupService\:\:addDiskToZip\(\) expects array\, array\ given\.$#' identifier: parameterByRef.type @@ -282,54 +276,18 @@ parameters: count: 1 path: app/Services/PayrollService.php - - - message: '#^Offset ''amount'' on array\{code\: mixed, label\: mixed, type\: mixed, amount\: float, reason\?\: string\|null, origin\: non\-falsy\-string\} on left side of \?\? always exists and is not nullable\.$#' - identifier: nullCoalesce.offset - count: 1 - path: app/Services/PayrollService.php - - message: '#^Strict comparison using \!\=\= between mixed and null will always evaluate to true\.$#' identifier: notIdentical.alwaysTrue count: 1 path: app/Services/PayrollService.php - - - message: '#^Offset ''by_type'' on array\{classe\: float\|null, compo\: float\|null, moy\: float\|null, by_type\: array\\} on left side of \?\? always exists and is not nullable\.$#' - identifier: nullCoalesce.offset - count: 1 - path: app/Services/ReportCardBuilder.php - - message: '#^Called ''count'' on Laravel collection, but could have been retrieved as a query\.$#' identifier: larastan.noUnnecessaryCollectionCall count: 1 path: app/Services/StatisticsService.php - - - message: '#^Offset ''assez_bien'' on array\{passable\: int, assez_bien\: int, bien\: int, tres_bien\: int\} on left side of \?\? always exists and is not nullable\.$#' - identifier: nullCoalesce.offset - count: 1 - path: app/Services/StatisticsService.php - - - - message: '#^Offset ''bien'' on array\{passable\: int, assez_bien\: int, bien\: int, tres_bien\: int\} on left side of \?\? always exists and is not nullable\.$#' - identifier: nullCoalesce.offset - count: 1 - path: app/Services/StatisticsService.php - - - - message: '#^Offset ''passable'' on array\{passable\: int, assez_bien\: int, bien\: int, tres_bien\: int\} on left side of \?\? always exists and is not nullable\.$#' - identifier: nullCoalesce.offset - count: 1 - path: app/Services/StatisticsService.php - - - - message: '#^Offset ''tres_bien'' on array\{passable\: int, assez_bien\: int, bien\: int, tres_bien\: int\} on left side of \?\? always exists and is not nullable\.$#' - identifier: nullCoalesce.offset - count: 1 - path: app/Services/StatisticsService.php - - message: '#^Offset int\<20, max\> does not exist on array\{''zéro'', ''un'', ''deux'', ''trois'', ''quatre'', ''cinq'', ''six'', ''sept'', \.\.\.\}\.$#' identifier: offsetAccess.notFound From 793339680681d78568b1db0acc3300308cd71c4c Mon Sep 17 00:00:00 2001 From: horacioskrp Date: Fri, 13 Nov 2026 14:39:25 +0000 Subject: [PATCH 10/12] =?UTF-8?q?fix(phpstan):=20corrige=20withSum,=20temp?= =?UTF-8?q?nam=20&=20gardes=20mortes=20(56=20=E2=86=92=2047)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CashAccountController: la contrainte INCOME du withSum était ignorée (closure passée en 3e arg positionnel) → syntaxe tableau, total_income ne compte plus que les transactions INCOME. - BackupService: garde l'échec de tempnam() (string|false) et retire le `! empty($chunk)` mort sur array_chunk. - BulletinRenderer / PayrollService: retire le `!== null` redondant après isset(). - FrenchNumberSpeller: remplace le fallback hors-plage UNITS[$n] par une LogicException explicite. - StudentTestSeeder: resolveAcademicYear() ne retourne jamais null, type de retour non-nullable + condition d'inscription simplifiée. - HasMatricule / ArchiveController: type `self` sur la closure creating, retrait de la constante TYPES morte. - Baseline régénérée : 47 faux positifs résiduels (alias de requêtes selectRaw/withCount, relations Spatie, auth multi-modèle Sanctum). --- .../Archives/ArchiveController.php | 2 - .../Comptabilite/CashAccountController.php | 2 +- app/Services/BackupService.php | 11 ++-- app/Services/BulletinRenderer.php | 2 +- app/Services/PayrollService.php | 2 +- app/Support/FrenchNumberSpeller.php | 2 +- app/Traits/HasMatricule.php | 2 +- database/seeders/StudentTestSeeder.php | 8 ++- phpstan-baseline.neon | 60 +------------------ 9 files changed, 19 insertions(+), 72 deletions(-) diff --git a/app/Http/Controllers/Archives/ArchiveController.php b/app/Http/Controllers/Archives/ArchiveController.php index 6eadf886..956e55fb 100644 --- a/app/Http/Controllers/Archives/ArchiveController.php +++ b/app/Http/Controllers/Archives/ArchiveController.php @@ -16,8 +16,6 @@ class ArchiveController extends Controller { - private const TYPES = ['student' => Student::class, 'classroom' => Classroom::class]; - private function authorizeManage(Request $request): void { // Socle de sécurité : les routes portent déjà la permission fine (create/edit/ diff --git a/app/Http/Controllers/Comptabilite/CashAccountController.php b/app/Http/Controllers/Comptabilite/CashAccountController.php index e982222a..a70b40df 100644 --- a/app/Http/Controllers/Comptabilite/CashAccountController.php +++ b/app/Http/Controllers/Comptabilite/CashAccountController.php @@ -15,7 +15,7 @@ class CashAccountController extends Controller public function index(): Response { $accounts = CashAccount::withCount('payments') - ->withSum('transactions as total_income', 'amount', fn ($q) => $q->where('type', 'INCOME')) + ->withSum(['transactions as total_income' => fn ($q) => $q->where('type', 'INCOME')], 'amount') ->orderBy('type') ->orderBy('name') ->get(); diff --git a/app/Services/BackupService.php b/app/Services/BackupService.php index 0319dc22..7f009fe1 100644 --- a/app/Services/BackupService.php +++ b/app/Services/BackupService.php @@ -229,6 +229,9 @@ private function addDiskToZip(\ZipArchive $zip, string $diskName, string $prefix $zip->addFile($local, "{$prefix}/{$rel}"); } else { $t = tempnam(sys_get_temp_dir(), 'med_'); + if ($t === false) { + continue; // impossible de créer le fichier temporaire : on saute ce média + } file_put_contents($t, $disk->get($rel)); $temps[] = $t; $zip->addFile($t, "{$prefix}/{$rel}"); @@ -269,7 +272,7 @@ private function writeDump(string $format, string $tmp): string * (json[.gz], sql[.gz] ou dump PostgreSQL). Une sauvegarde de sécurité * (JSON) est générée au préalable. * - * @return array{format:string, tables:int, rows?:int} + * @return array{format:string, tables:int, rows?:int, media?:int} */ public function restore(UploadedFile $file, ?string $onlyTable = null): array { @@ -496,10 +499,8 @@ private function restoreTableBatched(string $table, array $rows): int $total = 0; foreach (array_chunk($rows, 500) as $chunk) { - if (! empty($chunk)) { - DB::transaction(fn () => DB::table($table)->insert($chunk)); - $total += count($chunk); - } + DB::transaction(fn () => DB::table($table)->insert($chunk)); + $total += count($chunk); } return $total; diff --git a/app/Services/BulletinRenderer.php b/app/Services/BulletinRenderer.php index 0c00f045..7c0ec693 100644 --- a/app/Services/BulletinRenderer.php +++ b/app/Services/BulletinRenderer.php @@ -235,7 +235,7 @@ private function infoBlock(array $p): string private function footBlock(array $p, array $options): string { $average = $this->num($p['average'] ?? null); - $rank = isset($p['rank']) && $p['rank'] !== null ? e($this->ordinal((int) $p['rank'])) : '—'; + $rank = isset($p['rank']) ? e($this->ordinal((int) $p['rank'])) : '—'; $effectif = e((string) ($p['effectif'] ?? '')); $mention = e($p['mention'] ?? '—'); $obs = e($p['observations'] ?? ''); diff --git a/app/Services/PayrollService.php b/app/Services/PayrollService.php index fcd0ea5b..eaaac199 100644 --- a/app/Services/PayrollService.php +++ b/app/Services/PayrollService.php @@ -197,7 +197,7 @@ private function progressiveTax(float $taxable, array $brackets): float $tax = 0.0; $prev = 0.0; foreach ($brackets as $b) { - $cap = isset($b['up_to']) && $b['up_to'] !== null ? (float) $b['up_to'] : INF; + $cap = isset($b['up_to']) ? (float) $b['up_to'] : INF; $rate = (float) ($b['rate'] ?? 0); if ($taxable <= $prev) { break; diff --git a/app/Support/FrenchNumberSpeller.php b/app/Support/FrenchNumberSpeller.php index f0b8079c..a883b1ee 100644 --- a/app/Support/FrenchNumberSpeller.php +++ b/app/Support/FrenchNumberSpeller.php @@ -138,7 +138,7 @@ private static function tens(int $n, bool $isFinal): string ? ($isFinal ? 'quatre-vingts' : 'quatre-vingt') : 'quatre-vingt-'.self::UNITS[$unit], 90 => 'quatre-vingt-'.self::UNITS[10 + $unit], - default => self::UNITS[$n], // inatteignable, garde-fou + default => throw new \LogicException("Dizaine inattendue: {$base}"), // inatteignable pour n ∈ [20,99] }; } diff --git a/app/Traits/HasMatricule.php b/app/Traits/HasMatricule.php index 0dd83ade..e1f4da0e 100644 --- a/app/Traits/HasMatricule.php +++ b/app/Traits/HasMatricule.php @@ -15,7 +15,7 @@ trait HasMatricule public static function bootHasMatricule() { // Générer le matricule avant la création si absent - static::creating(function ($model) { + static::creating(function (self $model) { if (empty($model->natricule) && method_exists($model, 'generateMatricule')) { $model->natricule = $model->generateMatricule(); } diff --git a/database/seeders/StudentTestSeeder.php b/database/seeders/StudentTestSeeder.php index 8a87fc36..c6a49e65 100644 --- a/database/seeders/StudentTestSeeder.php +++ b/database/seeders/StudentTestSeeder.php @@ -54,10 +54,12 @@ public function run(): void $classroomIds = Classroom::query()->pluck('id')->all(); $enrolledBy = User::query()->value('id'); - $canEnroll = $school && $academicYear && $classroomIds !== []; + // $academicYear est toujours résolu (créé au besoin) ; seules l'école et + // les classes peuvent manquer sur une base vide. + $canEnroll = $school && $classroomIds !== []; if (! $canEnroll) { $this->command->warn( - 'Élèves créés sans inscription : école, année académique active ou classes manquantes.' + 'Élèves créés sans inscription : école ou classes manquantes.' ); } @@ -162,7 +164,7 @@ public function run(): void * Année académique active : on réutilise l'existante, sinon on crée * l'année scolaire courante (développement uniquement). */ - private function resolveAcademicYear(): ?AcademicYear + private function resolveAcademicYear(): AcademicYear { $active = AcademicYear::query()->where('active', true)->first() ?? AcademicYear::query()->latest('start_date')->first(); diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 0d6a72a5..a291b9a6 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -54,18 +54,6 @@ parameters: count: 2 path: app/Http/Controllers/Archives/ArchiveController.php - - - message: '#^Constant App\\Http\\Controllers\\Archives\\ArchiveController\:\:TYPES is unused\.$#' - identifier: classConstant.unused - count: 1 - path: app/Http/Controllers/Archives/ArchiveController.php - - - - message: '#^Method Illuminate\\Database\\Eloquent\\Builder\\:\:withSum\(\) invoked with 3 parameters, 2 required\.$#' - identifier: arguments.count - count: 1 - path: app/Http/Controllers/Comptabilite/CashAccountController.php - - message: '#^Access to an undefined property App\\Models\\ReportCard\:\:\$avg_general\.$#' identifier: property.notFound @@ -192,12 +180,6 @@ parameters: count: 1 path: app/Http/Controllers/Examens/OfficialExamController.php - - - message: '#^Offset ''media'' on array\{format\: string, tables\: int, rows\?\: int\} in isset\(\) does not exist\.$#' - identifier: isset.offset - count: 1 - path: app/Http/Controllers/Parametres/BackupController.php - - message: '#^Using nullsafe method call on non\-nullable type Carbon\\CarbonImmutable\. Use \-\> instead\.$#' identifier: nullsafe.neverNull @@ -222,30 +204,6 @@ parameters: count: 1 path: app/Models/User.php - - - message: '#^Cannot access property \$natricule on class\-string\|object\.$#' - identifier: property.nonObject - count: 1 - path: app/Models/User.php - - - - message: '#^Parameter &\$temps by\-ref type of method App\\Services\\BackupService\:\:addDiskToZip\(\) expects array\, array\ given\.$#' - identifier: parameterByRef.type - count: 1 - path: app/Services/BackupService.php - - - - message: '#^Variable \$chunk in empty\(\) always exists and is not falsy\.$#' - identifier: empty.variable - count: 1 - path: app/Services/BackupService.php - - - - message: '#^Strict comparison using \!\=\= between mixed and null will always evaluate to true\.$#' - identifier: notIdentical.alwaysTrue - count: 1 - path: app/Services/BulletinRenderer.php - - message: '#^Match arm comparison between ''female'' and ''female'' is always true\.$#' identifier: match.alwaysTrue @@ -276,12 +234,6 @@ parameters: count: 1 path: app/Services/PayrollService.php - - - message: '#^Strict comparison using \!\=\= between mixed and null will always evaluate to true\.$#' - identifier: notIdentical.alwaysTrue - count: 1 - path: app/Services/PayrollService.php - - message: '#^Called ''count'' on Laravel collection, but could have been retrieved as a query\.$#' identifier: larastan.noUnnecessaryCollectionCall @@ -289,10 +241,10 @@ parameters: path: app/Services/StatisticsService.php - - message: '#^Offset int\<20, max\> does not exist on array\{''zéro'', ''un'', ''deux'', ''trois'', ''quatre'', ''cinq'', ''six'', ''sept'', \.\.\.\}\.$#' - identifier: offsetAccess.notFound + message: '#^Call to function method_exists\(\) with App\\Models\\User and ''generateMatricule'' will always evaluate to true\.$#' + identifier: function.alreadyNarrowedType count: 1 - path: app/Support/FrenchNumberSpeller.php + path: app/Traits/HasMatricule.php - message: '#^Call to an undefined method Illuminate\\Database\\Schema\\IndexDefinition\:\:index\(\)\.$#' @@ -305,9 +257,3 @@ parameters: identifier: method.notFound count: 1 path: database/migrations/2026_06_14_000011_add_locked_at_to_evaluations_table.php - - - - message: '#^Method Database\\Seeders\\StudentTestSeeder\:\:resolveAcademicYear\(\) never returns null so it can be removed from the return type\.$#' - identifier: return.unusedType - count: 1 - path: database/seeders/StudentTestSeeder.php From 898be3e5bdfd1e9b6a0d41200cee3920ca15acec Mon Sep 17 00:00:00 2001 From: horacioskrp Date: Fri, 13 Nov 2026 15:17:12 +0000 Subject: [PATCH 11/12] =?UTF-8?q?fix(phpstan):=20r=C3=A9sorbe=20les=2047?= =?UTF-8?q?=20derniers=20faux=20positifs,=20supprime=20la=20baseline=20(47?= =?UTF-8?q?=20=E2=86=92=200)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Niveau 5 désormais vert SANS baseline : le fichier phpstan-baseline.neon est supprimé et son include retiré de phpstan.neon. Typage honnête (pas de suppression) : - Portail API (ApiController, AuthController, ChildrenController) : le principal authentifié est Student|Guardian (gardes Sanctum), annoté comme tel → instanceof, collect() et currentAccessToken() se résolvent. - Lignes d'agrégat (Enrollment/StudentStats/Dashboard/Payroll) : résultats selectRaw/jointures typés via variables intermédiaires (object{...}). - OfficialExam : @property-read pour les alias withCount. - User / Student : relation Spatie roles et pivot guardian_student typés. - TimetableController : closure $range et forme de la grille typées. Vrais bugs corrigés au passage : - ArchiveController : le libellé d'archive d'un élève affichait un nom vide (Student n'a pas de colonne « name ») → compose prénom + nom. - Migration evaluations : ->after() était chaîné sur la définition de clé étrangère (ignoré) → réordonné pour positionner la colonne. - Migration matricules : retrait d'un ->unique()->index() sans effet. Code mort retiré (enums/colonnes NOT NULL → arms inatteignables) : CashAccount::typeLabel, HasMatricule::method_exists. DocumentRenderer : genre/date de naissance lus via getAttribute() (mixed) car $student peut être un modèle NON persisté (aperçu/test) dont les colonnes NOT NULL sont nulles — les gardes défensives sont conservées. StatisticsService : unique suppression inline documentée (count() sur GROUP BY + HAVING ; un count() SQL compterait par groupe, pas les groupes). --- app/Http/Controllers/Api/ApiController.php | 4 + .../Controllers/Api/V1/AuthController.php | 6 +- .../Controllers/Api/V1/ChildrenController.php | 1 + .../Archives/ArchiveController.php | 9 +- .../Dashboard/DashboardController.php | 2 + .../Eleves/EnrollmentController.php | 3 + .../Eleves/StudentStatsController.php | 30 +- .../Eleves/TimetableController.php | 8 +- .../Resources/Api/CalendarEventResource.php | 2 +- app/Models/CashAccount.php | 2 +- app/Models/OfficialExam.php | 4 + app/Models/Student.php | 4 + app/Models/User.php | 5 + app/Services/DocumentRenderer.php | 11 +- app/Services/PayrollService.php | 2 + app/Services/StatisticsService.php | 3 + app/Traits/HasMatricule.php | 5 +- ...50000_add_unique_indexes_to_matricules.php | 4 +- ...011_add_locked_at_to_evaluations_table.php | 2 +- phpstan-baseline.neon | 259 ------------------ phpstan.neon | 1 - 21 files changed, 78 insertions(+), 289 deletions(-) delete mode 100644 phpstan-baseline.neon diff --git a/app/Http/Controllers/Api/ApiController.php b/app/Http/Controllers/Api/ApiController.php index 9dd0da72..48783eb4 100644 --- a/app/Http/Controllers/Api/ApiController.php +++ b/app/Http/Controllers/Api/ApiController.php @@ -18,6 +18,9 @@ abstract class ApiController extends Controller */ protected function accessibleStudents(Request $request): Collection { + // Ces routes API authentifient un élève ou un tuteur (gardes Sanctum + // dédiées), jamais le User par défaut supposé par l'analyse statique. + /** @var Student|Guardian|null $user */ $user = $request->user(); if ($user instanceof Student) { @@ -38,6 +41,7 @@ protected function accessibleStudents(Request $request): Collection */ protected function resolveStudent(Request $request, string $studentId): Student { + /** @var Student|Guardian|null $user */ $user = $request->user(); if ($user instanceof Student) { diff --git a/app/Http/Controllers/Api/V1/AuthController.php b/app/Http/Controllers/Api/V1/AuthController.php index 7b8962fe..e8da0508 100644 --- a/app/Http/Controllers/Api/V1/AuthController.php +++ b/app/Http/Controllers/Api/V1/AuthController.php @@ -39,6 +39,8 @@ public function login(LoginRequest $request): JsonResponse public function me(Request $request): JsonResponse { + // Portail parent/élève : le principal authentifié est un tuteur ou un élève. + /** @var Guardian|Student $user */ $user = $request->user(); $type = $user instanceof Guardian ? 'guardian' : 'student'; @@ -47,7 +49,9 @@ public function me(Request $request): JsonResponse public function logout(Request $request): JsonResponse { - $request->user()->currentAccessToken()->delete(); + /** @var Guardian|Student $user */ + $user = $request->user(); + $user->currentAccessToken()->delete(); return response()->json(['message' => 'Déconnecté.']); } diff --git a/app/Http/Controllers/Api/V1/ChildrenController.php b/app/Http/Controllers/Api/V1/ChildrenController.php index 58edffca..b72c98f1 100644 --- a/app/Http/Controllers/Api/V1/ChildrenController.php +++ b/app/Http/Controllers/Api/V1/ChildrenController.php @@ -16,6 +16,7 @@ public function index(Request $request): AnonymousResourceCollection { $withActiveEnrollment = ['enrollments' => fn ($q) => $q->where('status', Enrollment::STATUS_ACTIVE)->with('classroom:id,name')]; + /** @var Student|Guardian $user */ $user = $request->user(); if ($user instanceof Student) { diff --git a/app/Http/Controllers/Archives/ArchiveController.php b/app/Http/Controllers/Archives/ArchiveController.php index 956e55fb..3ead58ee 100644 --- a/app/Http/Controllers/Archives/ArchiveController.php +++ b/app/Http/Controllers/Archives/ArchiveController.php @@ -232,9 +232,12 @@ private function linkLabel(ArchivedDocument $d): ?string return null; } - return match ($d->documentable_type) { - Student::class => 'Élève : '.$d->documentable->name, - Classroom::class => 'Classe : '.$d->documentable->name, + $linked = $d->documentable; + + return match (true) { + // Un élève n'a pas de colonne « name » : on compose nom + prénom. + $linked instanceof Student => 'Élève : '.trim($linked->firstname.' '.$linked->lastname), + $linked instanceof Classroom => 'Classe : '.$linked->name, default => null, }; } diff --git a/app/Http/Controllers/Dashboard/DashboardController.php b/app/Http/Controllers/Dashboard/DashboardController.php index 25545ed9..01dfcb8b 100644 --- a/app/Http/Controllers/Dashboard/DashboardController.php +++ b/app/Http/Controllers/Dashboard/DashboardController.php @@ -307,6 +307,8 @@ public function index(Request $request): Response ->first(['id', 'name']); // Bulletins figés (locked_at) de l'année (et de la période courante le cas échéant). + // Agrégat sans GROUP BY : toujours une ligne, colonnes calculées. + /** @var object{validated: int|string, avg_general: float|int|string|null} $rc */ $rc = ReportCard::query() ->whereNotNull('locked_at') ->when($yearId, fn ($q) => $q->where('academic_year_id', $yearId)) diff --git a/app/Http/Controllers/Eleves/EnrollmentController.php b/app/Http/Controllers/Eleves/EnrollmentController.php index 9fc5fc3c..f2216fa2 100644 --- a/app/Http/Controllers/Eleves/EnrollmentController.php +++ b/app/Http/Controllers/Eleves/EnrollmentController.php @@ -86,6 +86,9 @@ public function index(Request $request): Response ->where('invoices.status', '!=', 'CANCELLED'); $applyFilters($aggQuery); + // Agrégat sans GROUP BY : renvoie toujours exactement une ligne de synthèse + // dont les colonnes sont calculées (invisibles à l'analyse statique). + /** @var object{billed: int|float|string, collected: int|float|string, remaining: int|float|string, unpaid_count: int|float|string, unpaid_amount: int|float|string} $agg */ $agg = $aggQuery->selectRaw(' COALESCE(SUM(invoices.total), 0) as billed, COALESCE(SUM(invoices.amount_paid), 0) as collected, diff --git a/app/Http/Controllers/Eleves/StudentStatsController.php b/app/Http/Controllers/Eleves/StudentStatsController.php index bf611729..410b6322 100644 --- a/app/Http/Controllers/Eleves/StudentStatsController.php +++ b/app/Http/Controllers/Eleves/StudentStatsController.php @@ -8,6 +8,7 @@ use App\Models\Student; use Illuminate\Http\Request; use Illuminate\Support\Carbon; +use Illuminate\Support\Collection; use Illuminate\Support\Facades\DB; use Illuminate\Support\Str; use Inertia\Inertia; @@ -48,7 +49,8 @@ public function index(Request $request): Response ]; // Répartition par nationalité (top 6, cohorte) - $byNationality = Student::query() + /** @var Collection $natRows */ + $natRows = Student::query() ->whereIn('id', $studentIds) ->select('nationality', DB::raw('COUNT(*) as count')) ->whereNotNull('nationality') @@ -56,8 +58,8 @@ public function index(Request $request): Response ->groupBy('nationality') ->orderByDesc('count') ->limit(6) - ->get() - ->map(fn ($r) => ['label' => $r->nationality, 'count' => (int) $r->count]); + ->get(); + $byNationality = $natRows->map(fn ($r) => ['label' => $r->nationality, 'count' => (int) $r->count]); // Répartition par tranche d'âge (cohorte) $brackets = [ @@ -89,6 +91,7 @@ public function index(Request $request): Response // Sur-âge (retard scolaire) : âge de l'élève >= âge attendu de sa classe + 2. $overAgeThreshold = 2; + /** @var Collection $ageRows */ $ageRows = Enrollment::query() ->join('classes', 'classes.id', '=', 'enrollments.class_id') ->join('students', 'students.id', '=', 'enrollments.student_id') @@ -102,7 +105,8 @@ public function index(Request $request): Response // Effectifs par classe (année sélectionnée, scolarité active), enrichis du // cycle, du sexe et de la capacité pour permettre filtre et empilage côté client. - $byClass = $selectedYearId + /** @var Collection $byClassRows */ + $byClassRows = $selectedYearId ? Enrollment::query() ->join('classes', 'classes.id', '=', 'enrollments.class_id') ->join('students', 'students.id', '=', 'enrollments.student_id') @@ -118,16 +122,16 @@ classes.expected_age AS level, ->orderByRaw('classes.expected_age NULLS LAST') ->orderBy('classes.name') ->get() - ->map(fn ($r) => [ - 'label' => $r->label, - 'cycle' => $this->cycleLabel($r->cycle), - 'capacity' => (int) $r->capacity, - 'level' => $r->level !== null ? (int) $r->level : null, - 'count' => (int) $r->total, - 'male' => (int) $r->male, - 'female' => (int) $r->female, - ]) : collect(); + $byClass = $byClassRows->map(fn ($r) => [ + 'label' => $r->label, + 'cycle' => $this->cycleLabel($r->cycle), + 'capacity' => (int) $r->capacity, + 'level' => $r->level !== null ? (int) $r->level : null, + 'count' => (int) $r->total, + 'male' => (int) $r->male, + 'female' => (int) $r->female, + ]); return Inertia::render('Eleves/Students/Stats', [ 'summary' => [ diff --git a/app/Http/Controllers/Eleves/TimetableController.php b/app/Http/Controllers/Eleves/TimetableController.php index a20b864e..df4b6e79 100644 --- a/app/Http/Controllers/Eleves/TimetableController.php +++ b/app/Http/Controllers/Eleves/TimetableController.php @@ -211,15 +211,19 @@ private function teacherSlots(string $teacherId, ?string $yearId): Collection */ private function buildScheduleGrid(Collection $slots): array { - $range = fn ($s) => substr($s->start_time, 0, 5).'-'.substr($s->end_time, 0, 5); + $range = fn (TimetableSlot $s): string => substr($s->start_time, 0, 5).'-'.substr($s->end_time, 0, 5); + /** @var array> $grid */ $grid = []; foreach ($slots as $slot) { $grid[$range($slot)][$slot->day_of_week] = $slot; } + /** @var Collection $timeRanges */ + $timeRanges = $slots->map($range)->unique()->sort()->values(); + return [ - 'timeRanges' => $slots->map($range)->unique()->sort()->values(), + 'timeRanges' => $timeRanges, 'grid' => $grid, ]; } diff --git a/app/Http/Resources/Api/CalendarEventResource.php b/app/Http/Resources/Api/CalendarEventResource.php index 6a17f04f..f148d845 100644 --- a/app/Http/Resources/Api/CalendarEventResource.php +++ b/app/Http/Resources/Api/CalendarEventResource.php @@ -17,7 +17,7 @@ public function toArray(Request $request): array 'id' => $this->id, 'title' => $this->title, 'type' => $this->type, - 'start_date' => $this->start_date?->format('Y-m-d'), + 'start_date' => $this->start_date->format('Y-m-d'), 'end_date' => $this->end_date?->format('Y-m-d'), 'all_day' => $this->all_day, 'start_time' => $this->start_time, diff --git a/app/Models/CashAccount.php b/app/Models/CashAccount.php index a5e71d32..1f610cae 100644 --- a/app/Models/CashAccount.php +++ b/app/Models/CashAccount.php @@ -50,11 +50,11 @@ public function payments(): HasMany public function typeLabel(): string { + // Colonne enum en base : les trois valeurs couvrent tous les cas. return match ($this->type) { 'CASH' => 'Espèces', 'MOBILE_MONEY' => 'Mobile Money', 'BANK' => 'Banque', - default => $this->type, }; } } diff --git a/app/Models/OfficialExam.php b/app/Models/OfficialExam.php index 9e57478c..1511909f 100644 --- a/app/Models/OfficialExam.php +++ b/app/Models/OfficialExam.php @@ -7,6 +7,10 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasMany; +/** + * @property-read int|null $registrations_count Agrégat withCount('registrations'). + * @property-read int|null $admis_count Agrégat withCount alias (status = 'admis'). + */ class OfficialExam extends Model { use HasUuids; diff --git a/app/Models/Student.php b/app/Models/Student.php index eae22c0e..df3cc6c4 100644 --- a/app/Models/Student.php +++ b/app/Models/Student.php @@ -28,9 +28,13 @@ use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasOne; +use Illuminate\Database\Eloquent\Relations\Pivot; use Illuminate\Database\Eloquent\SoftDeletes; use Laravel\Sanctum\HasApiTokens; +/** + * @property-read Pivot&object{relationship: string} $pivot Pivot guardian_student (chargé via belongsToMany). + */ class Student extends Model implements AuthenticatableContract { use Auditable, AuthenticatableTrait, HasApiTokens, HasFactory, HasResetToken, HasUuids, SoftDeletes; diff --git a/app/Models/User.php b/app/Models/User.php index c70b3d22..b43d3128 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -23,6 +23,7 @@ use App\Traits\HasMatricule; use Database\Factories\UserFactory; use Illuminate\Database\Eloquent\Casts\Attribute; +use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Concerns\HasUuids; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\HasMany; @@ -30,8 +31,12 @@ use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; use Laravel\Fortify\TwoFactorAuthenticatable; +use Spatie\Permission\Models\Role; use Spatie\Permission\Traits\HasRoles; +/** + * @property-read Collection $roles + */ class User extends Authenticatable { /** @use HasFactory */ diff --git a/app/Services/DocumentRenderer.php b/app/Services/DocumentRenderer.php index 03b60d2d..9a9b48b1 100644 --- a/app/Services/DocumentRenderer.php +++ b/app/Services/DocumentRenderer.php @@ -90,16 +90,21 @@ public function resolveVariables( ]; if ($student) { + // $student peut être un modèle NON persisté (aperçu/test) : les colonnes + // NOT NULL en base peuvent alors être nulles. On lit via getAttribute() + // (type mixed) pour conserver les gardes défensives. + $birthDate = $student->getAttribute('birth_date'); + $vars += [ 'eleve.nom_complet' => trim(($student->lastname ?? '').' '.($student->firstname ?? '')), 'eleve.nom' => $student->lastname ?? '', 'eleve.prenom' => $student->firstname ?? '', 'eleve.matricule' => $student->matricule ?? '', - 'eleve.date_naissance' => $student->birth_date - ? Carbon::parse($student->birth_date)->locale('fr')->isoFormat('D MMMM YYYY') + 'eleve.date_naissance' => $birthDate + ? Carbon::parse($birthDate)->locale('fr')->isoFormat('D MMMM YYYY') : '', 'eleve.lieu_naissance' => $student->place_of_birth ?? '', - 'eleve.sexe' => match ($student->gender) { + 'eleve.sexe' => match ($student->getAttribute('gender')) { 'male' => 'Masculin', 'female' => 'Féminin', default => '', diff --git a/app/Services/PayrollService.php b/app/Services/PayrollService.php index eaaac199..2e7eea71 100644 --- a/app/Services/PayrollService.php +++ b/app/Services/PayrollService.php @@ -382,6 +382,8 @@ private function totals(array $lines): array private function refreshTotals(PayRun $run): void { + // Agrégat sans GROUP BY : toujours une ligne, colonnes calculées. + /** @var object{g: int|float|string, d: int|float|string, n: int|float|string} $sums */ $sums = $run->payslips()->selectRaw(' coalesce(sum(gross), 0) as g, coalesce(sum(total_deductions), 0) as d, diff --git a/app/Services/StatisticsService.php b/app/Services/StatisticsService.php index abe65952..353cef0b 100644 --- a/app/Services/StatisticsService.php +++ b/app/Services/StatisticsService.php @@ -395,6 +395,9 @@ public function attendanceStats(array $filters): array ->sortByDesc('absence_rate') ->values(); + // GROUP BY + HAVING : un ->count() SQL compterait les lignes par groupe, + // pas le nombre de groupes. On matérialise puis on compte en mémoire. + // @phpstan-ignore larastan.noUnnecessaryCollectionCall $chronic = (clone $base) ->where('ar.status', 'absent') ->groupBy('ar.student_id') diff --git a/app/Traits/HasMatricule.php b/app/Traits/HasMatricule.php index e1f4da0e..f3914455 100644 --- a/app/Traits/HasMatricule.php +++ b/app/Traits/HasMatricule.php @@ -14,9 +14,10 @@ trait HasMatricule */ public static function bootHasMatricule() { - // Générer le matricule avant la création si absent + // Générer le matricule avant la création si absent. + // Le modèle hôte expose generateMatricule() (cf. User::generateMatricule()). static::creating(function (self $model) { - if (empty($model->natricule) && method_exists($model, 'generateMatricule')) { + if (empty($model->natricule)) { $model->natricule = $model->generateMatricule(); } }); diff --git a/database/migrations/2026_02_16_150000_add_unique_indexes_to_matricules.php b/database/migrations/2026_02_16_150000_add_unique_indexes_to_matricules.php index 60ef1dc7..53d5760b 100644 --- a/database/migrations/2026_02_16_150000_add_unique_indexes_to_matricules.php +++ b/database/migrations/2026_02_16_150000_add_unique_indexes_to_matricules.php @@ -16,7 +16,7 @@ public function up(): void Schema::table('users', function (Blueprint $table) { // Check if index doesn't already exist if (! Schema::hasIndex('users', 'users_natricule_unique')) { - $table->unique('natricule')->index(); + $table->unique('natricule'); } }); } @@ -25,7 +25,7 @@ public function up(): void if (Schema::hasTable('students') && Schema::hasColumn('students', 'registration_number')) { Schema::table('students', function (Blueprint $table) { if (! Schema::hasIndex('students', 'students_registration_number_unique')) { - $table->unique('registration_number')->index(); + $table->unique('registration_number'); } }); } diff --git a/database/migrations/2026_06_14_000011_add_locked_at_to_evaluations_table.php b/database/migrations/2026_06_14_000011_add_locked_at_to_evaluations_table.php index d8bbe6ec..b1000e37 100644 --- a/database/migrations/2026_06_14_000011_add_locked_at_to_evaluations_table.php +++ b/database/migrations/2026_06_14_000011_add_locked_at_to_evaluations_table.php @@ -10,7 +10,7 @@ public function up(): void { Schema::table('evaluations', function (Blueprint $table) { $table->timestamp('locked_at')->nullable()->after('status'); - $table->foreignUuid('locked_by')->nullable()->constrained('users')->nullOnDelete()->after('locked_at'); + $table->foreignUuid('locked_by')->nullable()->after('locked_at')->constrained('users')->nullOnDelete(); }); } diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon deleted file mode 100644 index a291b9a6..00000000 --- a/phpstan-baseline.neon +++ /dev/null @@ -1,259 +0,0 @@ -parameters: - ignoreErrors: - - - message: '#^Instanceof between App\\Models\\User\|null and App\\Models\\Guardian will always evaluate to false\.$#' - identifier: instanceof.alwaysFalse - count: 2 - path: app/Http/Controllers/Api/ApiController.php - - - - message: '#^Instanceof between App\\Models\\User\|null and App\\Models\\Student will always evaluate to false\.$#' - identifier: instanceof.alwaysFalse - count: 2 - path: app/Http/Controllers/Api/ApiController.php - - - - message: '#^Parameter \#1 \$value of function collect contains unresolvable type\.$#' - identifier: argument.unresolvableType - count: 1 - path: app/Http/Controllers/Api/ApiController.php - - - - message: '#^Call to an undefined method App\\Models\\User\:\:currentAccessToken\(\)\.$#' - identifier: method.notFound - count: 1 - path: app/Http/Controllers/Api/V1/AuthController.php - - - - message: '#^Instanceof between App\\Models\\User\|null and App\\Models\\Guardian will always evaluate to false\.$#' - identifier: instanceof.alwaysFalse - count: 1 - path: app/Http/Controllers/Api/V1/AuthController.php - - - - message: '#^Parameter \#1 \$user of method App\\Http\\Controllers\\Api\\V1\\AuthController\:\:profile\(\) expects App\\Models\\Guardian\|App\\Models\\Student, App\\Models\\User\|null given\.$#' - identifier: argument.type - count: 1 - path: app/Http/Controllers/Api/V1/AuthController.php - - - - message: '#^Instanceof between App\\Models\\User\|null and App\\Models\\Student will always evaluate to false\.$#' - identifier: instanceof.alwaysFalse - count: 1 - path: app/Http/Controllers/Api/V1/ChildrenController.php - - - - message: '#^Parameter \#1 \$value of function collect contains unresolvable type\.$#' - identifier: argument.unresolvableType - count: 1 - path: app/Http/Controllers/Api/V1/ChildrenController.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$name\.$#' - identifier: property.notFound - count: 2 - path: app/Http/Controllers/Archives/ArchiveController.php - - - - message: '#^Access to an undefined property App\\Models\\ReportCard\:\:\$avg_general\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Dashboard/DashboardController.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$name\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Dashboard/DashboardController.php - - - - message: '#^Access to an undefined property App\\Models\\Enrollment\:\:\$billed\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Eleves/EnrollmentController.php - - - - message: '#^Access to an undefined property App\\Models\\Enrollment\:\:\$collected\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Eleves/EnrollmentController.php - - - - message: '#^Access to an undefined property App\\Models\\Enrollment\:\:\$remaining\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Eleves/EnrollmentController.php - - - - message: '#^Access to an undefined property App\\Models\\Enrollment\:\:\$unpaid_amount\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Eleves/EnrollmentController.php - - - - message: '#^Access to an undefined property App\\Models\\Enrollment\:\:\$unpaid_count\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Eleves/EnrollmentController.php - - - - message: '#^Access to an undefined property App\\Models\\Enrollment\:\:\$birth_date\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Eleves/StudentStatsController.php - - - - message: '#^Access to an undefined property App\\Models\\Enrollment\:\:\$capacity\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Eleves/StudentStatsController.php - - - - message: '#^Access to an undefined property App\\Models\\Enrollment\:\:\$cycle\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Eleves/StudentStatsController.php - - - - message: '#^Access to an undefined property App\\Models\\Enrollment\:\:\$expected_age\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Eleves/StudentStatsController.php - - - - message: '#^Access to an undefined property App\\Models\\Enrollment\:\:\$female\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Eleves/StudentStatsController.php - - - - message: '#^Access to an undefined property App\\Models\\Enrollment\:\:\$label\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Eleves/StudentStatsController.php - - - - message: '#^Access to an undefined property App\\Models\\Enrollment\:\:\$level\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Eleves/StudentStatsController.php - - - - message: '#^Access to an undefined property App\\Models\\Enrollment\:\:\$male\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Eleves/StudentStatsController.php - - - - message: '#^Access to an undefined property App\\Models\\Enrollment\:\:\$total\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Eleves/StudentStatsController.php - - - - message: '#^Access to an undefined property App\\Models\\Student\:\:\$count\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Eleves/StudentStatsController.php - - - - message: '#^Parameter \#1 \$callback of method Illuminate\\Database\\Eloquent\\Collection\\:\:map\(\) contains unresolvable type\.$#' - identifier: argument.unresolvableType - count: 1 - path: app/Http/Controllers/Eleves/StudentStatsController.php - - - - message: '#^Method App\\Http\\Controllers\\Eleves\\TimetableController\:\:buildScheduleGrid\(\) should return array\{timeRanges\: Illuminate\\Support\\Collection\, grid\: array\\>\} but returns array\{timeRanges\: Illuminate\\Support\\Collection\, grid\: array\, App\\Models\\TimetableSlot\>\>\}\.$#' - identifier: return.type - count: 1 - path: app/Http/Controllers/Eleves/TimetableController.php - - - - message: '#^Access to an undefined property App\\Models\\OfficialExam\:\:\$admis_count\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Controllers/Examens/OfficialExamController.php - - - - message: '#^Parameter \#1 \$callback of method Illuminate\\Database\\Eloquent\\Collection\\:\:map\(\) contains unresolvable type\.$#' - identifier: argument.unresolvableType - count: 1 - path: app/Http/Controllers/Examens/OfficialExamController.php - - - - message: '#^Using nullsafe method call on non\-nullable type Carbon\\CarbonImmutable\. Use \-\> instead\.$#' - identifier: nullsafe.neverNull - count: 1 - path: app/Http/Resources/Api/CalendarEventResource.php - - - - message: '#^Access to an undefined property App\\Http\\Resources\\Api\\StudentResource\:\:\$pivot\.$#' - identifier: property.notFound - count: 1 - path: app/Http/Resources/Api/StudentResource.php - - - - message: '#^Match arm comparison between ''BANK'' and ''BANK'' is always true\.$#' - identifier: match.alwaysTrue - count: 1 - path: app/Models/CashAccount.php - - - - message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$name\.$#' - identifier: property.notFound - count: 1 - path: app/Models/User.php - - - - message: '#^Match arm comparison between ''female'' and ''female'' is always true\.$#' - identifier: match.alwaysTrue - count: 1 - path: app/Services/DocumentRenderer.php - - - - message: '#^Ternary operator condition is always true\.$#' - identifier: ternary.alwaysTrue - count: 1 - path: app/Services/DocumentRenderer.php - - - - message: '#^Access to an undefined property App\\Models\\Payslip\:\:\$d\.$#' - identifier: property.notFound - count: 1 - path: app/Services/PayrollService.php - - - - message: '#^Access to an undefined property App\\Models\\Payslip\:\:\$g\.$#' - identifier: property.notFound - count: 1 - path: app/Services/PayrollService.php - - - - message: '#^Access to an undefined property App\\Models\\Payslip\:\:\$n\.$#' - identifier: property.notFound - count: 1 - path: app/Services/PayrollService.php - - - - message: '#^Called ''count'' on Laravel collection, but could have been retrieved as a query\.$#' - identifier: larastan.noUnnecessaryCollectionCall - count: 1 - path: app/Services/StatisticsService.php - - - - message: '#^Call to function method_exists\(\) with App\\Models\\User and ''generateMatricule'' will always evaluate to true\.$#' - identifier: function.alreadyNarrowedType - count: 1 - path: app/Traits/HasMatricule.php - - - - message: '#^Call to an undefined method Illuminate\\Database\\Schema\\IndexDefinition\:\:index\(\)\.$#' - identifier: method.notFound - count: 2 - path: database/migrations/2026_02_16_150000_add_unique_indexes_to_matricules.php - - - - message: '#^Call to an undefined method Illuminate\\Database\\Schema\\ForeignKeyDefinition\:\:after\(\)\.$#' - identifier: method.notFound - count: 1 - path: database/migrations/2026_06_14_000011_add_locked_at_to_evaluations_table.php diff --git a/phpstan.neon b/phpstan.neon index bea8f51a..b3fc9c57 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,6 +1,5 @@ includes: - vendor/larastan/larastan/extension.neon - - phpstan-baseline.neon parameters: level: 5 From bb6111c9c8afddbfa7843f9e31b9dc8d0912e84d Mon Sep 17 00:00:00 2001 From: horacioskrp Date: Fri, 13 Nov 2026 16:10:49 +0000 Subject: [PATCH 12/12] =?UTF-8?q?fix(seeders):=20restaure=20l'acc=C3=A8s?= =?UTF-8?q?=20nullsafe=20$this->command=3F->=20(corrige=20SeederHardeningT?= =?UTF-8?q?est)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Un commit précédent avait retiré les `?->` sur $this->command, jugés superflus par PHPStan (Seeder::$command est typé non-nul par le framework). Mais la propriété vaut null quand run() est appelé directement, hors `db:seed` — ce que font les seeders invoqués programmatiquement et SeederHardeningTest, d'où un « Call to a member function warn()/info() on null » en CI. - Rétablit `$this->command?->` dans les 5 seeders (comportement identique quand la commande console est présente, sûr quand elle est absente). - phpstan.neon : règle ignoreErrors ciblée et documentée pour le faux positif nullsafe.neverNull sur Illuminate\Console\Command dans database/seeders. Suite complète : 505 passants, 0 échec. PHPStan niveau 5 : vert. --- database/seeders/DatabaseSeeder.php | 2 +- database/seeders/DefaultUsersSeeder.php | 6 +++--- database/seeders/DemoSeeder.php | 16 ++++++++-------- database/seeders/RolesAndPermissionsSeeder.php | 2 +- database/seeders/StudentTestSeeder.php | 8 ++++---- phpstan.neon | 10 ++++++++-- 6 files changed, 25 insertions(+), 19 deletions(-) diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index 96c617b1..5925260f 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -23,7 +23,7 @@ public function run(): void // 2) Données de démonstration — jamais en production if (app()->environment('production')) { - $this->command->warn( + $this->command?->warn( 'Données de démonstration ignorées (environnement de production). ' .'Seules les données de référence ont été installées.' ); diff --git a/database/seeders/DefaultUsersSeeder.php b/database/seeders/DefaultUsersSeeder.php index 4df59493..cc684958 100644 --- a/database/seeders/DefaultUsersSeeder.php +++ b/database/seeders/DefaultUsersSeeder.php @@ -18,7 +18,7 @@ public function run(): void { // Garde-fou : ces comptes utilisent un mot de passe connu, jamais en production. if (app()->environment('production')) { - $this->command->warn('DefaultUsersSeeder ignoré : comptes de démonstration interdits en production.'); + $this->command?->warn('DefaultUsersSeeder ignoré : comptes de démonstration interdits en production.'); return; } @@ -71,10 +71,10 @@ public function run(): void $user->save(); } - $this->command->info("• {$email} ({$role})"); + $this->command?->info("• {$email} ({$role})"); } - $this->command->warn( + $this->command?->warn( 'Comptes de démonstration créés — mot de passe : "'.self::PASSWORD.'". ' .'Un changement de mot de passe sera exigé à la première connexion.' ); diff --git a/database/seeders/DemoSeeder.php b/database/seeders/DemoSeeder.php index 0cc82d6d..8dca323c 100644 --- a/database/seeders/DemoSeeder.php +++ b/database/seeders/DemoSeeder.php @@ -162,7 +162,7 @@ class DemoSeeder extends Seeder public function run(): void { if (app()->environment('production')) { - $this->command->warn('DemoSeeder ignoré : données de démonstration interdites en production.'); + $this->command?->warn('DemoSeeder ignoré : données de démonstration interdites en production.'); return; } @@ -195,8 +195,8 @@ public function run(): void $this->step('Bulletins', fn () => $this->seedReportCards()); $this->step('Années passées (comparaisons)', fn () => $this->seedHistory()); - $this->command->newLine(); - $this->command->info('Jeu de démonstration prêt.'); + $this->command?->newLine(); + $this->command?->info('Jeu de démonstration prêt.'); } /* ------------------------------------------------------------------ */ @@ -214,7 +214,7 @@ private function resolveContext(): bool ?? AcademicYear::query()->latest('start_date')->first(); if (! $school || ! $year) { - $this->command->error('DemoSeeder : école ou année académique manquante. Lancez db:seed au préalable.'); + $this->command?->error('DemoSeeder : école ou année académique manquante. Lancez db:seed au préalable.'); return false; } @@ -228,7 +228,7 @@ private function resolveContext(): bool ->get(); if ($this->periods->isEmpty()) { - $this->command->error('DemoSeeder : aucune période académique pour l\'année active.'); + $this->command?->error('DemoSeeder : aucune période académique pour l\'année active.'); return false; } @@ -240,7 +240,7 @@ private function resolveContext(): bool ->get(); if ($this->classes->isEmpty()) { - $this->command->error('DemoSeeder : aucune classe active (ClassroomSeeder).'); + $this->command?->error('DemoSeeder : aucune classe active (ClassroomSeeder).'); return false; } @@ -272,7 +272,7 @@ private function targetSize(Classroom $class): int private function step(string $label, callable $work): void { - $this->command->line(" - {$label}..."); + $this->command?->line(" - {$label}..."); $work(); } @@ -295,7 +295,7 @@ private function localites(): array $path = database_path('data/togo-localites.json'); if (! is_file($path)) { - $this->command->warn('togo-localites.json introuvable : les élèves seront créés sans origine géographique.'); + $this->command?->warn('togo-localites.json introuvable : les élèves seront créés sans origine géographique.'); return $this->localites = []; } diff --git a/database/seeders/RolesAndPermissionsSeeder.php b/database/seeders/RolesAndPermissionsSeeder.php index fd8ca28d..0f9630d1 100644 --- a/database/seeders/RolesAndPermissionsSeeder.php +++ b/database/seeders/RolesAndPermissionsSeeder.php @@ -141,7 +141,7 @@ public function run(): void ], [Permissions::VIEW_STUDENT_PARENTS_INFO]); Role::firstOrCreate(['name' => Roles::SECRETARIAT])->syncPermissions($secretary); - $this->command->info('Rôles et permissions synchronisés ('.count(Permissions::all()).' permissions).'); + $this->command?->info('Rôles et permissions synchronisés ('.count(Permissions::all()).' permissions).'); } /** diff --git a/database/seeders/StudentTestSeeder.php b/database/seeders/StudentTestSeeder.php index c6a49e65..a21b9a50 100644 --- a/database/seeders/StudentTestSeeder.php +++ b/database/seeders/StudentTestSeeder.php @@ -34,14 +34,14 @@ public function run(): void { // Garde-fou : données fictives interdites en production. if (app()->environment('production')) { - $this->command->warn('StudentTestSeeder ignoré : données fictives interdites en production.'); + $this->command?->warn('StudentTestSeeder ignoré : données fictives interdites en production.'); return; } // Idempotent : on ne régénère pas de faux élèves si la base en contient déjà. if (Student::query()->exists()) { - $this->command->warn('StudentTestSeeder ignoré : des élèves existent déjà.'); + $this->command?->warn('StudentTestSeeder ignoré : des élèves existent déjà.'); return; } @@ -58,7 +58,7 @@ public function run(): void // les classes peuvent manquer sur une base vide. $canEnroll = $school && $classroomIds !== []; if (! $canEnroll) { - $this->command->warn( + $this->command?->warn( 'Élèves créés sans inscription : école ou classes manquantes.' ); } @@ -155,7 +155,7 @@ public function run(): void } } - $this->command->info( + $this->command?->info( self::COUNT.' élèves de test créés'.($canEnroll ? ' et inscrits.' : ' (sans inscription).') ); } diff --git a/phpstan.neon b/phpstan.neon index b3fc9c57..f3da84b7 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -14,5 +14,11 @@ parameters: # viewDirectories: # - resources/views - # Ignore les faux positifs connus liés à la magie Laravel ici si besoin. - ignoreErrors: [] + # Faux positif de framework : Illuminate\Database\Seeder::$command est typé + # non-nul, mais vaut null quand run() est appelé directement (hors `db:seed`), + # ce que font les seeders invoqués programmatiquement et SeederHardeningTest. + # L'accès nullsafe `$this->command?->` est donc volontaire et correct. + ignoreErrors: + - + message: '#Using nullsafe method call on non-nullable type Illuminate\\Console\\Command\. Use -> instead\.#' + path: database/seeders/*.php