Problem/Motivation

Right now, RenderableInterface can be used to print objects in twig templates. It would be nice if things implementing the interface could be used as and within render arrays.

Proposed resolution

Update the renderer to treat RenderableInterface as something that can be turned into markup

Remaining tasks

  • Validate the issue and idea.
  • Write a patch and test.

User interface changes

None.

API changes

Additions only.

Data model changes

None

Issue fork drupal-2602368

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

Sam152 created an issue. See original summary.

sam152’s picture

Assigned: Unassigned » sam152
sam152’s picture

Version: 8.1.x-dev » 8.0.x-dev
Status: Active » Needs review

Surprisingly simply patch. Lets see what testbot says. 8.0.x is only for the bot.

sam152’s picture

StatusFileSize
new2.8 KB

Status: Needs review » Needs work

The last submitted patch, 4: 2602368-renderable-interface.patch, failed testing.

sam152’s picture

StatusFileSize
new1.78 KB
sam152’s picture

StatusFileSize
new2.76 KB
sam152’s picture

Status: Needs work » Needs review

One day I'll learn to create a patch.

wim leers’s picture

Component: theme system » render system

+1 on concept.

But I'm not sure what the wider consequences are. We should enumerate all consequences, so we can at least try to avoid unexpected (DX) regressions.

Crell’s picture

I believe the intent here is to let any arbitrary object do what Link is now doing. It *shouldn't* impact the rendering engine at all.

The DX implication, though, is that depending on when we process RenderableInterface objects an alter hook may not know if it's getting, say, a #table array or a #table => TableBuilder implements RenderableInterface. That would be the concern.

sam152’s picture

I hadn't considered the DX of hooks that alter markup. We kind of already have that with twig and the element attribute object/arrays, but it would be nice to avoid entirely. It might be a step backwards but things transitioning from arrays to objects could implement ArrayAccess to make existing code which relied on them being arrays continue to work.

Crell’s picture

That is only sort of the case. ArrayAccess lets [] work, but doesn't support foreach() (for which you need Iterable) or the dozens of array functions available (for which there is no straightforward object equivalent). This is a long-standing known issue with PHP.

On the other hand, we are explicitly excluding most render arrays from our definition of "API", so that we can change them to improve the UI in point releases. Maybe we can get away with using objects in some places, therefore? Either way, it is a concern.

(And I say that as someone who really wants to be using more objects.)

wim leers’s picture

The DX implication, though, is that depending on when we process RenderableInterface objects an alter hook may not know if it's getting, say, a #table array or a #table => TableBuilder implements RenderableInterface. That would be the concern.

Exactly.

On the other hand, we are explicitly excluding most render arrays from our definition of "API", so that we can change them to improve the UI in point releases. Maybe we can get away with using objects in some places, therefore? Either way, it is a concern.

Indeed.

The second quoted bit there made me realize that at worst, those cases can be addressed by those alter hooks creating a new object that inherits all data from the old object, with modifications. If that's not possible, then the object is poorly designed.

This patch really is just enabling experimentation with this. It is not changing anything in core.

Consequently, I'm not sure there actually are any concerns about this? And I think this should even be considered a Contributed project blocker, because it blocks experimenting with improvements in contrib?

Crell’s picture

I agree. Let's make sure that core supports arbitrary RenderableInterface objects in render arrays. Anything else that gets done with that is up to contrib to figure out.

Strictly speaking, the same object-or-array issue would exist now for Link since Link, by design, does have an array representation one could use instead of the object if one were so inclined. So we're not even creating a new inconsistency, just letting that same inconsistency apply consistently. :-)

I defer to WimLeers on whether the patch in #7 is the correct way to do that.

wim leers’s picture

Assigned: sam152 » fabianx

I'd also like to get @Fabianx' thoughts on this. And I think this should be committed by catch or alexpott, and should preferably be reviewed by both.

benjy’s picture

Issue tags: +rc target triage

Is there still time to do this before release? It would be great to enable contrib and see what happens.

joelpittet’s picture

Version: 8.0.x-dev » 8.1.x-dev
Status: Needs review » Postponed

Bumping to a possible feature for 8.1.x

Crell’s picture

Status: Postponed » Needs review

I don't see why it's postponed now that 8.1.0 is the next stable. :-)

joelpittet’s picture

@crell was what I was told since the branch wasn't open.

xano’s picture

Issue tags: -rc target triage +BC break

This concept seems to break BC, because with the patch render arrays aren't just arrays anymore, so any code that expects them to be arrays, will break on RenderableInterface: type hints, arrray_*() functions, the addition operator, etc. #2316941-81: Use the builder pattern to make it easier to create render arrays provides a potential interim solution.

sam152’s picture

This would only be a BC break if another core patch changed an array to an object. Since that'll be where BC needs to be evaluated and this is a contrib blocker, I believe it should still be merged.

xano’s picture

This also breaks when new objects are introduced. We currently tell everyone render arrays are pure arrays, with the exception of element 'property' values. Allowing objects breaks any code that uses array functionality over elements, or type hints on them.

The only reason this was marked a blocker, is for future development and not because it currently blocks contrib functionality.

As much as I'd like to see a full conversion, I see this as 9.0.x material.

Version: 8.1.x-dev » 8.2.x-dev

