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

Technical Evaluation

Architecture fit

This package sits in the enrollment/progress layer of a Laravel application: it owns the enrollment record, the completion decision (tests, attendance, admin short-circuit), and the progress summary that a UI or certificate issuer consumes. That is a good fit for the domain it covers, and v0.3.0 makes the boundary explicit in two useful ways. First, GatesSelfEnrollment turns "may this learner enroll themselves in this target?" into a swappable contract rather than a hard-coded controller behaviour, which is the right seam for hosts with checkout, memberships, or invite-only flows. Second, moving completion authority for in_person courses to attendance (via ProgressService::recordAttendance() and dispatch of AttendanceRecorded) keeps a single write path for state changes, so existing listeners on the completion event continue to fire regardless of whether a test or an attendance row triggered completion.

The package now implicitly models three course modes — online, hybrid, in_person — with different progress semantics. overall_percent averaging "the online half and the attendance half" for enrollments that have both is a pragmatic answer to a mixed-unit problem, and it is contained inside the summary rather than exposed as something hosts must compute. That said, this is where the package stops being a thin progress calculator and starts encoding academic policy. Every host that displays a percentage, gates a certificate on it, or reports on completion will inherit that policy whether or not it matches their own.

Integration feasibility

Feasible for a standard Laravel host, with three concrete integration points:

  1. Bind GatesSelfEnrollment (or accept the default FreeEnrollmentOnly) and adapt the checkout flow to call EnrollmentService::enroll() on payment confirmation. The release notes are explicit that this path is not governed by the gate, so the intended remediation is small but must land in the same release.
  2. New schema: an attendances table (one row per session: date, hours) and a courses.attendance_hours column. Standard migration, additive.
  3. Admin surface: GET|POST enrollments/{enrollment}/attendances and the delete route are admin-gated and require a UI or tooling for whoever records hours. No learner-facing route exists, so attendance capture is deliberately a staff operation.

The main feasibility question is not technical but procedural: someone in the organisation has to actually record attendance, and that someone is an administrator, not the learner or the instructor unless they hold the admin gate.

Technical risk

High, concentrated in the two breaking changes.

  • Self-enrollment lockout. Any existing flow that relied on POST enrollments to enroll a learner in a priced course will now silently refuse (FreeEnrollmentOnly admits only price null or zero). Existing enrollments are returned without consulting the gate, so the failure only appears for new enrollments — meaning it will surface in production as "new users can't start the course" rather than in a regression test with existing fixtures. Silent, late-breaking, and revenue-adjacent.
  • Progress regression on in_person courses with lessons. Lessons and tests stop counting toward progress on in_person courses. A host that authored an in_person course with lessons — which the previous version permitted and counted — will see those learners' progress drop, potentially un-completing enrollments that were not yet completed and changing overall_percent for everyone mid-course. The remedy (switch to hybrid) is a data migration the host owns, and it is not obviously reversible in intent: hybrid also changes how attendance_hours resolves.
  • hybrid semantics differ. On hybrid, only an explicit attendance_hours counts; null does not fall back to hours. On in_person, null means "all of hours". A host that switches courses to hybrid to restore lesson progress, but does not set attendance_hours, gets an attendance half that never completes. Two independent knobs, easy to set one and not the other.
  • Deletion does not reopen completion. Correct policy for certificates, but it means an attendance recorded in error and then deleted leaves a completed enrollment completed. There is no in-package correction path; the admin short-circuit is one-way and un-revoking is manual.
  • No self-completion for hourless in-person courses. An in_person course with no hours never completes on its own. Fine as a deliberate stance, but it converts a configuration omission into a permanent "0% forever" — precisely the bug v0.3.0 set out to fix, now reachable through a different door.

Secondary risk: v0.3.0 is a pre-1.0 package shipping breaking changes. That is defensible for the maintainer and still a real cost for us — we should assume another one on the road to 1.0 and pin accordingly.

Key questions

  1. Which of our live courses are in_person and have lessons or tests? That is the blast radius of the progress change, and the answer decides whether this is a config change or a data migration.
  2. For each course we sell, where does the enrollment currently get created — the POST enrollments endpoint, or our own code? If the former, that flow is now broken for priced courses and must move to EnrollmentService::enroll() on payment confirmation.
  3. Do we have any legitimate reason for learners to self-enroll in priced courses (membership tiers, comped seats, partner codes)? If yes, we need our own GatesSelfEnrollment binding, and the default is not enough.
  4. Who operates attendance, and through what? The routes are admin-gated; if our instructors are not admins, they cannot record hours, and completion stalls.
  5. Do downstream consumers read overall_percent as a number, or do they read summary()'s individual fields? Averaging two different units changes the meaning of the number even when it does not change the code.
  6. Is the certificate issuance triggered on the completion event? If so, the new in_person completion path will start issuing certificates for a population that previously could only be completed by an admin — which is the point, but it should be a conscious release-note line, not a surprise.

Integration Approach

Stack fit

