Drupal's service container builds every registered service's constructor dependencies eagerly by default — inject a service into your class, and Drupal instantiates it (and everything it depends on) whether or not your code actually ends up calling any of its methods on this particular request. A lazy service defers that construction until the exact moment something first calls a method on it — free if that moment never arrives.
What you'll learn in this lesson
- Why eager service construction can be wasteful for conditionally-used dependencies
- How to mark a service definition as lazy
- What actually happens under the hood — a proxy object
The eager default
# mymodule.services.yml
services:
mymodule.report_generator:
class: Drupal\mymodule\ReportGenerator
arguments: ['@database', '@mymodule.expensive_analytics_service']
Even if ReportGenerator only calls expensive_analytics_service's methods on the rare code path where a user actually requests a report, the container constructs that expensive service every single time ReportGenerator itself gets instantiated — which might happen on every page load if it's injected somewhere commonly used, like a block plugin.
Marking a service lazy
services:
mymodule.expensive_analytics_service:
class: Drupal\mymodule\ExpensiveAnalyticsService
arguments: ['@database']
lazy: true
With lazy: true, the container hands ReportGenerator a lightweight proxy object instead of the real service immediately. That proxy looks and behaves like the real service from the calling code's perspective — but doesn't actually construct the genuine ExpensiveAnalyticsService instance until the first time one of its methods is actually called.
symfony/proxy-manager-bridge package (bundled with Drupal core), and adds a very small overhead of its own (the proxy's method-call indirection) — meaning lazy: true is a net win specifically for services that are expensive to construct but only occasionally used, not a free optimization to apply everywhere by default.When this actually helps
The win is largest for services that are: injected into something commonly instantiated (a block, a widely-used form), but themselves expensive to build (opening a network connection, loading a large dataset) and only actually used on a minority of requests. A cheap, always-used service (like current_user) gains nothing from laziness — the proxy indirection is pure overhead if the real service gets used on every single call anyway.
Quick check: would marking Drupal core's
current_userservice as lazy make sense? If you said no — it's cheap to construct and used on nearly every request anyway, so the proxy indirection would be pure added overhead with no benefit — you've understood exactly which services this technique actually helps.
Key takeaways
- Services are constructed eagerly by default — every dependency of an instantiated class gets built immediately, used or not.
lazy: truein a service definition defers real construction until the first actual method call, via an auto-generated proxy.- The win is real specifically for expensive-to-construct, conditionally-used services — not a blanket optimization to apply to every service definition.
- A cheap, always-used service gains nothing from laziness; the proxy indirection becomes pure overhead instead.
Coming up next
You've now trimmed unused modules, tightened hook overhead, and deferred expensive service construction. Last in this section: actually measuring which of your modules contribute the most overhead, instead of guessing.