Drupal 8.1.0-beta1 was released on March 2, 2016, which means new developments and disruptive changes should now be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

fabianx’s picture

Status: Needs review » Needs work
Issue tags: -BC break

It is not a BC break for core as we just allow contrib to use RenderableInterface objects instead of arrays.

In fact we already allow that but only for Twig variables, so it seems fair to make that even earlier.

I wanted to RTBC this, but:

On the other hand as pointed out contrib can easily circumvent that:

$build['my_object'] = [
  '#type' => 'renderable',
  '#renderable' => $renderable_object,
];

Both cannot be traversed, however the object could be early-rendered to change its contents by the caller, but not sure that is desirable. (kinda reminds one of the Shadow-DOM dilemma).

But for the render array with #type one a #pre_render could be added.

$build['my_object']['#pre_render'] = ['change_my_object'];

but that would not work as when the type is a lazy builder it would bail out and if the type is using #pre_render itself, it would overwrite it as we don't merge deep here.

So the #pre_render would need to use:

$build['my_object']['#pre_render'] = [
  'ObjectRenderable::preRender',
  'change_my_object',
];

and hence repeat it.

To get the same effect for the renderable objects, we would need MutableRenderableInterface, which would allow to do:

  $build['my_object']['#renderable']->addPreRender('change_my_object');

or

  $build['my_object']->addPreRender('change_my_object');

Given that the same problems happen and there is no distinct advantage to storing objects in the array tree, I am tending to won't fix this:

UNLESS

we think about adding a generic #type => renderable to core itself, which then likely should indeed use a lazy builder (though it can't as its an object) and only allow manipulation on the object itself OR by unwrapping it (and accessing #renderable property to early render it).

So that would take Xano's BC concerns into account and give contrib all the flexibility it needs, while ensuring that renderables can be used far and wide (and are controlled from core).

@Sam152: Thoughts?

---

Edit:

Hmmm there is _one_ advantage to the object itself and that is that neither lazy builders nor #type are allowed to return #cache properties.

However there is a very simple workaround:

Just use a child item in #pre_render:

e.g. in RenderableObject::preRender()

$build['renderable'] = $object->toRenderable();
return $build;

So the only ugliness is that someone could overwrite #type information, though maybe that is good if additional things are needed to add to the object itself.

--

TL;DR: Object in render array => no go as Xano described (got that now), adding generic support for '#type' => 'renderable' => lets discuss that!

fabianx’s picture

Assigned: fabianx » Unassigned

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.0-beta1 was released on August 3, 2016, which means new developments and disruptive changes should now be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.0-alpha1 will be released the week of January 30, 2017, which means new developments and disruptive changes should now be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.0-alpha1 will be released the week of July 31, 2017, which means new developments and disruptive changes should now be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.0-alpha1 will be released the week of January 17, 2018, which means new developments and disruptive changes should now be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

markhalliwell’s picture

Issue tags: +Needs reroll

Version: 8.6.x-dev » 8.7.x-dev

Drupal 8.6.0-alpha1 will be released the week of July 16, 2018, which means new developments and disruptive changes should now be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

MerryHamster’s picture

StatusFileSize
new2.79 KB

Reroll #7 patch for 8.7x

MerryHamster’s picture

MerryHamster’s picture

Status: Needs work » Needs review
andypost’s picture

Issue tags: -Needs reroll

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.9.x-dev » 9.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

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

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.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.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.

jonathanshaw’s picture

#22 and #24 agree on the problem:

  • We currently let everyone expect that render arrays contain only arrays as child elements, except for the 'property' elements whose kleys begin with '#'.
  • Therefore allowing objects implementing RenderableInterface as children breaks any code that uses array functionality over elements, or type hints on them.
  • This is a language limitation of php, even if RenderableInterface were to extend Traversable, Iterable and ArrayAccess.

Here's one approach to solving this making use of php 8's throwing TypeErrors for non-array inputs to array functions:

1. Have RenderableInterface extend Traversable, Iterable and ArrayAccess, so most code can continue to work without trouble.
2. Create RenderableBase and RenderableDefault classes implementing RenderableInterface
3. In Renderer::doRender() begin by casting $elements into a RenderableDefault if an array is passed
4. In Renderer::doCallback and ThemeManager::render, try with a RenderableInterface and fall back to array with a deprecation error:

try {
      $output = $render_function($template_file, $variables);
}
catch (\TypeError $e) {
  $output = $render_function($template_file, $variables->toRenderable());
  @trigger_error("$template_file should expect to be passed an instance of RenderableInterface", E_USER_DEPRECATED);
}

This allows us to gracefully change the expectations about what is passed to theme hooks. Once the system is passing objects around, it gets easier to improve how we are using them.

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.

smustgrave’s picture

Status: Needs review » Needs work
Issue tags: +Needs Review Queue Initiative

This issue is being reviewed by the kind folks in Slack, #need-reveiw-queue. We are working to keep the size of Needs Review queue [2700+ issues] to around 200, following Review a patch or merge require as a guide.

Would like to see an approach like #42 for 10 (where php8 is required)

smustgrave’s picture

Also if that is the approach the issue summary should be updated to reflect that.

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.

geek-merlin’s picture

meeni_dhobale made their first commit to this issue’s fork.

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.