Philosophy
Winter rests on four principles. They shape how the code looks and explain why the framework is safe to hand to a team: less hidden behaviour, less routine, predictable performance, and no decisions imposed on you.
Explicit beats implicit
Behaviour lives in the code, not in configuration you have to keep in your head. A route is an attribute on a method; body binding is an attribute on a parameter; a table column is an attribute on a property. What you see is what runs.
#[RequestMapping('auth')]
class AuthController extends Controller
{
#[PostMapping('login')]
public function login(#[RequestJson, Valid] LoginRequest $req): ResponseEntity
{
// route, method, binding and validation — all visible right here
return ResponseEntity::ok(/* ... */);
}
}The test is simple: to understand what a method does you never have to open another file. No route list, no schema description, no validation rules kept elsewhere — and when the method is renamed there is nothing left behind to drift out of sync.
CLI instead of boilerplate
The generator writes the routine. call make creates components from templates,
already in the right namespace and the right directory:
php call make -c .User # controller
php call make -s .User # service
php call make -r .User # repository
php call make -P .Import # process
php call make -N .Queue # daemonThe same call starts the server, prints the route table, runs migrations, launches
processes and installs shell completion. One tool for the whole life of the project
— see Console.
Performance by default
Fast is the resting state, not the result of tuning.
The expensive work happens once. The application is resident: scanning the project, building the container and compiling the route table all happen before the first request arrives. The request itself builds nothing — it only runs.
Route lookup is O(1). Static paths resolve through a map; dynamic ones through one combined regular expression per chunk, rather than by trying routes one at a time.
Waiting does not block. Requests are served by coroutines: while one waits on the database or an external call, the worker runs another. A worker does not need a thread per client.
The cache is about boot, not about requests
With DEBUG=false the project’s class list is cached, so a restart does not walk
the disk. With DEBUG=true the cache is off so a new class is picked up without a
manual rebuild.
In neither mode does scanning happen per request — only at startup. That is why
a code change is visible after a restart; under call run dev the restart is done
for you when a file changes.
Mechanism, not policy
The framework gives you a pipeline and the points to hook into it. It does not decide for you what is correct.
The logger masks values under well-known keys — it does not try to guess what counts as a secret in your domain. Middleware receives the request before the controller — which authorisation check you put there is not dictated. There is a connection pool — the retry strategy on failure is yours.
The reason is plain: a policy baked into a framework is right until the first project where it is wrong, and then it has to be worked around. A mechanism handed outwards never has to be.
In practice this means nearly everything in Winter is an interface or a base class you can replace. If it feels like the framework decided something for you, there is almost certainly a place nearby where that decision is overridden.
What a team gets
Shorter onboarding. The stereotypes — Controller, Middleware, Repository,
Process, Daemon — impose the same shape on every project. A developer who knows
one reads the next straight away.
Less drift. There are no parallel configuration files quietly diverging from the code: behaviour and code are the same file.
Easier review. A controller diff is enough to see the new route, its parameters and its validation rules. You do not have to go hunting through the repository for context.
Next
- Key concepts — what these principles look like in code
- Ecosystem — what the framework is assembled from
- Quick start — try it