Skip to main content
Every Winter Boot application begins with a single annotated class and a call to a runner. From that moment the framework scans your code, builds a complete bean graph, calls lifecycle hooks, and hands control to the runtime — whether that is a Swoole HTTP server, plain PHP-FPM, a CLI tool, or a migration runner. Knowing exactly what happens at each stage helps you write correct startup code and understand why the order of #[PostConstruct] and #[OnApplicationReady] matters.

Startup sequence

The container follows a fixed, deterministic sequence every time your application boots.
1

Bootstrap the runner

Instantiate a runner class (e.g. WinterWebSwooleApplication) and call run(MyApplication::class). The runner reads the #[WinterBootApplication] attribute to learn which config directories and namespaces to use.
2

Load configuration

The runner locates and parses every application.yml (and profile-specific variants) in the configured configDirectory paths. The merged result is available through ApplicationContext::getProperty*() before any bean is created.
3

Scan namespaces

The runner walks the PSR-4 namespaces listed in scanNamespaces, skipping any prefixes in scanExcludeNamespaces. Every class carrying a known stereotype attribute is recorded as a ClassResource in the registry. If autoload is true, classes that have not been loaded by Composer yet are loaded on demand during this scan.
4

Build the application context

The WinterApplicationContext is constructed from the scanned resources. If eager is true, every bean is instantiated immediately; otherwise beans are instantiated lazily on first access.
5

Wire dependencies

For each instantiated bean the container resolves all #[Autowired] properties and #[Value] properties, injecting the correct objects and scalar values.
6

Call #[PostConstruct] methods

After a bean is fully constructed and its dependencies are injected, any public non-abstract method annotated with #[PostConstruct] is called. Use this hook for validation, connection setup, or derived-field initialisation that depends on injected values.
7

Load modules

Modules listed in application.yml under the modules key are initialised. Each module can contribute its own beans and namespaces to the context.
8

Fire #[OnApplicationReady]

Every bean whose class is annotated with #[OnApplicationReady] has its onApplicationReady() method called. This is the correct place for work that must happen after the entire context is ready — seeding data, starting background threads, registering routes, or opening connections.
9

Start serving

The runner hands off to the underlying runtime — starting the Swoole HTTP server, dispatching a single PHP-FPM request, executing CLI logic, or running migrations.

#[WinterBootApplication]

The entry-point class must be annotated with #[WinterBootApplication]. This attribute is the single source of truth for all startup configuration.
MyApplication.php

Option reference

array
required
List of directory paths to search for application.yml and logger.yml.
array
required
PSR-4 [prefix, directory] pairs to scan for stereotype attributes.
bool
default:"false"
Load classes from disk if not already in memory during scanning.
array
default:"[]"
Namespace prefixes to skip entirely during scanning.
string | null
default:"null"
Active environment profile. Loads application-{profile}.yml overrides on top of the base configuration.
bool
default:"false"
Instantiate all beans at startup instead of lazily on first access. Results in a slower startup but a faster first request.

Application runner types

Choose the runner class that matches how your application is deployed.

WinterWebSwooleApplication

High-performance HTTP server powered by OpenSwoole. Starts a persistent multi-worker server that handles concurrent requests in a single PHP process. The right choice for production APIs and microservices.

WinterWebApplication

Traditional PHP-FPM / Apache web runner. Bootstraps the full context once per request under a conventional web server. Use when Swoole is unavailable or when integrating with an existing PHP hosting environment.

WinterCliApplication

Command-line runner. Boots the full DI context, fires #[OnApplicationReady], then returns control so your CLI logic can execute. Ideal for console commands, one-off scripts, and integration test harnesses.

WinterMigrationApplication

Database migration runner. Accepts --configDir, --sqlPath, and --migrationType CLI flags. Intentionally skips module loading and #[OnApplicationReady] events for a lean, fast migration run.

Multiple starters

A single codebase can expose several starter classes, each bootstrapping a different slice of the application. This is a first-class pattern in Winter Boot — ideal for separating test, web, and worker entry points.
Multiple starters

#[PostConstruct]

#[PostConstruct] is a method-level attribute. The container calls the annotated method immediately after the bean is instantiated and all #[Autowired] and #[Value] injections have been applied. The framework enforces these rules at startup:
  • The method must be public.
  • The method must not be abstract or a constructor.
  • The method must not declare a return type (it should return void).
DatabaseConnectionPool.php
Do not put initialisation logic that relies on injected values inside __construct. Injections happen after construction, so #[Autowired] and #[Value] properties are still null inside the constructor. Use #[PostConstruct] instead.

#[OnApplicationReady]

#[OnApplicationReady] is a class-level attribute. The framework calls onApplicationReady() on every annotated bean after the entire application context has been built, modules have been loaded, and the container is fully operational. The framework enforces these requirements at startup:
  • The class must also carry #[Component], #[Service], or #[Configuration].
  • The class must implement the ApplicationReadyEvent interface (which declares onApplicationReady(): void).
AppStartupTasks.php
#[OnApplicationReady] fires once per application boot, not once per request. In the Swoole runner it fires in the master process before the HTTP server starts accepting connections.

Shutdown hooks

Winter Boot automatically registers bean destroy methods to run on process exit. When a #[Bean] factory method declares a destroyMethod, the framework registers it with PHP’s shutdown mechanism. On process exit, all registered destroy methods are called in registration order.
Destroy methods must be public and accept no required parameters. The framework discovers them via reflection at startup and validates both constraints before the application begins serving.

WinterModule

WinterModule is the base contract for optional framework modules that extend Winter Boot with additional capabilities. Modules are declared in your application.yml and are initialised after the main application context has been built. Each module can register its own beans, scan additional namespaces, and integrate with the application context. Winter Boot’s built-in features — such as transaction management, caching, async tasks, and scheduling — are all delivered as modules.
application.yml — enabling modules
To create a custom module, implement the WinterModule interface and list your module class under modules in application.yml. The framework instantiates each module during the Load modules phase of startup and calls its initialisation methods in declaration order.
CustomAuditModule.php
application.yml — loading a custom module
Modules are the correct extension point when you need to share infrastructure beans (loggers, metrics clients, audit trails) across multiple Winter Boot applications in the same organisation.