CLI · Winter Console

Console — overview

Everything done around the project is done through one file — call. It starts the server, generates classes, applies the schema, manages background components and runs scripts of your own. One argument parser, one output format, one container.

Entry php callCommands 13 built inYour own Cmd / CmdCustom

Why a console

Development needs more than the request–response cycle: create a component, bring up the server, apply the schema, warm a cache, start a background handler, run a script once. Without a common tool that is a scatter of files in the project root, each with its own way of taking arguments and its own idea of how to print an error.

call gives one entry point: the same argument parsing, the same output, access to the container and to the application configuration — for the built-in commands and for yours alike.

How a call is structured

bash
php call <command> [subcommand] [arguments] [-flags] [--options[=value]]

Parsing splits $argv into three parts:

Part What it is Example
Arguments Positional values call make .Usermake, .User
Flags Single-character, they stack -csrc, s, r
Options --key or --key=value --port=9000, --mvc

The first argument is the command name; without one, help runs. Flags may be written separately or stuck together: -c -s -r and -csr are equivalent.

Short names

Four commands have aliases:

Alias Command
sc script
proc process
dmn daemon
sch schedule

Running it from anywhere

The call file contains a chdir(__DIR__), so the commands work from any directory — just give the path:

bash
php /srv/my-app/call mapping show

That is also what makes calls from systemd, cron and docker exec predictable: the working directory does not matter.

The commands

Development

Command What it does
make Creates components from templates
run Starts the application
mapping Shows the route table
script Runs commands of yours (sc)

Environment and data

Command What it does
cfg .env, the project key, Docker, shell completion
db Connection, migrations, the connection pool
storage The service directories
di The scanner cache and #[Async] proxies

Background components

Command What it does
process Processes: start, stop, state (proc)
daemon Daemons and their worker fleets (dmn)
schedule The scheduler and its task list (sch)

Service

Command What it does
help The command list, versions, help for one command
complete The internal entry point for shell completion; not called by hand

Help

bash
php call                 # the same as help
php call help            # the command list + PHP, kernel and project versions
php call help make       # detailed help for one command
php call make -h         # the same thing, as a flag

call help with no arguments also prints the environment — the kernel version, the PHP version, the SAPI and the project root. That is the first thing worth attaching to a “why does this not work for me” question.

Shell completion

bash
php call cfg completion       # print the script to stdout
php call cfg completion -i    # install it
php call cfg completion -if   # reinstall over the top

The shell is detected from $SHELL. For zsh the file goes to ~/.zsh/completions/_call and an fpath line is appended to ~/.zshrc; for bash, to ~/.bash_completion.d/call. Once installed, command names, subcommands and — where it makes sense — the project’s class names are completed.

A command of your own

A class extending Cmd. The scaffold is php call make -n .Sync:

main/Command/Sync.php
use Flytachi\Winter\Console\Inc\Cmd;

class Sync extends Cmd
{
  public static string $title = 'sync external data';

  public function handle(): void
  {
      $from = $this->args['options']['from'] ?? 'yesterday';

      self::printInfo("syncing since {$from}");
      // ...
      self::printSuccess('done');
  }

  public static function help(): void
  {
      self::printInfo('call script main.command.Sync --from=2026-01-01');
  }
}
bash
php call sc main.command.Sync --from=2026-01-01
Class member Role
handle() The command’s body
static $title The line in the call script list listing
static help() Help; printed on -h or --help automatically
init() An optional hook before handle()

The arguments arrive in $this->args — as those same three parts: arguments, flags, options.

Dependencies

The command is created by the container, so injection works as it does everywhere else:

php
class Sync extends Cmd
{
  #[Autowired] private ExchangeService $exchange;
  #[Autowired] private LoggerInterface $logger;

  public function handle(): void
  {
      $this->logger->info('sync started');
      $this->exchange->pull();
  }
}

Cmd or CmdCustom

Cmd CmdCustom
A title in the listing $title no
Handling -h / --help Automatic No
The help() method Required Not needed
When to take it A command people will use A one-off script

Both are found by the scanner and both are run through call script. The only difference is how much boilerplate is mandatory — see script.

`call <name>` will not run your command

The first-level names — make, run, db — belong to the framework: they resolve to kernel classes. Your commands live in your namespace and are called through call script <path.Class> or the short call sc.

That way your command’s name will never collide with a new framework command.

Output

There is no need to echo: Printer has ready-made shapes, and the whole console output looks alike:

Method What it gives
printSuccess() · printError() A success, and an error block with a stack trace
printInfo() · printWarning() The [i] and [!] marks
printKeyValue() An aligned key–value pair
printBadge() A line with a status on the right: OK, EXIST, FAILED
printStep() Progress of the [3/12] kind
printTitle() · printLabel() · printDivider() A heading, a subheading, a divider

An exception escaping handle() is caught and printed through printError() — the command does not crash into the console with a PHP stack trace.

Next