Skip to content

Build1 publisher2 min readPublished

Symfony 8 swaps generated DI proxies for PHP 8.4's native lazy objects

Symfony 8 creates its container services as PHP 8.4 native lazy objects, cutting boot time 15-25% for apps with 200+ services, according to a dev.to post. The gain comes from retiring generated proxy classes, so it depends on how much boot time those classes cost you.

The Engineer · Build desk

Illustration accompanying Symfony 8 swaps generated DI proxies for PHP 8.4's native lazy objects

What happened

  • PHP 8.4 adds lazy objects to the language itself: an object exists, but its constructor and dependencies wait until code first reads its state.
  • Before PHP 8.4, Symfony made services lazy by generating proxy classes at runtime, storing them in a cache and loading them through the autoloader.
  • In Symfony 8, container services are created as native lazy objects, and each constructor runs only when a method or property is first used.
  • A post on dev.to estimates the boot-time improvement at 15 to 25 percent for a typical Symfony application with more than 200 services.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • cost Serverless functions and frequently rescaled containers boot most often, so they paid the old proxy setup most and are where the post expects a direct cut in operating cost.
  • constraint Lazy construction defers cost without removing it, so endpoints that touch most of the container will see less gain than a boot-time figure suggests.
  • decision The 15-25% range is grounds for profiling a Symfony 8 branch against your own service count before the saving goes into an upgrade budget.

The pattern predates PHP 8.4. Doctrine has used dynamically generated proxies for its entities for years [2]. Symfony built lazy services with the LazyGhostTrait class and with proxies its DI container generated at runtime [3]. PHP 8.4 moves the deferral into the runtime. According to a post on dev.to, that removes the overhead of code generation and introspection, and with it the extra classes, the cache and the autoloader step [7].

The post lists what the old path cost: code generation on first execution, cache I/O, and autoloader overhead for loading the proxy classes [5]. It calls those costs measurable [6] without saying who measured them. I think the runtime is the right place for this work. Every item on that list belongs to generated code, and a native lazy object has no generated code [5][7].

The size of the gain rests on one post's estimate [11]. For it to transfer, a codebase has to spend its boot where the proxy path spent it. Code generation happens on first execution [5]. The autoloader cost is paid each time a proxy class has to be loaded [5]. A boot profile that shows little time in either leaves Symfony 8 little to remove. The post expects larger gains for applications carrying hundreds of services from heavy bundles such as Sonata or EasyAdmin [12]. Its worked example assumes 300 services, 50 Doctrine entities and a REST API with 40 endpoints [14].

The post also says Symfony 8 boots by creating empty shells, with initialization spread across the request and paid only for the services actually used [9]. By its account the container registers hundreds of services, and a single HTTP request uses a fraction of them [10]. The per-request saving, then, is the construction of services the request never calls, plus the proxy overhead.

The rest of the post covers property hooks and asymmetric visibility, which it says Symfony 8 uses in the Form, Serializer and Validator components, the Doctrine integration, and value objects and DTOs [15].

What to watch

  • A reproducible Symfony 8 boot benchmark that states PHP version, opcache settings and service count, run with and without native lazy objects.
  • Cold-start timings from serverless PHP deployments running Symfony 8, where the post expects the saving to show up in operating cost.
  • Figures from Sonata or EasyAdmin applications, which the post says should gain more than the 15-25% range.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories