Background components

Application components

A Winter application is not necessarily a web server. It is assembled from components, and the web is only one of four: long-lived processes, supervised worker fleets and a task scheduler can run alongside it. Any of them can be the only one.

Manifest #[Enable*] attributesStartup php call runWithout web yes

What components are and why

A component is a self-contained part of the application with its own way of working: the web answers requests, a process runs a loop, a daemon holds a fleet of workers, the scheduler starts tasks by the clock.

The problem. An application rarely has just one shape. A service with an API almost always needs something else: draining a queue, polling a device, cleaning old records at night. Usually that is solved from outside — separate scripts under cron, systemd units, a third-party supervisor. And then one application becomes several deployments: different startup methods, different configurations, different logs, with the shared code dragged between them as a package.

The solution. Winter describes the application’s make-up inside the application. The entry class lists with attributes what it consists of, and php call run raises all of it together, under one configuration, with a shared dependency container and shared logs. Need a worker — that is a line in the manifest, not a new deployment.

The manifest

Everything is declared with attributes on the application class:

bootstrap.php
<?php

use Flytachi\Winter\Kernel\App\Attribute\EnableDaemon;
use Flytachi\Winter\Kernel\App\Attribute\EnableProcess;
use Flytachi\Winter\Kernel\App\Attribute\EnableScheduler;
use Flytachi\Winter\Kernel\App\Attribute\EnableWeb;
use Flytachi\Winter\Kernel\WinterApplication;

require __DIR__ . '/vendor/autoload.php';

#[EnableWeb]                                  // the HTTP server
#[EnableScheduler]                            // scheduled tasks
#[EnableProcess(\Main\Process\SnmpPoller::class)]   // one worker
#[EnableDaemon(\Main\Process\Emails::class)]        // a fleet of workers
final class Application extends WinterApplication
{
  public static function main(array $argv): never
  {
      parent::run($argv);
  }
}

That is the whole launcher file. There are no hooks to override: settings live in ordinary classes the scanner finds — see Dependency injection and Web-layer configuration.

The four components

Attribute What it raises Repeatable
#[EnableWeb] The HTTP server no
#[EnableProcess(Class)] One long-lived process yes
#[EnableDaemon(Class)] A supervised fleet of workers yes
#[EnableScheduler] The #[Scheduled] task scheduler no

Repeatable means literally that: several different processes and several different daemons are declared with several lines.

php
#[EnableProcess(\Main\Process\SnmpPoller::class)]
#[EnableProcess(\Main\Process\Heartbeat::class)]
#[EnableDaemon(\Main\Process\Emails::class)]
#[EnableDaemon(\Main\Process\Webhooks::class)]

#[EnableWeb] takes neither an address nor a port — that is a property of the deployment, not of the application. The address is set by the --host / --port flags, by environment variables or by the web-layer configurer.

What is not a component

Two attributes look similar but raise nothing:

Attribute What it does
#[EnableAsync] Enables proxying of #[Async] methods — see Async calls
#[EnableActuator] Adds diagnostic endpoints to the web tier — see Actuator / Health

Startup

One command raises everything listed in the manifest:

bash
php call run       # production mode
php call run dev   # development: restart on .php changes

When the manifest includes the web tier, the HTTP server becomes the main process and the other components run beside it, under the same master’s supervision: a worker dies, it is brought back; the application is stopped, they all stop.

An application without a web tier

Remove #[EnableWeb] and the application comes up headless: processes, daemons and the scheduler only.

bootstrap.php
#[EnableScheduler]
#[EnableDaemon(\Main\Process\Emails::class)]
final class Application extends WinterApplication { /* ... */ }

Such an application still uses the container, the configuration, logging and database access — it simply does not listen on a port. That is the ordinary way to deploy a dedicated queue handler or a nightly routine alongside the main service.

An empty manifest is an error

At least one component has to be declared. An application with no #[Enable*] at all will not start and says so directly: No components declared, with the list of available attributes.

What to choose

The four tools are easy to confuse, because all of them “do something in the background”. They separate on the question of when the work should run.

You need to Tool
Do a piece of work without keeping the client waiting #[Async]
Run one endless loop: listen to a queue, poll a device A process
The same, but in several hands, with crashed ones restarted A daemon
Run on a schedule: every 5 minutes, every night at 3:00 The scheduler

The boundaries are worth spelling out, because that is where the mistakes happen:

#[Async] is not a background job. The method goes into its own coroutine and interleaves with the calling code on I/O waits, but the work stays inside the same worker and lives exactly as long as it does: a restart loses it, it will not move to another machine, and it has no queue. Good for not making the client wait; not good for guaranteeing an email is delivered — that needs a process or a daemon with a queue.

Process versus daemon. A process is one instance; a daemon is several under supervision, with a restart policy and scaling. If the work divides into independent units and one worker is not enough, take a daemon; if the task is one by nature — listening on a single socket, holding a single connection — take a process.

The scheduler versus a process with sleep(). A loop with sleep(300) inside a process looks like the simple answer, but with it you take responsibility for keeping a task from overlapping itself, for what happens after a restart, and for schedules expressed in wall-clock terms. The scheduler already handles all of that.

Next

  • Processes — one long-lived worker
  • Daemons — a supervised fleet of workers
  • Scheduler — tasks on a schedule
  • Async calls — not making the client wait for a slow operation
  • Runtime — how this executes under Swoole