Problem/Motivation

This can be seen as a follow-up for #3391702

On that issue, it was proposed that #slots were made more resilient and forgiving by accepting non-scalar values.

Another common Drupalism we might be facing on slots are translated strings, which

  1. Can include simple inline HTML tags, like <br> or <em>
  2. Will take the shape of a TranslatableMarkup, which will fail both the is_scalar and Element::isRenderArray checks and, as such, will be rejected witj an exception

Steps to reproduce

Try to feed a translated string (with or without HTML) to a slot:

      return [
        '#type' => 'component',
        '#component' => 'theme:icon_message',
        '#props' => [
          'icon' => $this->options['icon'],
        ],
        '#slots' => [
          'message' => $this->t('No @type were found.<br> Please try again later.', ['@type' => $this->options['content_type']]),
        ]
      ];

A 'Unable to render component "%s". A render array or a scalar is expected for the slot "%s" when using the render element with the "#slots" property', will be thrown.

Proposed resolution

As we did with scalars, wrap the translatable markup in order to make it pass the checks and render.

Remaining tasks

Propose a MR

User interface changes

None

Introduced terminology

None

API changes

None

Data model changes

None

Release notes snippet

Issue fork drupal-3504477

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

idiaz.roncero created an issue. See original summary.

idiaz.roncero’s picture

Issue summary: View changes
idiaz.roncero’s picture

idiaz.roncero’s picture

Status: Active » Needs review

Submitted MR that I have tested locally and works.

smustgrave’s picture

Status: Needs review » Needs work
Issue tags: +Needs tests

Thank you for opening.

Lets add some test coverage for the proposed change.

idiaz.roncero’s picture

Status: Needs work » Needs review

Added test, executed pipeline and it passed.

smustgrave’s picture

Status: Needs review » Reviewed & tested by the community
Issue tags: -Needs tests +Needs Review Queue Initiative

Removing tests tag as the test-only run shows the coverage

1) Drupal\KernelTests\Components\ComponentRenderTest::testRender
Drupal\Core\Render\Component\Exception\InvalidComponentDataException: Unable to render component "sdc_test:my-banner". A render array or a scalar is expected for the slot "banner_body" when using the render element with the "#slots" property

Summary seems clear with clear proposal that lines up with the code change.

Code change seems pretty straightforward and see no objection.

LGTM

berdir’s picture

Can confirm that this fixes an issue I see with navigation:title the entity returns a TranslatableMarkup object.

Left one comment on the MR whether this should be expanded to MarkupInterface or even Stringable.

catch’s picture

Status: Reviewed & tested by the community » Needs work

I think we should use MarkupInterface here.

berdir’s picture

Status: Needs work » Needs review

Updated, also shortened the test method description, it was already over 80 characters and that made it even longer.

smustgrave’s picture

Status: Needs review » Reviewed & tested by the community
1) Drupal\KernelTests\Components\ComponentRenderTest::testRender
Drupal\Core\Render\Component\Exception\InvalidComponentDataException: Unable to render component "sdc_test:my-banner". A render array or a scalar is expected for the slot "banner_body" when using the render element with the "#slots" property
/builds/issue/drupal-3504477/core/lib/Drupal/Core/Render/Element/ComponentElement.php:118
/builds/issue/drupal-3504477/core/lib/Drupal/Core/Render/Element/ComponentElement.php:65
/builds/issue/drupal-3504477/core/lib/Drupal/Core/Security/DoTrustedCallbackTrait.php:107
/builds/issue/drupal-3504477/core/lib/Drupal/Core/Render/Renderer.php:830
/builds/issue/drupal-3504477/core/lib/Drupal/Core/Render/Renderer.php:387
/builds/issue/drupal-3504477/core/lib/Drupal/Core/Render/Renderer.php:459
/builds/issue/drupal-3504477/core/lib/Drupal/Core/Render/Renderer.php:203
/builds/issue/drupal-3504477/core/tests/Drupal/KernelTests/Components/ComponentKernelTestBase.php:95
/builds/issue/drupal-3504477/core/lib/Drupal/Core/Render/Renderer.php:593
/builds/issue/drupal-3504477/core/tests/Drupal/KernelTests/Components/ComponentKernelTestBase.php:95
/builds/issue/drupal-3504477/core/tests/Drupal/KernelTests/Components/ComponentRenderTest.php:313
/builds/issue/drupal-3504477/core/tests/Drupal/KernelTests/Components/ComponentRenderTest.php:45
ERRORS!

