particle-academy/laravel-courses
A Laravel package for building course platforms: manage courses, lessons, and curriculum structure, handle enrollments, and track learner progress with simple, framework-native APIs that drop into any Laravel app.
POST enrollments would self-enrol anyone into anything, letting a learner walk around your payment flow with a single request. The GatesSelfEnrollment contract (default FreeEnrollmentOnly) now means priced courses are enrolment-by-server-only: you call EnrollmentService::enroll() after payment confirms. This is a product decision you can now defend in a security review, and it makes "free trial / free audit" vs "paid seat" a first-class distinction rather than an honour system.attendances table plus courses.attendance_hours gives you completion-by-attendance, dispatched through the same ProgressService that completes on a passed final test. Course::requiredAttendanceHours() encodes the policy you actually want: on in_person, null means "all of hours"; on hybrid, only an explicit value counts, because there hours describes the whole course. That distinction is a product rule, and it's now expressible without custom code.in_person course's lessons and tests from counting toward progress is, read as a product decision, the ability to sell in person first and author the e-learning later — drafting lessons no longer strands already-enrolled learners at 0%. If a course's lessons are meant to be completed, the answer is to make it hybrid, and that becomes an explicit catalogue decision rather than an accident of authoring order.summary() gains hours_required, hours_attended, hours_percent, and overall_percent averages the online half and the attendance half when both exist (the units differ). This is what you need for a learner-facing progress bar that doesn't lie, and for an ops dashboard that can answer "who is close to finishing?" across formats.Adopt when most of the following hold:
EnrollmentService::enroll() call; this release explicitly makes that your job, not the package's.composer.json.GatesSelfEnrollment changes behaviour on deploy: any priced course that previously relied on open self-enrolment will start refusing learners the moment you upgrade. If you have live traffic and a membership model, you need to bind a custom gate before you ship.in_person course whose lessons currently count toward progress will see that progress change on upgrade — if those lessons are load-bearing, you must switch the course to hybrid as part of the release.Look elsewhere when:
GatesSelfEnrollment implementation.attendances is one row per session with a date and hours; scheduling is out of scope.To executives: this release turns two product ideas we couldn't previously ship into configuration. First, we can now sell courses safely — a learner can no longer bypass checkout with a single request; paid enrolments are created server-side only after payment confirms. Second, we can run in-person and hybrid courses with real completion tracking, backed by an attendance ledger and an hours requirement, reported through the same progress summary learners already see. It also unlocks a commercial sequencing we wanted: sell a course in person now, author its online material later, without stranding the people already enrolled.
To engineering: the package handles the enrolment state machine, mixed-unit progress arithmetic, and completion triggers; we own checkout, the payment-to-enrolment handoff, UI, and any non-default enrolment policy. Be clear about the two breaking changes: (1) POST enrollments no longer admits learners into priced courses — the new GatesSelfEnrollment default FreeEnrollmentOnly enforces this, existing enrolments are returned without asking, and we must call EnrollmentService::enroll() post-payment (bind a custom gate if a membership model needs otherwise); (2) in_person lessons and tests no longer count toward progress — reclassify affected courses to hybrid. Attendance routes are admin-gated by design; learners cannot record their own hours. overall_percent averages online and attendance halves when both exist; online-only is unchanged. Deleting an attendance row does not reopen a completed enrolment, and an in-person course with no hours set never auto-completes — both are intentional, and we should treat certificate withdrawal as a separate revocation feature if we ever need it.
To customer success / ops: admins record attendance against an enrolment; completion fires when required hours are reached, using the same trigger as passing a final test. Two things will look like bugs but aren't: removing an attendance record won't un-complete a learner who has already finished, and a course with no attendance_hours set will never complete on its own — it needs an admin completion. Flag any course still marked in_person that has lessons people are meant to finish; those need to become hybrid before release.
How can I help you explore Laravel packages today?