Skip to content

The Module System: Extending Winter with Libraries

Winter Boot’s module system gives you a clean, attribute-driven mechanism for bundling infrastructure integrations — Redis, Kafka, Doctrine ORM, service discovery, and more — as self-contained, reusable units. Modules are discovered and loaded before the application starts, so each module can register its own beans, scan additional namespaces, and run any required initialisation without touching your application code. The WinterModule interface keeps the contract minimal: implement two lifecycle hooks and annotate your class with #[Module].

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 {
// initialise 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() void Called during the module loading phase. Use this hook to register beans, validate configuration, and verify required PHP extensions are present.

  • begin() void Called 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.

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 {
// ...
}

Declare modules in your application.yml under the top-level modules key. Each entry requires a module class name and an enabled flag.

application.yml
modules:
- module: 'dev\winterframework\telemetry\OpenTelemetryModule'
enabled: true
configFile: 'config/telemetry.yml' # optional path to module-specific config
- module: 'dev\winterframework\data\redis\RedisModule'
enabled: true
- module: 'dev\winterframework\kafka\KafkaModule'
enabled: false # set to false to temporarily disable

All official modules live in the suvera/winter-modules monorepo. Install the entire collection with a single Composer command:

Terminal window
composer require suvera/winter-modules

See the Modules overview for a full catalog with guidance on which module to pick for which use case.

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.

  1. Create the module class

    src/Module/AcmeMyModule.php
    <?php
    declare(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
    }
    }
  2. 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 // mark as primary bean
    );
    }
  3. Read module configuration

    The framework passes the raw moduleDef YAML entry to your #[Module] attribute. Use the ModuleTrait helper to retrieve 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';
    }
    }
  4. Enable the module in application.yml

    application.yml
    modules:
    - module: 'Acme\MyModule\AcmeMyModule'
    enabled: true
    configFile: 'config/acme.yml'
  5. Install via Composer

    Terminal window
    composer require acme/my-winter-module