Shows the test coverage

Believe this would solve a problem I see on my patterns theme were I have to pass #markup => content, in the twig file.

Change makes sense.

quietone’s picture

I didn't find any problems with this and all questions are answered.

quietone’s picture

Title: SDC ComponentElement: Transform slots TranslatableMarkup values to #markup instead of throwing an exception » Allow TranslatableMarkup in ComponentElement::generateComponentTemplate

Trying to improve the title

pdureau’s picture

Hello,

Thanks for the work.

The current (11.x) logic is:

      if (\is_scalar($slot_value)) {
        $slot_value = [
          "#plain_text" => (string) $slot_value,
        ];
      }

Obviously, it was not enough.

So, this MR is adding

      if ($slot_value instanceof MarkupInterface) {
        $slot_value = [
          "#markup" => $slot_value,
        ];
      }

On UI Patterns 2, they have added something a bit different:

    if (($value instanceof TwigMarkup) ||  ($value instanceof MarkupInterface)) {
      return ['#children' => $value];
    }
    if ($value instanceof \Stringable) {
      return [
        '#plain_text' => (string) $value,
      ];
    }

I don't know which one is better, maybe we a mix of both would be great. I will check with them.

pdureau’s picture

Assigned: Unassigned » pdureau

I keep the issue assigned to me until I have an answer ;)

needs-review-queue-bot’s picture

Status: Reviewed & tested by the community » Needs work
StatusFileSize
new91 bytes

The Needs Review Queue Bot tested this issue. It no longer applies to Drupal core. Therefore, this issue status is now "Needs work".

This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.

Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.

geek-merlin’s picture

Title: Allow TranslatableMarkup in ComponentElement::generateComponentTemplate » Allow all Renderable values in SDC slots
Issue tags: +Needs issue summary update

At least updating the title to how understand the current direction and scope.

And, let's add the Core-blessed RenderableInterface to the list.
In a time where static analysis thrives, it trumps naked render arrays.

Will look into #15. But need to grok some renderer details about #children first.

geek-merlin’s picture

Research note:
- There is no such class as TwigMarkup, but ui_patterns does use Twig\Markup as TwigMarkup;

Dunno if and how it hits Drupal code (did not encounter it yet), but no kittens killed putting it on our list here.

geek-merlin’s picture

@pdureau #15:

    if ($value === NULL) {
      // NULL is an rarely used but legal return of many callbacks, like EntityInterface::label().
      // Addresses #3505182 and a whole family of to-be-expected similar issues.
      return [];
    }

    if ($value instanceof RenderableInterface {
      // Return the wrapped renderable (array) of the renderable (object).
      return $value->toRenderable();
    }

    if (($value instanceof \Twig\Markup) ||  ($value instanceof MarkupInterface)) {
      // Mark value as safe for rendering. 
      // Prefer #markup over the undocumented #children.
      return ['#markup' => $value];
    }

    if ($value instanceof \Stringable) {
      // Ensure that results of stringable are properly escaped.
      return ['#plain_text' => (string) $value];
    }
geek-merlin’s picture

geek-merlin’s picture

StatusFileSize
new3.33 KB

I'm needing this for my navigation breaking when an entity returns markup, so rolled it in code and attaching a snapshot for composer.

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.

geek-merlin’s picture

Rebased. Did not investigate why this fails though.

pdureau’s picture

Status: Needs work » Needs review

Rebased. Did not investigate why this fails though.

Thanks a lot @geek-merlin.

The last pipeline run failed though. I will rebase and run the tests again. Then review your proposal.

pdureau’s picture

Same mysterious pipeline fail... investigating

needs-review-queue-bot’s picture

Status: Needs review » Needs work
StatusFileSize
new549 bytes

The Needs Review Queue Bot tested this issue. It fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".

This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.

Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.

smustgrave’s picture

Status: Needs work » Needs review

Think the bot was wrong on this one.

smustgrave’s picture

Status: Needs review » Needs work

Actually that test failure seems relevant to the issue.