Application Lifecycle and Startup Hooks in Winter Boot
Every Winter Boot application starts from a single annotated class and a call to a runner. The framework then scans your code, builds the bean graph, calls lifecycle hooks, and hands control to the runtime — the built-in HTTP server, PHP-FPM, or the migration runner. Knowing what happens at each stage helps you put startup code in the right place, and explains why #[PostConstruct] and #[OnApplicationReady] run when they do.
The container follows a fixed, deterministic sequence every time your application boots.
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.
Load configuration
The runner reads application.yml (or application-{profile}.yml when a profile is set) from every directory in configDirectory, merging them in order. The result is available through ApplicationContext::getProperty*() before any bean is created.
Scan namespaces
The runner walks the namespaces listed in scanNamespaces (plus those contributed by enabled modules), skipping any prefixes in scanExcludeNamespaces. Every class carrying a known stereotype attribute is registered. If autoload is true, files Composer cannot autoload are required during the scan.
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.
Wire dependencies
For each instantiated bean the container resolves all #[Autowired] properties and #[Value] properties, injecting the correct objects and scalar values.
Call #[PostConstruct] methods
After a bean is constructed and its dependencies are injected, its #[PostConstruct] method is called. Use this hook for validation, connection setup, or anything else that depends on injected values.
Load modules
Modules enabled under the modules key in application.yml are initialized and started. See Module System.
Fire #[OnApplicationReady]
Every bean whose class is annotated with #[OnApplicationReady] has its onApplicationReady() method called. This is the place for work that needs the whole context — warming caches, seeding data, or checking connections.
Start serving
The runner hands off to the runtime — starting the built-in HTTP server, dispatching a single PHP-FPM request, or running migrations.
The entry-point class must be annotated with #[WinterBootApplication]. It tells the framework where the config files are (configDirectory) and which namespaces to scan (scanNamespaces), and optionally sets a profile, exclusions, and eager loading. See Configuration for every option.
Choose the runner class that matches how your application is deployed.
WinterWebSwooleApplication
Built-in HTTP server (recommended). Starts a long-running server with a
pool of worker processes. Required for #[Async], #[Scheduled], daemon
threads, and the local KV/queue stores. The right choice for production APIs.
PHP-FPM / Apache runner. Bootstraps the full context once per request
under a conventional web server. Use it only when you must run inside existing
PHP hosting; background features are not available.
Migration runner. Runs SQL or OpenSearch migrations and exits. Requires
--configDir and --sqlPath; --migrationType is optional. Skips modules
and #[OnApplicationReady] for a fast run. See Migrations.
One codebase can have several entry-point classes, each with its own config and scan settings. A common pair is the web server and a migration runner that ships in the same image:
#[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 return void (or declare no return type).
DatabaseConnectionPool.php
use dev\winterframework\core\context\ApplicationContext;
use dev\winterframework\stereotype\{Service, Autowired, PostConstruct, Value};
#[Service]
classDatabaseConnectionPool {
#[Autowired]
privateApplicationContext$appCtx;
#[Value('${db.poolSize}')]
privateint$poolSize;
privatearray$connections= [];
#[PostConstruct]
publicfunctioninit():void {
// $this->poolSize and $this->appCtx are already injected here
#[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
use dev\winterframework\stereotype\{Component, OnApplicationReady};
use dev\winterframework\stereotype\Autowired;
use dev\winterframework\core\app\ApplicationReadyEvent;
#[OnApplicationReady] runs once, in the master process. Code that needs to run inside each worker process, such as opening a per-worker connection, warming a worker-local cache, or flushing buffers on the way out, uses the worker lifecycle attributes instead. Both are class-level and only take effect with the WinterWebSwooleApplication runner.
#[OnWorkerStart]: the class implements WorkerStartEvent (onWorkerStart(int $workerId): void), called as each worker starts, after the framework’s own worker setup.
#[OnWorkerStop]: the class implements WorkerStopEvent (onWorkerStop(int $workerId): void), called as each worker stops (shutdown, reload, max_request recycling).
As with #[OnApplicationReady], the class must also carry #[Component], #[Service], or #[Configuration]. One class may carry both attributes.
WorkerResources.php
use dev\winterframework\stereotype\{Component, OnWorkerStart, OnWorkerStop};
use dev\winterframework\core\app\{WorkerStartEvent, WorkerStopEvent};
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.
ResourceConfig.php
use dev\winterframework\stereotype\{Configuration, Bean};
Libraries such as Kafka, Redis, or Doctrine plug into this lifecycle as modules. Enabled modules contribute their namespaces before the scan, are initialized after the context is built, and are started just before #[OnApplicationReady] fires:
config/application.yml
modules:
- module: dev\winterframework\kafka\KafkaModule
enabled: true
See Module System for the WinterModule contract and how to write your own.