You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Plugin system → Service Provider model (Wheels 4.0) #1917
Replace the zip-based, mixin-injection plugin system with a Service Provider model in Wheels 4.0. Plugins would use register(container) and boot(app) instead of having methods silently injected via StructAppend into component variable scopes.
Merit Assessment
Recommendation: Proceed — this is the correct architectural direction, but the 3.x bridging issues must land first.
Addresses real pain points: The current system in Plugins.cfc uses StructAppend(variablesScope, mixins, true) (line 313) to inject methods, meaning any two plugins defining the same method name silently overwrite each other. There is zero namespacing, zero collision detection, and zero warning. This is a real source of production bugs.
DI container already exists: Injector.cfc (297 lines) provides map().to().asSingleton(), asRequestScoped(), auto-wiring, circular dependency detection, and onDIcomplete() callbacks. The service() and injector() helpers in Global.cfc (lines 997-1027) give controllers/models access. The infrastructure for the Service Provider register(container) call is already in place.
Middleware pipeline is ready: Pipeline.cfc implements the handle(request, next) chain pattern. MiddlewareInterface.cfc defines the contract. The boot(app) method can register middleware via app.addMiddleware() without new pipeline work.
Middleware pipeline — Pipeline.cfc and MiddlewareInterface.cfc exist
Phase 1 — Foundation (3.x, do first):
Add plugin.json manifest support alongside box.json #1976 — plugin.json manifest support. Extend $pluginMetaData() (Plugins.cfc lines 184-196) to read plugin.json alongside box.json. This unlocks declarative metadata for all subsequent features.
Phase 2 — Lifecycle & DI (3.x, after Phase 1):
2. #1979 — Lifecycle hooks (onPluginLoad, onPluginActivate). Modify $pluginsProcess() (lines 146-178) and onapplicationstart.cfc (lines 235-246).
3. #1980 — Plugin DI integration via services() method. Bridges legacy plugins to the container without requiring the full ServiceProvider pattern.
Phase 3 — Service Provider Bridge (3.x, after Phase 2):
4. #1983 — ServiceProviderInterface with register(container) / boot(app). The key bridge component.
5. #1984 — Deprecation warnings for mixin-only plugins in dev mode. Only meaningful after #1976 and #1983.
Phase 4 — Full 4.0 Transition:
6. #1966 — Module system and package format (replaces zip extraction)
7. #1967 — Interface-driven design contracts
8. #1968 — Engine adapter modules
9. #1969 — Legacy compatibility adapter (wheels-legacy-plugin-adapter)
Current Codebase State
Key files and their current responsibilities:
File
Lines
Role
4.0 Impact
vendor/wheels/Plugins.cfc
420
Zip extraction, mixin injection, dependency checking
Major rewrite — extraction logic removed, mixin injection deprecated
vendor/wheels/Injector.cfc
297
DI container (map/bind/resolve, singleton + request scoping)
Stable — becomes primary plugin integration point
vendor/wheels/middleware/Pipeline.cfc
59
Middleware chain execution
Stable — plugins register middleware via boot()
vendor/wheels/middleware/MiddlewareInterface.cfc
19
Middleware contract
Stable
vendor/wheels/events/onapplicationstart.cfc
~270
Bootstrap: loads plugins at lines 235-246, mixins at 241-246
Modified — add service provider register/boot phases
vendor/wheels/Global.cfc:$loadPlugins()
lines 2767-2786
Creates Plugins object, extracts mixins to app scope
Modified — add service provider collection
Mixable targets that currently receive injected methods: application, dispatch, controller, mapper, model, base, sqlserver, mysql, postgresql, h2, test (Plugins.cfc line 15).
boot(app) context definition: The epic references app.addMiddleware() and app.modelTrait() but neither API exists yet. The boot() parameter needs a concrete definition — recommend a lightweight AppContext.cfc that wraps the mapper, middleware pipeline, DI container, and a new modelTrait() registration mechanism.
Plugin discovery: The current system discovers plugins via zip files in a plugin directory. The 4.0 module system (Wheels 4.0: Module system and package format #1966) should support Composer/ForgeBox-style package resolution. This is a significant design decision that should be specified early.
Testing: The test infrastructure (vendor/wheels/tests/) has no plugin-specific test specs currently. Each phase should include tests — especially for dual-mode (legacy + ServiceProvider) plugins.
BoxLang compatibility: Plugins.cfc already has BoxLang-specific code paths (lines 53-85, 101-103, 138-139). Any new plugin loading logic must be tested on both Lucee and BoxLang.
Automated analysis by @claude — review and adjust as needed.
Activity
bpamiri commentedon Mar 15, 2026
3.x Incremental Modernization Plan
Filed 10 issues to build the foundation for Service Providers incrementally in 3.x, organized in three backward-compatible phases:
Phase 1 — Cleanup & Foundation
plugin.jsonmanifest support alongside box.jsonPhase 2 — DI Integration & Lifecycle
Phase 3 — Migration Path (3.x → 4.0 bridge)
After Phase 3 ships, this issue (#1917) becomes: "remove mixin injection as default, require Service Providers" for 4.0.
bpamiri commentedon Mar 16, 2026
Analysis
Synopsis
Replace the zip-based, mixin-injection plugin system with a Service Provider model in Wheels 4.0. Plugins would use
register(container)andboot(app)instead of having methods silently injected viaStructAppendinto component variable scopes.Merit Assessment
Recommendation: Proceed — this is the correct architectural direction, but the 3.x bridging issues must land first.
Plugins.cfcusesStructAppend(variablesScope, mixins, true)(line 313) to inject methods, meaning any two plugins defining the same method name silently overwrite each other. There is zero namespacing, zero collision detection, and zero warning. This is a real source of production bugs.Injector.cfc(297 lines) providesmap().to().asSingleton(),asRequestScoped(), auto-wiring, circular dependency detection, andonDIcomplete()callbacks. Theservice()andinjector()helpers inGlobal.cfc(lines 997-1027) give controllers/models access. The infrastructure for the Service Providerregister(container)call is already in place.Pipeline.cfcimplements thehandle(request, next)chain pattern.MiddlewareInterface.cfcdefines the contract. Theboot(app)method can register middleware viaapp.addMiddleware()without new pipeline work.Implementation Plan (Epic-Level)
The 3.x bridge work (Phase 3) must land before the 4.0 full transition. Here is the recommended sequencing:
Already done:
Injector.cfcis implemented and integratedPipeline.cfcandMiddlewareInterface.cfcexistPhase 1 — Foundation (3.x, do first):
plugin.jsonmanifest support. Extend$pluginMetaData()(Plugins.cfc lines 184-196) to readplugin.jsonalongsidebox.json. This unlocks declarative metadata for all subsequent features.Phase 2 — Lifecycle & DI (3.x, after Phase 1):
2. #1979 — Lifecycle hooks (
onPluginLoad,onPluginActivate). Modify$pluginsProcess()(lines 146-178) andonapplicationstart.cfc(lines 235-246).3. #1980 — Plugin DI integration via
services()method. Bridges legacy plugins to the container without requiring the full ServiceProvider pattern.Phase 3 — Service Provider Bridge (3.x, after Phase 2):
4. #1983 —
ServiceProviderInterfacewithregister(container)/boot(app). The key bridge component.5. #1984 — Deprecation warnings for mixin-only plugins in dev mode. Only meaningful after #1976 and #1983.
Phase 4 — Full 4.0 Transition:
6. #1966 — Module system and package format (replaces zip extraction)
7. #1967 — Interface-driven design contracts
8. #1968 — Engine adapter modules
9. #1969 — Legacy compatibility adapter (
wheels-legacy-plugin-adapter)Current Codebase State
Key files and their current responsibilities:
vendor/wheels/Plugins.cfcvendor/wheels/Injector.cfcvendor/wheels/middleware/Pipeline.cfcboot()vendor/wheels/middleware/MiddlewareInterface.cfcvendor/wheels/events/onapplicationstart.cfcvendor/wheels/Global.cfc:$loadPlugins()Mixable targets that currently receive injected methods:
application, dispatch, controller, mapper, model, base, sqlserver, mysql, postgresql, h2, test(Plugins.cfc line 15).Dependencies & Risks
wheels-legacy-plugin-adapter(Wheels 4.0: Legacy compatibility adapter and migration path Issue #1969 #1969) is essential — it should shim the oldStructAppendbehavior for plugins that haven't migrated.boot(app)context definition: The epic referencesapp.addMiddleware()andapp.modelTrait()but neither API exists yet. Theboot()parameter needs a concrete definition — recommend a lightweightAppContext.cfcthat wraps the mapper, middleware pipeline, DI container, and a newmodelTrait()registration mechanism.vendor/wheels/tests/) has no plugin-specific test specs currently. Each phase should include tests — especially for dual-mode (legacy + ServiceProvider) plugins.Plugins.cfcalready has BoxLang-specific code paths (lines 53-85, 101-103, 138-139). Any new plugin loading logic must be tested on both Lucee and BoxLang.Automated analysis by @claude — review and adjust as needed.
bpamiri commentedon Mar 28, 2026
Resolved by Package System (PR #1995)
The package system that landed in PR #1995 (merged 2026-03-26) implements the full scope of this epic:
ServiceProviderInterfacewithregister()/boot()vendor/wheels/ServiceProviderInterface.cfcplugin.json)package.jsonwithprovidesdeclarations (mixins, services, middleware)packages/→vendor/activation model via copy or symlinkregister(container)provides.middlewarein manifest +boot(app)controller,view,model,global,noneThree first-party packages shipped as proof: wheels-sentry, wheels-hotwire, wheels-basecoat — all with tests and CI.
The 3.x bridging sub-issues (#1975–#1984) were also closed as resolved.
Remaining Wheels 4.0 work (tracked separately)
These are orthogonal to the plugin→package transition and remain open.