The di command
At startup the framework walks the project and finds its classes — that is the most
expensive part of booting. di manages the cache of that walk: it builds
it ahead of time, shows its contents and deletes it. It also builds the proxies for
#[Async].
Why
Walking the project reads every .php and includes those that declare a class. On an
average project that is tens of milliseconds and extra megabytes in the master
process’s memory — the process every worker is then forked from.
di build does that work once, ahead of time, and puts the result in a file. At
startup the application reads the prepared list instead of walking the disk.
Actions
| Action | What it does |
|---|---|
build |
Walks the project once: writes the cache, generates the #[Async] proxies, checks correctness |
clean |
Deletes the cache and every generated proxy |
show [pattern] |
Prints the classes from the cache; the pattern filters by FQCN substring |
async [pattern] |
Prints the #[Async] methods and whether a proxy was built for each |
php call di build
php call di clean
php call di show
php call di show Repository # only those containing "Repository"
php call di asyncbuild before starting
The main scenario is warming it up in a container, before the application starts. That
is exactly how the image cfg docker creates works:
if ! su-exec winter php /var/www/html/call di build; then
echo "entrypoint: 'call di build' failed — refusing to start" >&2
exit 1
fi
exec su-exec winter php /var/www/html/call run --port="$PORT"`build` is also a check
The command does not merely store a list: it parses every #[Async] method and
generates a proxy for it. A method that cannot be proxied that way fails the build —
here, with a comprehensible message and exit code 1, rather than in production
on the first call.
That is why it goes into the entrypoint before startup: a broken application gives one
intelligible line in docker logs instead of a container crash-looping.
build additionally warns when a class with #[Async] methods is created with new
— the proxy will not apply then. The check is textual and does not see new $class or
factories, so it is a warning specifically: it does not fail the build.
Where the cache lives
By default, in the system temp directory rather than in the project:
/tmp/flytachi.winter.volatile.<project-name>/di.php the class list
/tmp/flytachi.winter.volatile.<project-name>/async/ the #[Async] proxiesThe exact paths are printed by php call help di. Generated code does not end up in
the image and does not survive a machine reboot — that is a deliberate choice.
The practical consequence: cleaning storage/ to reset the cache is pointless; di clean is what does that.
With `DEBUG=true` the cache is not used at all
In development mode the application walks the project on every start, so that a new
class is picked up without a manual rebuild. A di build you made is simply ignored
in that mode.
If changes are “not visible” after a di build, check DEBUG first.
show without a cache
If there is no cache, show does not fail: it performs a live walk, prints the result
and warns that what it showed was not written to disk. That keeps the command useful
in development too.
async
php call di asyncIt shows every method marked #[Async] and the state of each one’s proxy. This is how
to answer the question “why does this method run synchronously”: if no proxy was
built, the call goes through directly.
Next
- Dependency injection — what lands in the container
- Asynchronous calls —
#[Async]and its proxy - cfg docker — where
di buildsits in a container’s startup - Console — overview — every command