Problem/Motivation

I was looking into creating a lazy service, since I knew this was possible in the Symfony DI component. There isn't much documentation on this in Drupal (see #2514582: Document lazy services and fix script doxygen) though it would be easy to think this works the same as in Symfony, which would be incorrect.

Lazy services were first introduced in #1973618: DIC: Lazy instantiation of service dependencies (ProxyManager for "proxy services"), in which the Symfony approach of a compiler pass was passed over in favor of a custom ProxyBuilder and ProxyDumper. The reasoning in the IS was:

This is the same approach as Symfony documents, except instead of using the Ocramius ProxyManager library that Symfony recommends, we use a custom lightweight implementation, which is faster (e.g., calls the proxied object's methods directly rather than via reflection) but not as complete (e.g., doesn't proxy property access, only method calls, and doesn't proxy magic methods). We think that's a worthwhile trade-off, since Drupal services don't use public properties, magic methods, etc.: the whole point of a service is to be defined by an interface.

This approach was later found to be incompatible with contrib modules, as the execution order of operations in building and dumping he DI container meant contrib interfaces were not discoverable. #2408371: Proxies of module interfaces don't work introduced the requirement to manually create the proxy service, using a script shipped with core.

There are a number of issues, such as #2532126: [meta] Follow-up for #2408371: Fix various issues with proxy services, #2539494: Provide a nice error message when trying to run the generate proxy class script on uninstalled/missing modules, the above-linked documentation todo, and #2513336: Consider checking checksum of interfaces during container building for proxy services which is probably most significant, having to do with the generated proxies going stale.

The vast majority of this work (and isuses) were done/logged 6+ years ago. From a DX perspective, I've personally concluded that the benefit of creating a lazy-instantiated service in Drupal is more trouble/danger than it's worth.

Steps to reproduce

Proposed resolution

Drupal's DI container differs from the upstream Symfony component in that it is serialized and dumped into the database. That introduces some significant challenges, but I'm optimistic that recent improvements in Symfony, paired with efforts in Drupal to come back closer to feature parity with upstream, might mean we can back out of the very Drupal-ized lazy service pattern. This would help site owners/developers, allow us to delete a lot of code from Drupal core, and make this less of a DX WTF.

Remaining tasks

User interface changes

API changes

Data model changes

Release notes snippet

Issue fork drupal-3228623

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

bradjones1 created an issue. See original summary.

bradjones1’s picture

bradjones1’s picture

wim leers’s picture

IIRC this was critical for certain aspects of the Drupal bootstrap process to become sufficiently fast in the majority of cases.

So the work that gets done here almost certainly will need to provide profiling data if it changes lazy services, and certainly if it removes them altogether.

That being said: +1 to this being poor DX right now. It's painful to make service lazy.

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

longwave’s picture

Symfony 6.2 supports lazy services out of the box: https://symfony.com/doc/current/service_container/lazy_services.html

We should figure out how this works and see if we can swap our custom implementation to using Symfony's.

catch’s picture

ghost of drupal past’s picture

You want ServiceClosureArgument

Your problem is our YAML loader AFAIK doesn't support it

You want to add a notation like the already existing @? probably @> because it's reminiscent of fn () => and probably upstream it too -- Symfony has its own way to do it using some godforsaken YAML notation uglier than sin and waaaaay too long, too.

bradjones1’s picture

Re: ##13, we're using this pattern at #3272093: Cache bin names should be set from service tags, not the service name though as mentioned, the Drupal container doesn't support it directly. I disagree that the Symfony syntax is too ugly to use, but service closure is a good candidate here to remove all this even uglier boilerplate.

longwave’s picture

#3450516: Add support for !service_closure custom tag in YamlFileLoader ?

I'm also (slowly) working on removing/replacing our lazy proxy implementations in #3432820: [meta] Remove some lazy proxy services, given that at least two of the ones I've investigated so far are broken in that they aren't really lazy at all.

bradjones1’s picture

Does this mean we could deprecate the existing lazy proxy creation script and require any new core lazy services be injected with a service closure or something else less boilerplate?

ghost of drupal past’s picture

Yeah, arguments: [!service_closure '@example_service_1'] is exactly the godforsaken ugly syntax I mentioned. Nah. We could and should do better: arguments: ['@>example_service_1'] and be done.

dpi’s picture

We already support the nice thing, \Symfony\Component\DependencyInjection\Attribute\AutowireServiceClosure. AFAICT.

Symfony Docs

!service_closure should be avoided in new code.

dpi’s picture

Does this mean we could deprecate the existing lazy proxy creation script

A worthwhile cause <3

kim.pepper’s picture

catch’s picture

kim.pepper’s picture

I created #3483996: Remove lazy declaration and proxy class for cron and use service closure instead to look at the service closure approach. It might be a big change to do in one go, so we could split it into smaller tasks.

ghost of drupal past’s picture

I found the !service_closure syntax so ugly I put aside my disdain for all things upstream and added the shorthand to Symfony https://github.com/symfony/symfony/commit/ba299601bf460a5888a511d2c6da16...

I'd recommend this to be applied to the YamlFileLoader in core, it's really little code.

longwave’s picture

#23 oh that's a nice little improvement, yes we should add that to Drupal too!

kim.pepper’s picture

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.