Lazy Objects in PHP 8.4 and Their Integration with Nette DI

David Grudl
Nette Core | 8228
+
+5
-

PHP 8.4 brings a powerful new feature: native support for lazy objects. A lazy object is essentially a placeholder that postpones its actual initialization until the moment it's truly needed – specifically when any of its properties are first accessed. Until that point, it exists merely as a lightweight shell, consuming minimal resources and avoiding potentially expensive constructor operations.

This feature proves particularly valuable for Nette's Dependency Injection container. Traditionally, when an application boots up, the DI container instantiates the entire dependency tree – creating all services and their dependencies upfront. While thorough, this approach can be resource-intensive, especially since many services might not be needed during a particular request.

The new testing version of nette/di 3.2-dev introduces a simple way to enable lazy service creation through configuration:

di:
    lazy: true

To illustrate this in practice, consider retrieving a service from the container:

$database = $container->getByType(Database::class);

What you receive is a lazy object that perfectly mimics a standard Database instance – the difference is invisible to the consumer. However, under the hood, it hasn't established any database connection yet. The actual connection occurs only when you first interact with the database:

$database->query('SELECT ...');  // connection is established here

The benefits become clear: if a request doesn't utilize the database, you've saved the resources that would have been spent on initialization. This optimization can be particularly impactful in larger applications with sophisticated dependency trees.

This approach does come with certain trade-offs. For instance, service creation errors (such as invalid database credentials) won't surface until the first actual use, rather than during application startup. Depending on your specific needs, this behavior might be either advantageous or challenging.

We're eager to see how this feature impacts application performance in production environments – the potential for optimization is substantial.


When implementing this feature, I initially considered making lazy loading opt-in for individual services. However, I realized a more elegant approach would be to enable it globally by default, with the option to selectively disable it where needed. Note that this functionality excludes internal PHP classes, which cannot be lazy-loaded.