Getting started

Installation

Winter is a library, not a skeleton with a mandatory layout. A project is built out of four files, and every one of them is explained below: what it does and why it is there.

Requirements

Requirement Value
PHP 8.4 or newer
Composer 2.x
Extensions ext-pcntl, ext-posix, ext-fileinfo
For a web application ext-swoole

The web tier runs on Swoole only

Winter does not use PHP’s built-in server. If the application declares #[EnableWeb], starting it requires the Swoole extension and refuses without it:

call run with a web tier needs ext-swoole (pecl install swoole)

That includes development mode — it differs only by watching files; the server is the same one.

An application without a web tier — only processes, daemons or the scheduler — does not need Swoole.

The remaining extensions are installed for specific jobs:

Extension What for
ext-pdo Database access
ext-simplexml Parsing XML request bodies
ext-bcmath, ext-decimal Exact numeric types in parameter binding
ext-shmop Passing data between processes in Swoole mode

Checking the environment

php -v shows the version, php -m the extension list. A missing swoole in that list is the most common reason the first run fails.

Two paths

Path When
Ready skeletoncreate-project A new project; you want a working skeleton in one command
From scratchrequire winter-kernel Adding Winter to an existing project, a custom layout, or wanting to understand every file

The ready skeleton

bash
composer create-project flytachi/winter my-app
cd my-app
php call run dev

That is all. After installing, Composer runs storage init and cfg init itself, so the directories and a .env with a fresh WINTER_KEY are already in place.

The skeleton holds exactly what is assembled by hand below: bootstrap.php with the application class, the call launcher, the PSR-4 mapping Main\ → main/, a ready MainController, and monolog/monolog in require-dev so logging works out of the box.

You can stop reading here

If the skeleton suits you, move on to the Quick start. The section below is for people who need to assemble a project by hand, or who want to know what it is made of.

Building a project from scratch

1. Directory and dependency

bash
mkdir my-app && cd my-app
composer require flytachi/winter-kernel

2. Code directory and autoloading

Winter imposes nothing: the directory and the namespace are yours to pick — in the examples below they are main/ and Main\.

bash
mkdir main

Now tie them together in composer.json:

composer.json
{
  "autoload": {
      "psr-4": {
          "Main\\": "main/"
      }
  },
  "require": {
      "php": ">=8.4",
      "flytachi/winter-kernel": "^4.0"
  }
}
bash
composer dump-autoload

3. The application class — bootstrap.php

This is the only configuration file. It pulls in the autoloader and declares what the application consists of:

bootstrap.php
<?php

declare(strict_types=1);

use Flytachi\Winter\Kernel\App\Attribute\EnableWeb;
use Flytachi\Winter\Kernel\WinterApplication;

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

#[EnableWeb]
final class Application extends WinterApplication
{
  public static function main(array $argv): never
  {
      parent::run($argv);
  }
}

There are no hooks here to override — everything else lives in ordinary classes the scanner finds. What else can be declared alongside #[EnableWeb] is on the Components page.

4. The launcher — call

Every command goes through it, including starting the server:

call
#!/usr/bin/env php
<?php

if (PHP_VERSION_ID < 80400) {
  echo "Please use PHP version 8.4 or higher. Current: " . PHP_VERSION . "\n";
  exit(1);
}

chdir(__DIR__);
require './bootstrap.php';

Application::main($argv);
bash
chmod +x call

chdir() is not optional here: it anchors relative paths — .env, storage/, resources/ — to the project directory rather than to wherever you invoked the command from.

5. Environment and directories

bash
php call cfg init       # .env from the template + a fresh WINTER_KEY
php call storage init   # the service directories inside storage/

The resulting .env is minimal:

.env
WINTER_KEY=<generated key>
TIME_ZONE=UTC
DEBUG=true

The rest — database, logs, server settings — is added as you need it; see Configuration.

6. The first controller

bash
php call make -c .Main   # → main/MainController.php

The generator writes a working stub — enough to check that everything came together.

What you end up with

text
my-app/
├── bootstrap.php     — the application class: what it consists of
├── call              — the entry point for every command
├── composer.json     — autoloading and dependencies
├── .env              — environment
├── main/             — your code
└── storage/          — service directories

A resources/ directory for templates and translations appears when you need it.

The first run

bash
php call run dev

The server listens on port 8000 on all interfaces — open http://localhost:8000.

The word dev turns on file watching: change any .php and the application restarts itself. In production you run it without — php call run.

Address and port are overridden by flags:

bash
php call run dev --port=9000
php call run dev --host=127.0.0.1 --port=9000   # local only

Cloning an existing project

After git clone the skeleton is already there, but everything that does not go into the repository has to be recreated:

bash
composer install
chmod +x call

php call cfg env -i       # create .env if it is missing
php call cfg key -g       # generate your own WINTER_KEY
php call storage init     # create the directories inside storage/

php call run dev

Every installation has its own key

WINTER_KEY does not travel from someone else’s project or from the repository — generate it locally. .env is not committed for the same reason.

Worth having straight away

Shell completion — suggestions for commands and their arguments:

bash
php call cfg completion -i

Docker configurationDockerfile, docker-compose.yml and a docker/ directory with a Swoole entrypoint:

bash
php call cfg docker

The mode is chosen by the DEV variable, and it is off by default:

bash
docker compose up            # production mode
DEV=true docker compose up   # development: restart on file change

Next