Repository navigation
Description
Summary
Expand AppContext from a minimal mode-only object into the true execution state holder. AppContext should represent the full runtime identity of a single application execution, with typed accessors that delegate to its own DiContainer.
Parent Issue
Subtask of #373 (Refactor App Bootstrapping & DI Ownership Model).
Depends on:
- Convert HttpRequest/HttpResponse from static to instance-based #454 (HttpRequest/HttpResponse instance-based) — Request/Response must be real instances before AppContext can expose them
- Split Di into static facade + instance-based DiContainer #455 (Di facade + DiContainer split) — AppContext must hold its own DiContainer
Current State
AppContext only holds $mode:
class AppContext
{
private string $mode;
// that's it
}All execution state is scattered: Config in DI, Request/Response in static properties, Environment in a static singleton, routes in DI, console app on the adapter instance. There's no single object that represents "this execution."
Proposed Design
AppContext delegates to its own DiContainer — no dual registration, no duplicated state:
class AppContext
{
private string $mode;
private string $baseDir;
private DiContainer $container;
public function __construct(string $mode, string $baseDir, DiContainer $container)
{
$this->mode = $mode;
$this->baseDir = $baseDir;
$this->container = $container;
}
public function getMode(): string { return $this->mode; }
public function getBaseDir(): string { return $this->baseDir; }
public function getContainer(): DiContainer { return $this->container; }
public function getEnvironment(): Environment
{
return $this->container->get(Environment::class);
}
public function getConfig(): Config
{
return $this->container->get(Config::class);
}
public function getRequest(): Request
{
return $this->container->get(Request::class);
}
public function getResponse(): Response
{
return $this->container->get(Response::class);
}
public function getRoutes(): RouteCollection
{
return $this->container->get(RouteCollection::class);
}
public function getConsoleApp(): Application
{
return $this->container->get(Application::class);
}
}Tasks
Core changes
- Add
$baseDirto AppContext (move from staticApp::$baseDir) - Add
DiContainerreference to AppContext (from Split Di into static facade + instance-based DiContainer #455) - Add typed accessor methods:
getEnvironment(),getConfig(),getRequest(),getResponse(),getRoutes(),getConsoleApp() - Register AppContext itself in DI so it's accessible via
Di::get(AppContext::class)
Environment migration
- Remove
Environment::$instancestatic singleton andgetInstance() - Make Environment DI-managed (one instance per execution, loaded from that execution's
.env) - Update
LoadEnvironmentStageto create and register Environment in DI - Update
env()helper to resolve from DI instead ofEnvironment::getInstance()
App::$baseDir migration
- Remove static
App::$baseDir - Store
baseDirin AppContext - Update
App::getBaseDir()to resolve fromDi::get(AppContext::class)->getBaseDir() - Update
base_dir()helper if one exists
AppFactory update
- Create
DiContainer+AppContexttogether increateInstance() - Set container as current via
Di::setCurrent() - Register AppContext in its own container
Optional: update boot stages
- Boot stages already receive AppContext as parameter — optionally update them to use
$context->getConfig()instead ofDi::get(Config::class)for clarity
What does NOT change
- Helper functions (
config(),cookie(),server(), etc.) — still useDi::get()internally - Factories — still use
Di::get(self::class)->resolve() - Controller autowiring — still works through
Di::autowire() $_ENVsuperglobal — stays process-level (butEnvironment::$envContentis per-execution)
Acceptance Criteria
- AppContext holds mode, baseDir, and a DiContainer reference
- Typed accessors delegate to the container (single source of truth, no dual registration)
Environmentis no longer a static singleton — it's DI-managed and execution-scopedApp::$baseDiris no longer static — it's stored in AppContext- All existing tests pass
- PHPStan passes
Context
This completes the #373 vision: "AppContext represents the runtime identity of a single application execution." After this ticket, each application execution is a self-contained AppContext with its own DiContainer, Environment, Config, Request, Response, and routes. No static state leaks between executions.
Activity
[quantum-php#456] Expand AppContext with baseDir, DiContainer, and ty…
[quantum-php#456] Wire AppContext through AppFactory and adapters
[quantum-php#456] Replace App::setBaseDir with App::setContext/getCon…
[quantum-php#456] Register Environment in DI, migrate all callers fro…
[quantum-php#456] Remove Environment singleton, make appEnv an instan…
[quantum-php#456] Use createArrayBacked for dotenv, simplify Environm…
[quantum-php#456] Add environment() helper and env check shorthand me…
[quantum-php#456] Expand AppContext as execution state holder
[quantum-php#456] Guard current_module() against early boot when Requ…
[quantum-php#456] Add getRoutes() accessor to AppContext and tests fo…