ozankurt/laravel-modules-forum
A Laravel package that adds a ready-to-use forum module to modular applications, including discussion threads, posts, categories, and moderation tools. It integrates with ozankurt/laravel-modules for clean, reusable community features and is easy to install and customize.
Architecture fit
illuminate/contracts, database, and support, so it does not impose an application skeleton, service provider boot order, or routing convention beyond standard Laravel package discovery via spatie/laravel-package-tools.laravel-modules-core, laravel-modules-interactions, and this package), all pinned to ^2.0. That makes the package a participant in a coordinated major-version release train rather than a standalone dependency — architecturally fine, but it means our upgrade unit of work is the trio, not this package alone.Integration feasibility
PHP ^8.4 and Laravel ^13.0 are both mandatory; there is no dual-support window. Feasibility therefore collapses to a single question: can our application run on PHP 8.4 and Laravel 13 today?ozankurt/laravel-modules-core ^2.0 and ozankurt/laravel-modules-interactions ^2.0 must resolve together with cviebrock/eloquent-sluggable ^12.0 || ^13.0. The sluggable disjunction is helpful — it lets us hold sluggable at 12 while other constraints settle.DB::raw()/orderByRaw() narrowed to literal-string under Laravel 13, and the three affected call sites were rewritten using decrement()/increment()/incrementEach(). This is exactly the class of change that would otherwise have surfaced as a runtime TypeError or a static-analysis wall in our own code, and it is already done for us.Technical risk
^2.0 packages slipping or shipping a divergent minor leaves us unable to resolve. We should treat the trio as a single locked artefact set.decrement()/increment()/incrementEach() changes execution shape in ThreadObserver::created(), deleted(), and restored(). incrementEach() is a single atomic statement where the prior code was a concatenated DB::raw() — this is strictly safer under concurrency. ThreadDeletionDriftTest still asserts board.post_count matches recountBoard across soft delete and restore, which is the correct invariant. Residual risk is only for observers our own code extends or overrides.scopeSearch() qualifier removal (low now, latent later). Dropping the getTable() prefix from the ranking CASE is safe because the scope never joins today. It becomes a correctness bug the moment a future maintainer composes scopeSearch() with a join. This needs a code comment or a guard test, not a code change.Key questions
laravel-modules-core ^2.0 and laravel-modules-interactions ^2.0 published and stable as of this tag, or does the ^2.0 constraint currently resolve to nothing?ThreadObserver, extend Thread, or compose scopeSearch() with a join? If so, the SQL rewrites are not fully covered by upstream tests.Stack fit
^2.0 majors is the right shape for a modular product. It does mean our composer.json should pin all three with matching constraints and a single upgrade ticket.Migration path
^2.0 siblings together, resolve sluggable at ^12.0 || ^13.0.DB::raw() literals. That ordering is correct and removes the most common major-upgrade failure mode.literal-string narrowing will flag any DB::raw() in our own application the same way it flagged these three spots upstream.Compatibility
Thread scopes are ours to prove.^12.0 || ^13.0 disjunction is the one deliberate compatibility escape hatch in the constraint set; use it to defer that bump rather than let it block resolution.Sequencing
laravel-modules-core ^2.0 and laravel-modules-interactions ^2.0 are published and resolvable as a set.DB::raw() narrowing failures in our code.^2.0 together; resolve sluggable last.recountBoard invariant) plus any custom observer overrides.Maintenance
spatie/laravel-package-tools scaffolding, no exotic extensions. Fewer moving parts to babysit than a typical first-party module.^2.0 alignment adds a coordination cost to every future major: three changelogs to read, three release dates to align.Support
board.post_count vs recountBoard across soft delete and restore). That is a maintainer who understands their own invariants.ozankurt/* sibling packages. Our exposure to this package's quality is now partly exposure to those two packages' quality. Vendor diligence should cover all three.scopeSearch() qualifier removal is undocumented as a caveat and lives only in release notes. We should not expect upstream documentation to prevent a future maintainer from composing that scope with a join.Scaling
incrementEach() consolidates post-count updates into a single atomic statement. That is a marginal write-scaling and lock-contention improvement, not a step change.Failure modes
^2.0 packages lags, composer update cannot resolve and the whole upgrade stalls. Detect early — this is the most likely failure and it happens before any code runs.modules-core, is unsupported and will produce confusing failures. Enforce the trio as one atomic change in composer.json.incrementEach()/decrement()/increment() rewrite, but any application code that overrides or reimplements ThreadObserver::created()/deleted()/restored() inherits the old splicing risk. Audit overrides before upgrading.scopeSearch() would produce an ambiguous-column error or wrong ranking. Cheap to prevent with a comment or guard test at adoption time.Ramp-up
scopeSearch() qualifier assumption. Both are capture-in-a-comment items, not training items.literal-string narrowing will surface in our own DB::raw() calls, and scopeSearch() must never be composed with a join. That single note converts three latent failure modes into guarded ones at near-zero cost.How can I help you explore Laravel packages today?