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 skeleton — create-project |
A new project; you want a working skeleton in one command |
From scratch — require winter-kernel |
Adding Winter to an existing project, a custom layout, or wanting to understand every file |
The ready skeleton
composer create-project flytachi/winter my-app
cd my-app
php call run devThat 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
mkdir my-app && cd my-app
composer require flytachi/winter-kernel2. 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\.
mkdir mainNow tie them together in composer.json:
{
"autoload": {
"psr-4": {
"Main\\": "main/"
}
},
"require": {
"php": ">=8.4",
"flytachi/winter-kernel": "^4.0"
}
}composer dump-autoload3. The application class — bootstrap.php
This is the only configuration file. It pulls in the autoloader and declares what the application consists of:
<?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:
#!/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);chmod +x callchdir() 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
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:
WINTER_KEY=<generated key>
TIME_ZONE=UTC
DEBUG=trueThe rest — database, logs, server settings — is added as you need it; see Configuration.
6. The first controller
php call make -c .Main # → main/MainController.phpThe generator writes a working stub — enough to check that everything came together.
What you end up with
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 directoriesA resources/ directory for templates and translations appears when you need it.
The first run
php call run devThe 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:
php call run dev --port=9000
php call run dev --host=127.0.0.1 --port=9000 # local onlyCloning an existing project
After git clone the skeleton is already there, but everything that does not go
into the repository has to be recreated:
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 devEvery 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:
php call cfg completion -iDocker configuration — Dockerfile, docker-compose.yml and a docker/
directory with a Swoole entrypoint:
php call cfg dockerThe mode is chosen by the DEV variable, and it is off by default:
docker compose up # production mode
DEV=true docker compose up # development: restart on file changeNext
- Quick start — your first route in five minutes
- Project structure — what is responsible for what
- Components — web, processes, daemons, scheduler
- Configuration — environment variables