Getting started

Configuration

There is no config/ directory in Winter. Values live in .env, the application’s make-up is declared with attributes on the entry class, and everything else is described by ordinary classes the scanner finds. Nothing has to be registered.

Values .envMake-up #[Enable*] attributesBehaviour configurer classes

Three levels

Configuration is split across three places, and the division is simple.

Where What lives there Example
.env Values that change between environments database host, log level
The application class What the application consists of #[EnableWeb], #[EnableScheduler]
Configurer classes Behaviour expressed as code CORS, server settings, beans

Why there are no config files. Anything that is a value belongs to the environment and changes without a rebuild. Anything that is a decision — which implementation to use, how to assemble an object, which origins to allow — is expressed as code, where types and editor hints work. The intermediate layer of arrays in PHP files is deliberately absent.

.env — the values

The file is created from a template by php call cfg init and holds three lines:

.env
WINTER_KEY=<generated key>
TIME_ZONE=UTC
DEBUG=true
Variable Default What it sets
WINTER_KEY The project secret: signatures, tokens. Generated locally
TIME_ZONE UTC Default timezone
DEBUG false Development mode

What `DEBUG` actually changes

It is not just “show errors”. The variable switches four things:

  • error display — at false PHP hides them and a failing script goes quiet;
  • the error response body — stack traces and debug data reach the client only at true;
  • the class-list cache — at false the project is scanned once and the result is written to di.php; at true the cache is not used at all;
  • #[Async] proxies — at true they are regenerated when the source changes.

In production: false, always. At true the application’s internals leak outwards.

Logging

Variable Default What it sets
LOG_LEVEL unset — output disabled Minimum level to record
LOG_OUTPUT auto auto, stdout, stderr, file, syslog, null
LOG_FORMAT line line or json
LOG_FILE storage/logs/<channel>.log Path when LOG_OUTPUT=file
LOG_FILE_MAX 30 How many days of rotated files to keep
LOG_COLOR auto auto, always, never

Any of them can be set for one channel with the LOG_{CHANNEL}_ prefix:

.env
LOG_LEVEL=info
LOG_HTTP_LEVEL=warning     # from requests — problems only
LOG_SYS_OUTPUT=file        # system events into a file

More on the Logging page.

Web server

These apply only when the application declares #[EnableWeb].

Variable What it sets
SERVER_PROFILE Memory profile: stable, balance, performance, stress
SERVER_WORKERS Number of worker processes
SERVER_TASKS Number of Swoole task processes
SERVER_MAX_CONCURRENCY Concurrent requests per worker
SERVER_MAX_CONNECTIONS Concurrent connections
SERVER_MAX_REQUEST Requests before a worker is replaced
SERVER_MAX_REQUEST_GRACE Jitter so workers do not all recycle at once
SERVER_MAX_REQUEST_SIZE Largest request in bytes
SERVER_REQUEST_TIMEOUT Request deadline in seconds
SERVER_IDLE_TIMEOUT When to close a silent connection
SERVER_MEMORY_LIMIT Memory ceiling for each worker
SERVER_MEMORY_TRIM When to return unused memory to the system

What each one means and how they relate is on the Web-layer configuration page. Address and port are not set by variables — they arrive as the --host and --port flags.

Everything else

Variable What it sets
WINTER_BANNER off removes the startup banner
PPA_POOL_TELEMETRY How often pool statistics are published; 0 disables it

Database variables are declared by you

The core does not read DB_HOST or anything like it — connecting is your configuration class’s job, and which variables it asks for is your decision. The convention is to read them through env(); see Database.

The application manifest

What the application consists of is declared with attributes on the entry class:

bootstrap.php
#[EnableWeb]
#[EnableScheduler]
#[EnableProcess(\Main\Process\QueueWorker::class)]
final class Application extends WinterApplication
{
  public static function main(array $argv): never
  {
      parent::run($argv);
  }
}

Every available attribute and how they combine is on the Components page.

The one hook — configure()

You need it only when the project layout is unusual: this method runs before the scan and decides where the scanner should look.

bootstrap.php
use Flytachi\Winter\Kernel\App\ApplicationArguments;
use Flytachi\Winter\Kernel\Kernel;

protected static function configure(ApplicationArguments $args): void
{
  Kernel::init(
      pathRoot:     __DIR__,
      pathResource: __DIR__ . '/assets',
      pathStorage:  '/var/lib/my-app',
  );
}

Without an override the paths are derived from where the application class sits, and for a normal layout there is no reason to touch it.

Configurer classes

Everything else is described by ordinary classes. They need no registration — the scanner finds them by the interface they implement or the attribute they carry.

What to configure With what
Container bindings, factories, values from .env #[Configuration] with #[Bean] methods
CORS and web-server settings A class extending WebConfigurerAdapter
Extra log channels A class implementing LoggingConfigurer
Plugins The #[Import] attribute on the application class
Diagnostic endpoints The #[EnableActuator] attribute
main/AppConfig.php
#[Configuration]
final class AppConfig
{
  #[Bean]
  public function cache(#[Value('REDIS_URL')] string $url): CacheInterface
  {
      return new RedisCache($url);
  }
}

Each has its own page: Dependency injection, Web-layer configuration, Logging, Packages.

Boot order

The same for every entry point — the server and a console command alike:

text
1. configure()      — paths, .env, timezone, logging
2. Container        — created empty
3. One project walk:
    · classes carrying a scope         → container
    · #[Configuration] / #[Bean]       → factories
    · WebConfigurer, LoggingConfigurer → collected
    · the scope graph                  → checked
4. #[Singleton] ↔ #[Request] conflict check
5. Apply what was found: logging, CORS, plugins, actuator
6. Then either the server or the console command

The project walk is one, not one per kind of configuration. Two consequences follow:

  • the order in which configurer classes are declared does not matter — they are collected in a single pass and applied afterwards;
  • a new configurer is picked up by a restart, like everything else.

Scope conflicts are caught here

Step 4 walks the dependency graph and refuses to start the application when a #[Singleton] holds a #[Request] object: the first request’s data would otherwise be frozen inside it for the worker’s whole life. The log shows the chain A → $prop: B.

Managing the key

bash
php call cfg key -g     # generate a new one
php call cfg key -s     # show the current one
php call cfg env -s     # show every environment variable

Next