Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Courses Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

This release makes the package usable for in-person training, so the first thing to establish is which of the three course types you are actually running: online (lessons/tests only), in_person (attendance only), hybrid (both).

Minimal setup:

composer require <vendor>/<package>
php artisan vendor:publish --tag=<package>-migrations   # if you haven't already
php artisan migrate

v0.3.0 adds the attendances table and a courses.attendance_hours column, so an existing install needs a fresh migration run before anything else works.

Where to look first, in order:

  1. ProgressService::summary() — the single source of truth for "where is this learner". It now returns hours_required, hours_attended, hours_percent alongside the online fields. Build your UI off it rather than recomputing.
  2. EnrollmentService::enroll() — the ungated way to create an enrollment. This is the path you want in any paid flow.
  3. GatesSelfEnrollment — the contract that now guards POST enrollments. You only touch this if the default (FreeEnrollmentOnly) is wrong for you.

First use case to wire end to end: take a free online course, self-enrol via POST enrollments, and confirm progress moves as lessons/tests complete. Then take an in_person course with attendance_hours (or hours) set, record an attendance row, and confirm the enrollment completes when the hours are met. That exercises both halves of the new model in ten minutes.

Implementation Patterns

Selling courses: enrol after payment, not before. The intended path is explicit — once your checkout confirms, create the enrollment yourself:

// payment webhook / callback
app(EnrollmentService::class)->enroll($user, $course);

This bypasses the gate by design. Do not "fix" a broken paid flow by loosening FreeEnrollmentOnly; the gate exists so a host can't be walked around with one request.

Loosening the gate when self-enrolment in priced courses is legitimate (memberships, staff comps, internal training):

// AppServiceProvider::register()
$this->app->bind(GatesSelfEnrollment::class, MembershipEnrollment::class);

Implement the contract against your own rule (membership tier, coupon, domain allowlist). Keep the existing-enrollment short-circuit in mind: the gate is only asked for new self-enrolments.

Recording attendance from a check-in flow. Staff-facing routes are already there and admin-gated:

GET|POST enrollments/{enrollment}/attendances
DELETE   enrollments/{enrollment}/attendances/{attendance}

A sign-in desk posting date + hours is the natural consumer. For programmatic use (importing a CSV from a training provider, syncing a room scanner), call the service directly so the event fires and completion is evaluated:

$progress = app(ProgressService::class);

$progress->recordAttendance($enrollment, $sessionDate, 3.5);
// dispatches AttendanceRecorded; completes the enrollment when required hours are reached

Don't hand-roll completion. Reaching required hours completes the enrollment exactly the way passing the last test does. If you need a side effect (certificate, email, CRM push), listen for AttendanceRecorded and the existing completion event rather than reimplementing the threshold check — you will get the hybrid averaging rules wrong.

Choose course type deliberately, not by accident. The type decides what counts:

type lessons/tests attendance
online count n/a
in_person ignored counts
hybrid count counts, explicit attendance_hours required

The "sell in person first, author the online material later" workflow is the reason in_person ignores lessons — drafting them must not strand existing enrollments. If lessons are genuinely meant to be completed, the course is hybrid, not in_person.

Rendering progress for mixed courses. For an enrollment with both halves, overall_percent is the average of the online percentage and the attendance percentage — two numbers with different units, averaged. Show the components (hours_attended / hours_required and the online percent) next to the aggregate, because the aggregate alone will look wrong to a learner who has done everything in one half.

Gotchas and Tips

  • Priced self-enrolment now fails where it used to succeed. If a checkout page posts to POST enrollments and then takes payment, it is broken in v0.3.0 — the gate rejects the priced target before any money moves. Symptom is a 4xx on a flow that "worked yesterday". Move the enrollment to the payment confirmation step.
  • POST enrollments is idempotent, so it won't tell you anything. An existing enrollment is returned without asking the gate. Don't use the endpoint as a "has this user paid?" probe; it will happily return a stale enrollment for a priced course.
  • in_person courses with authored lessons now sit at 0%. This is the second breaking change and the one that will show up as a support ticket: a course that used to reach 100% via lessons now never moves. Fix is a data change — switch those rows to hybrid.
  • hybrid with no attendance_hours never completes by itself. On hybrid, hours is the whole course, so only an explicit attendance_hours counts; null means nothing to reach. On in_person, null falls back to hours. Same column, different semantics per type — check the type before debugging the number.
  • An in-person course with no hours set is uncompletable. "Nothing to reach is not the same as reaching it." If a course has no hours, only the admin completion short-circuit will finish it. Set hours (or attendance_hours) at course creation.
  • Deleting an attendance row does not reopen a completed enrollment. Treat attendance as an append-only audit trail: to correct a mistake, prefer adding a corrective row over deleting, and if you must delete past a completion, expect to revoke the certificate manually — the package deliberately won't do it for you.
  • Learners cannot record their own hours. Don't ship a "log my hours" button in the learner UI; those routes sit behind the admin gate. Self-reported attendance needs your own controller and your own trust model.
  • overall_percent looks pessimistic on lopsided progress. 100% online plus 1 of 10 sessions reads as ~55%. That is by design (units differ), but label it in the UI or you will get bug reports.
  • Debugging order for "why is this stuck at X%": course type → requiredAttendanceHours() → summary() → the raw attendances rows. Nine times out of ten it is the type.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource