The Module System: Extending Winter with Libraries
Modules package infrastructure integrations — Redis, Kafka, Doctrine ORM, service discovery, and more — as reusable units. Each module can register its own beans, add namespaces to the scan, and run its own setup without any changes to your application code. The contract is small: implement the two WinterModule hooks and annotate the class with #[Module].
The WinterModule Interface
Section titled “The WinterModule Interface”Every module must implement the WinterModule interface. The framework calls its two lifecycle methods during startup in a predictable order.
use dev\winterframework\core\app\WinterModule;use dev\winterframework\core\context\ApplicationContext;use dev\winterframework\core\context\ApplicationContextData;
class MyCustomModule implements WinterModule {
/** * Called during the module loading phase. * Register beans, validate configuration, check required extensions here. */ public function init(ApplicationContext $ctx, ApplicationContextData $ctxData): void { // set up resources }
/** * Called after all modules are loaded and the application context is ready. * Start background processes, connect to external services, etc. */ public function begin(ApplicationContext $ctx, ApplicationContextData $ctxData): void { // begin operations }}-
init()voidCalled during the module loading phase. Use this hook to register beans, validate configuration, and verify required PHP extensions are present. -
begin()voidCalled after all modules are loaded and the application context is fully ready. Use this hook to open connections, start background processes, or subscribe to events.
The #[Module] Attribute
Section titled “The #[Module] Attribute”Annotate every module class with #[Module]. This tells the framework how to scan the module’s namespaces and provides metadata used in log output.
use dev\winterframework\stereotype\Module;
#[Module( title: 'My Custom Module', // human-readable name shown in logs initMethod: 'init', // method called on load (default: 'init') destroyMethod: '', // method called on shutdown (optional) namespaces: [], // extra PSR-4 namespaces to scan)]class MyCustomModule implements WinterModule { // ...}Enabling Modules in application.yml
Section titled “Enabling Modules in application.yml”Declare modules in your application.yml under the top-level modules key. Each entry requires a module class name and an enabled flag. Most modules also need a configFile: a relative name is looked up in your config directories (so kafka-config.yml, not config/kafka-config.yml), and an absolute path is used as is. The file is read by the module itself; its keys are not added to application.yml properties.
modules: - module: 'dev\winterframework\telemetry\OpenTelemetryModule' enabled: true configFile: telemetry.yml # module config file, looked up in the config directory
- module: 'dev\winterframework\data\redis\RedisModule' enabled: true
- module: 'dev\winterframework\kafka\KafkaModule' enabled: false # set to false to temporarily disableFirst-Party Modules
Section titled “First-Party Modules”All official modules live in the suvera/winter-modules monorepo. Install the entire collection as described in the Installation guide.
See the Modules overview for a full catalog with guidance on which module to pick for which use case.
Building a Custom Module
Section titled “Building a Custom Module”Package any infrastructure concern — a third-party SDK, a custom cache layer, an internal service client — as a reusable, injectable module by following these steps.
-
Create the module class
src/Module/AcmeMyModule.php <?phpdeclare(strict_types=1);namespace Acme\MyModule;use dev\winterframework\core\app\WinterModule;use dev\winterframework\core\context\ApplicationContext;use dev\winterframework\core\context\ApplicationContextData;use dev\winterframework\stereotype\Module;#[Module(title: 'Acme My Module')]class AcmeMyModule implements WinterModule {public function init(ApplicationContext $ctx, ApplicationContextData $ctxData): void {// Validate required config, register beans}public function begin(ApplicationContext $ctx, ApplicationContextData $ctxData): void {// Start connections or background processes}} -
Register beans inside init()
Use
ApplicationContextData::getBeanProvider()to register beans that consumers can inject with#[Autowired]:public function init(ApplicationContext $ctx, ApplicationContextData $ctxData): void {$client = new AcmeClient(/* config */);$ctxData->getBeanProvider()->registerInternalBean($client,AcmeClient::class,true // replace any bean already registered for this class);} -
Read module configuration
The framework passes the module’s
application.ymlentry (including anyconfigFile) to your#[Module]attribute. Use theModuleTraithelper to load it:use dev\winterframework\util\ModuleTrait;#[Module(title: 'Acme My Module')]class AcmeMyModule implements WinterModule {use ModuleTrait;public function begin(ApplicationContext $ctx, ApplicationContextData $ctxData): void {$moduleDef = $ctx->getModule(static::class);$config = $this->retrieveConfiguration($ctx, $ctxData, $moduleDef);$host = $config['acme']['host'] ?? 'localhost';}} -
Enable the module in application.yml
application.yml modules:- module: 'Acme\MyModule\AcmeMyModule'enabled: trueconfigFile: acme.yml -
Publish it as a Composer package
Publish the module as its own package so applications can add it with a single command:
Terminal window composer require acme/my-winter-module