A conventional Laravel host: service provider binding for GatesSelfEnrollment, migrations for attendances and courses.attendance_hours, admin routes behind the existing admin gate, queue/listener wiring for AttendanceRecorded and the existing completion event. No new infrastructure. Nothing here requires a queue worker, a scheduler, or an external service, though if certificate issuance is already queue-backed that continues to work unchanged because completion still flows through the same event.

Migration path

  1. Schema first, additive. Ship the attendances table and courses.attendance_hours column ahead of any behaviour change. Additive, reversible, no read path depends on it yet.
  2. Backfill course mode and hours. Audit every course: online, hybrid, in_person. For in_person courses whose lessons are meant to be completed, set hybrid and set an explicit attendance_hours (do not rely on the null fallback, which does not apply on hybrid). For genuinely in-person courses, set attendance_hours explicitly rather than relying on "null means all of hours" — the implicit form is harder to audit later.
  3. Fix the checkout path before deploying the gate change. Move priced-course enrollment creation to EnrollmentService::enroll() on payment confirmation, and decide explicitly whether a custom GatesSelfEnrollment binding is needed. Deploy this with the upgrade, not after — the default closes a hole but also closes a door we may be using.
  4. Snapshot progress before the semantics change. Capture overall_percent and completion state for every active enrollment in affected courses, so the post-deploy diff is measurable rather than asserted. Enrollments that drop below completion threshold are the ones that need a decision (admin short-circuit to preserve, or accept the reset and communicate).
  5. Stand up the admin attendance surface, and confirm who holds the admin gate that reaches it.

Compatibility

Not backward compatible in two places, both behavioural rather than signature-level, which makes them harder to catch: existing code compiles and existing tests pass, and only new enrollments on priced courses and in-progress in_person enrollments reveal the change. Nothing here requires a PHP or Laravel version bump as far as the notes indicate, but that should be verified against composer.json rather than assumed. Pin the package version; do not float on ^0.3.

Sequencing

Schema → backfill (course modes, attendance_hours) → checkout path and gate binding → progress snapshot → deploy → attendance operations go live → monitor. Note that steps 2 and 3 can proceed in parallel, but both must complete before the deploy, and the snapshot (4) is worthless if taken after.

Operational Impact

Maintenance

Ongoing maintenance is low in code terms — the package owns the tables and services, and we own configuration data: course mode, attendance_hours, and the gate binding. The standing risk is configuration drift: courses created after this release inherit whatever default attendance_hours and mode the authoring flow applies, and a null on an in_person course is a silent never-complete. That argues for making attendance_hours a required field in the course authoring UI with validation, rather than trusting authors to know the fallback rules. Attendance rows are staff-entered and therefore need an audit trail — who recorded the hours, when — which the package does not appear to provide beyond the row itself.

Support

Expect two support themes. First, "I paid and I'm not enrolled" if the checkout path is not migrated correctly; this is the highest-severity failure mode because it is invisible until a new purchase happens and it looks like a payment problem rather than an enrollment one. Second, "my progress went down" or "my course is stuck at 0%" from learners in in_person courses with lessons or with no hours configured. Both are answerable if the backfill is documented; neither is answerable if it is not. Support also needs a clear story for the erroneous-attendance case: deleting the row does not reopen the enrollment, so the resolution is a manual decision about the certificate, not a support ticket the package can close.

Scaling

Attendance rows are one per session per enrollment — low volume, no scaling concern. summary() now computes three additional fields and, on mixed enrollments, averages two aggregates; if the summary is rendered per row in an admin listing, watch for N+1 queries against attendances, since the package is now reading a second table to produce a number that used to come from one. That is the only plausible scaling regression, and it is a query-count issue, not a throughput one.

Failure modes

  • Silent enrollment refusal on priced courses via the default gate — no error, no exception, just a learner who cannot start. Highest impact, lowest visibility.
  • Progress regression on in_person courses with lessons, up to and including loss of completion state for in-flight enrollments.
  • Never-completing courses — in_person with no hours, or hybrid with no explicit attendance_hours. Presents as the original 0%-forever bug the release was meant to fix.
  • Un-reopenable completion after an erroneous attendance row is deleted, with a certificate potentially already issued.
  • Admin-gate dependency: if the attendance routes sit behind a gate that instructors do not hold, hours never get recorded and nothing completes — a permissions misconfiguration that looks like a package bug.
  • Mid-course unit change: overall_percent changes meaning for any enrollment with both online and attendance components, which is a reporting discontinuity even where the underlying data is correct.

Ramp-up

Small but front-loaded, and it lands on two different groups. Engineering needs roughly a day to bind the gate, migrate the checkout path, run the schema and backfill, and take the progress snapshot — plus whatever the affected-course audit turns up, which is the unpredictable part. Operations needs training on the admin attendance routes and an explicit rule for correcting mistakes, given that deletion is not a rollback. Product needs to decide, course by course, whether each in_person course should be hybrid; that decision cannot be delegated to engineering because it is a statement about how the course is taught. Budget the audit and the course-mode decision as the real cost, not the code.

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.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings