Problem/Motivation
As discussed in #3228623: DX: Creating lazy services is too difficult/obscure/bespoke/brittle the DX for our current lazy service proxies is poor. We tried using symfony lazy services in #3396928: Deprecate Drupal ProxyBuilder in favor of Symfony lazy services however that was not possible due to:
Sadly SF uses eval for the proxy classes which at least I can't see working with our serialized container solution
However we can now use service closures to lazy-load services which are supported by our serialized container.
Steps to reproduce
Proposed resolution
Replacy services tagged 'lazy' with service closures.
Prefer using autowiring and the #[AutowireServiceClosure] attribute over the !service_closure yaml command where possible.
https://symfony.com/doc/current/service_container/autowiring.html#genera...
This requires changing the constructor signature and adding a new method to invoke the closure like so:
Before:
public function __construct(
protected CronInterface $cron,
) {}
Service definition:
services:
my_service:
class: Foo\Bar
arguments: ['@cron']
After:
public function __construct(
protected \Closure $cronClosure,
) {}
protected function getCron(): CronInterface {
return ($this->cronClosure)();
}
Service definition:
services:
my_service:
class: Foo\Bar
arguments: [!service_closure '@cron']
Autowiring approach
Before:
public function __construct(
protected CronInterface $cron,
) {}
Service definition:
services:
my_service:
class: Foo\Bar
autowire: true
After:
public function __construct(
#[AutowireServiceClosure('cron')]
protected \Closure $cronClosure,
) {}
protected function getCron(): CronInterface {
return ($this->cronClosure)();
}
Service definition:
services:
my_service:
class: Foo\Bar
autowire: true
Remaining tasks
Current proxies under core/lib/Drupal/Core/ProxyClass:
├── Batch
│ └── BatchStorage.php
├── Config
│ └── ConfigInstaller.php
├── Cron.php
├── Extension
│ └── ModuleInstaller.php
├── File
│ └── MimeType
│ ├── ExtensionMimeTypeGuesser.php
│ └── MimeTypeGuesser.php
├── Lock
│ ├── DatabaseLockBackend.php
│ └── PersistentDatabaseLockBackend.php
├── Menu
│ └── MenuActiveTrail.php
├── PageCache
│ └── ChainResponsePolicy.php
├── ParamConverter
│ ├── AdminPathConfigEntityConverter.php
│ └── MenuLinkPluginConverter.php
├── Render
│ └── BareHtmlPageRenderer.php
└── Routing
├── MatcherDumper.php
└── RouteBuilder.php
Proxies in core modules:
core/modules/language/src/ProxyClass/LanguageConverter.php
core/modules/node/src/ProxyClass/ParamConverter/NodePreviewConverter.php
core/modules/views_ui/src/ProxyClass/ParamConverter/ViewUIConverter.php
Comments
Comment #2
godotislateComment #3
berdirI created a sister issue for this one to start using service closures in places that are not yet using lazy service proxies: #3540386: [meta] Explore using more service closures to break deep dependency chains and load fewer services
Comment #4
berdirQuite a few of them are param converters. This is also something that came up in my issue. I need to have a closer look at how that actually works and if we can't find a more performant solution for them where they are managed (e.g. a service id collector instead of a service collector, or maybe we can add generic support for a service closure collector? This would also be useful for 1685492) so we don't need to push that complexity into every instance.
Comment #6
longwaveParamConverters should be solved by #3436295: ParamConverterManager lazy services are broken and should use a service locator. Service locators are lazy by default, they only construct a service when it is retrieved.