Support for Drupal 7 is ending on 5 January 2025—it’s time to migrate to Drupal 10! Learn about the many benefits of Drupal 10 and find migration tools in our resource center.
Problem/Motivation
We have drupal_render()
calls in ThemeManager
.
Proposed resolution
Inject the Renderer
.
Remaining tasks
Do it
User interface changes
None
API changes
TBD
Data model changes
None
Comment | File | Size | Author |
---|---|---|---|
#38 | 2529438-38.patch | 4.21 KB | andypost |
#38 | interdiff.txt | 1.49 KB | andypost |
Comments
Comment #1
cilefen CreditAttribution: cilefen commentedComment #2
colbol CreditAttribution: colbol commentedThe problem is this creates a circular service reference:
exception 'Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException' with message 'Circular reference detected for service[error]
"theme.manager", path: "theme.manager -> renderer -> theme.manager".'
Comment #4
plachNice, at very least let's use Drupal::service() :)
Comment #5
dawehnerDid you tried using setter injection?
Comment #6
cilefen CreditAttribution: cilefen commentedSetter injection results in a different circular reference:
We'll have to to with #4 without a major refactoring.
Comment #7
cilefen CreditAttribution: cilefen commentedComment #8
cilefen CreditAttribution: cilefen commentedComment #9
plachThanks
Comment #10
dawehnerHere is an alternative idea ... as ThemeManagerInterface::render is already meant to be internal.
Comment #11
plachWhat about making
$renderer
default to NULL so we can have a BC change? Otherwise not sure we can justify this issue in the beta evaluation.Comment #12
dawehnerWell, for sure we could do that, but on the other hand we really pushed hard to make theme() => _theme() internal.
Comment #13
Mile23I think #10 is the way to go, especially in 8.1.x.
-1 on #11. Even if it weren't @internal, we should still inject all dependencies. We want to leave the seam so that the problem from #6 is visible and eventually gets fixed.
One thing though:
Change those to
$this->container->service()
instead of\Drupal
.Also need a followup to properly inject all the
\Drupal
dependencies in the rest ofThemeManager
.Comment #15
joelpittetI agree with @plach in #11. To preserve BC we shouldn't change the public interface on
ThemeManager
. Maybe it leaves a bit of cruft in there but with a bit of comments it will be obvious to remove in D9.Comment #16
Mile23Adding a parent issue.
See also: #2704871: Replace usages of deprecated method drupal_render()
Comment #20
Mile23After #2704871: Replace usages of deprecated method drupal_render(), ThemeManager does not have references to
drupal_render()
within it. However, it does use\Drupal::service('render')->render()
, which doesn't count as injecting the service.I haven't experimented, but it's likely still a circular reference between the two services.
Comment #24
eelkeblokI am running into some issues trying to render stuff for embedding in an email, and encounter this issue where things go pear-shaped. It looks like the render context is not being passed on. I've rerolled #10 and squashed some more uses of themeManager->render(). Hopefully it's all this impacts.
Comment #26
eelkeblokThis fixes the two test failures (silly misplacement of brackets) and actually uses the passed renderer instance instead of asking for it from the Drupal global object, which is what originally led me to this issue.
Comment #27
eelkeblokHere's an interdiff, basically for 10 to 26 (24 was not only a reroll, it also contained fixes for some additional uses of themeManager::render()).
Comment #28
andypostComment #29
markhalliwellI'm not entirely sure this issue is still relevant. Its primary use was to get rid of
drupal_render()
usage, which has already been done.Yes, it still uses
\Drupal::service('renderer')
, but that's a semi-by product of the circular dependency created by the Renderer service.I'm also really not a huge fan of DI methods like the latest patches are doing.
I think something simliar to #6 can still be accomplished.
Instead of setting the renderer via the service definition, we could still create a
ThemeManager::setRenderer()
method that can be invoked when setting the$this->theme
property inRenderer::__construct
:An additional
ThemeManager::getRenderer()
method should be created and used so, if the renderer isn't set (for whatever reason), it can be set using\Drupal::service('renderer')
for BC reasons; so the line inThemeManager::render
should read as:This kind of approach avoids changing method signatures and essentially creating BC breaks with existing interfaces.
The only way to not break the signatures is to do what #11 suggested and use NULL. However, this is essentially no different from creating
ThemeManager::getRenderer()
, without the unnecessary extra parameter.I really don't think there is any way to avoid
\Drupal::service('renderer')
in cases whereThemeManager::render
is invoked before the Renderer service is constructed. While this use case isn't likely to be very common, it is still technically possible given the current architecture of Renderer and ThemeManager.Comment #30
eelkeblokWell, I wish I had a better way of reproducing my problem, but it seems that somehow themeManager may end up with a different instance of the renderer than other parts of the system. I have some custom code that gets the renderer from the container and then calls renderPlain, because it needs to produce some HTML for use in an email. Somewhere down the render chain, the render context goes AWOL, resulting in "Render context is empty, because render() was called outside of a renderRoot() or renderPlain() call. Use renderPlain()/renderRoot() or #lazy_builder/#pre_render instead." With the patch from #26, I don't get this, presumably because it ensures the renderer is preserved through the render chain.
I'll take a shot at producing a patch using your proposed resolution, which does sound better from a BC point of view.
Comment #31
eelkeblokSo, something like this. This too solves my problem, btw.
Comment #32
markhalliwellSet methods typically return
$this
to allow chaining.Would also allow this to be a one-liner in
Renderer::__construct
as I mentioned in #29:This means this needs a test to reliably reproduce this issue and semi-justify this change.
Comment #33
eelkeblokAgreed. It does feel like I am fighting symptoms instead of the actual cause. The render context going missing doesn't make a lot of sense, and there really should not be a functional difference between using \Drupal::service('renderer') or pseudo-injecting the renderer when it is constructed itself.
Comment #38
andypostThe
drupal_render()
is deprecated since 8.0 and already removed in 9.0 (only mentions in comments exist) so summary and title needs workMeantime here's re-roll and fix for #32.1
Comment #39
andypostComment #40
andypost