Problem/Motivation
> $service = \Drupal::service('paramconverter_manager');
= Drupal\Core\ParamConverter\ParamConverterManager {#1601}
> (new ReflectionProperty($service, 'converters'))->getValue($service);
= [
"drupal.proxy_original_service.paramconverter.views_ui" => Drupal\views_ui\ParamConverter\ViewUIConverter {#1608},
"drupal.proxy_original_service.paramconverter.configentity_admin" => Drupal\Core\ParamConverter\AdminPathConfigEntityConverter {#1603},
"paramconverter.entity" => Drupal\Core\ParamConverter\EntityConverter {#1606},
"paramconverter.entity_revision" => Drupal\Core\ParamConverter\EntityRevisionParamConverter {#1607},
"drupal.proxy_original_service.paramconverter.menu_link" => Drupal\Core\ParamConverter\MenuLinkPluginConverter {#1605},
"drupal.proxy_original_service.node_preview" => Drupal\node\ParamConverter\NodePreviewConverter {#1609},
]
Services tagged paramconverter are often declared as lazy in order to try and avoid instantiating them early. But it appears the lazy flag is ignored and the actual service (not the proxy) is instantiated when ParamConverterManager is constructed - which happens on every page request.
paramconverter services should only be needed when required by a route so we should lazily load all of them instead of each service having to declare itself lazy (which appears to be broken anyway).
Steps to reproduce
See above, or also try adding a breakpoint to ViewUIConverter::__construct().
Proposed resolution
Convert ParamConverterManager to use a service locator.
Remove the lazy proxy services for paramconverter services.
Remaining tasks
User interface changes
API changes
Data model changes
Release notes snippet
Issue fork drupal-3436295
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
Comment #2
longwaveBy adding logging to
Container::get():we can see the chains of services that are created:
If we lazily instantiate
paramconverter_manager's services properly then we remove all the direct children and perhaps some of the other children from being required on almost all page loads.Comment #4
longwaveComment #6
smustgrave commentedRebased to make sure everything passed with all the changes from the last month.
Unfortunately caused a large number of failures.
Comment #7
longwaveIt appears that
#[AutowireLocator]does not support ordering services by priority. This means that the services in the wrong order when iterating through all of them, and this causes bugs and the tests to fail. I traced through the compiler builder and inPriorityTaggedServiceTrait::findAndSortTaggedServices()the services are initially correctly sorted but when the final array is built the services are indexed by service name and the ordering is lost.https://github.com/symfony/symfony/issues/42506 implies this did work at some point - at least when using !tagged_locator instead of autowiring maybe - but it doesn't appear to here.
Comment #8
longwaveIn fact,
PriorityTaggedServiceTrait::findAndSortTaggedServices()does sort services correctly, but then later onServiceLocatorTagPass::map()callsksort($services)which destroys the order.Comment #9
longwaveOpened https://github.com/symfony/symfony/issues/57344 with a fix in https://github.com/symfony/symfony/pull/57345
Comment #10
longwaveUpstream bug is fixed in Symfony 7.0.9
https://github.com/symfony/symfony/pull/57581
Comment #12
longwaveComment #15
longwaveThe existing MR was too difficult to rebase, so I started again.
Comment #16
godotislateSome minor comments on the MR, fine to ignore.
Do we need an automated test that the paramconverter services are loaded lazily?
Comment #17
godotislateLooks like in a similar issue #3538294: Regression: All stack middlewares are constructed at the same time even for cached pages there were automated tests for lazy loading.
Comment #18
longwaveThanks for the review and suggestions, I applied them. Then I cleaned up the tests so we don't have
addConverter()any more, and added an explicit test for lazy instantiation.Comment #19
godotislateTest changes look good.
Comment #20
godotislateActually one comment about the laziness check.
Comment #21
longwaveExceptions are only caught after they have been expected.
If I swap the order:
then it fails:
Comment #22
longwaveHaving said that using exceptions for control flow isn't ideal, but perhaps it's okay in a test?
Comment #23
godotislateOh you're right, I misread the code flow in
PHPUnit\Framework\TestCase::runTest(). While a try statement wraps running the test method, the exception expectations need to be set before the exception is thrown.Comment #24
longwaveImproved the test as per the suggestion, we can reuse the existing converter as well to make the test shorter.
Comment #25
godotislateTest changes look good.
Comment #28
catchCommitted/pushed to main and 11.x, thanks!