We've gone back and forth over whether to allow plain strings as the result from a controller. I still argue that we should, if for no other reason than to make teaching and documentation easier. (Currently you cannot explain even a basic hello world route without getting into render arrays.)

However, even if we don't allow them then the current implicit lack of support for them is unhelpful. A string return is ignored by all view listeners (therefore triggering all of them) and makes it all the way up to HttpKernel, which throws a LogicException, which by default results in a white screen with generic, useless error message. That's... unhelpful.

There's two options:

1) Support strings by simply converting them to ['#markup' => $string] and letting render array handling take care of the rest. That can be done in the early-rendering wrapper class quite easily (and would still let us disallow/handle early rendering to the same extent we do now for render arrays).

2) Explicitly disallow string returns with a more productive error message that has some mention of what to do instead (eg, the #markup code above). That can also be done in the early-rendering wrapper class.

While I'd prefer option 1, I'll accept a ruling from the arch managers to the contrary if there's a good reason to disallow them at this point. However, we then must do option 2, as otherwise it's going to be very confusing for people.

The code in either case is quite trivial.

Issue fork drupal-2559491

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

Crell created an issue. See original summary.

wim leers’s picture

Status: Active » Needs review
Issue tags: +Needs tests, +DX (Developer Experience)
StatusFileSize
new2.86 KB

A KernelEvents::VIEW subscriber that maps the (escaped) string to a simple ['#markup' => $string] array makes sense to me. This is consistent, leverages Symfony, and doesn't require the Render API to have to deal with strings.

So, something like this.

Status: Needs review » Needs work

The last submitted patch, 2: string_view_subscriber-2559491-2.patch, failed testing.

wim leers’s picture

Status: Needs work » Needs review
Related issues: +#2555931: Add #plain_text to escape text in render arrays
StatusFileSize
new2.78 KB
new1.08 KB

Oh, #2555931: Add #plain_text to escape text in render arrays already landed. That allows me to simplify this :)

Also rebased. Patch should actually apply now.

Crell’s picture

:-)

My only concern is that, while this is the more correct(tm) way of doing it, the vast majority of controllers won't return strings. Yet this approach would add another view listener firing to every single request. Is that an acceptable performance hit?

dawehner’s picture

Mh, for a while I thought a string would result in a response, but well this patch indeed converts to a render array. Mh IMHO this is too much magic without that much of
a pratical win. I like the explicit over implicit rule of PIP 20 (https://www.python.org/dev/peps/pep-0020/).

How easy would people just create a string together, then its converted to a render array and then cached ... and we have NO cacheability metadata at all,
and boom another non obvious security issue.

+++ b/core/core.services.yml
@@ -940,10 +940,6 @@ services:
-  accept_negotiation_406:
-    class: Drupal\Core\EventSubscriber\AcceptNegotiation406
-    tags:
-      - { name: event_subscriber }

@@ -965,6 +961,17 @@ services:
+
+  accept_negotiation_406:
+    class: Drupal\Core\EventSubscriber\AcceptNegotiation406
+    tags:
+      - { name: event_subscriber }

IMHO an out of scope change :)

wim leers’s picture

StatusFileSize
new2.17 KB
new1.99 KB

#5: True. But in that case, I'd argue the answer is what I said on Twitter: for demo purposes, you should do new Response('Hello World'). I think we can leave it up to catch to decide whether this is acceptable overhead or not. If we don't do it here, we'd need to do it in Renderer::doRender(), but this surely has much more overhead. But we're still only talking thirteen additional function calls (I quickly profiled it). I'm happy to be trout-slapped for making Drupal 8 13 function calls slower if that one day yields a noticeable difference ;) :P

#6: You're right on both counts. First count: this was a very quick PoC. Second count: some commit totally screwed up the nice ordering/consistency in those services :( It annoyed me so much that I couldn't help fixing it. Turns out it was #2472323: Move modal / dialog to query parameters. Which I helped in. Wim--

borisson_’s picture

I think the usecase of doing new Response('Hello World'). is a really good one, code is straightforward and the patch still applies, setting to RTBC.

borisson_’s picture

Status: Needs review » Reviewed & tested by the community

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 7: string_view_subscriber-2559491-7.patch, failed testing.

borisson_’s picture

Status: Needs work » Reviewed & tested by the community

PIFR Fail, back to RTBC.

alexpott’s picture

Every listener fires on every request - why should we slow down the request for something we are not using?

alexpott’s picture

Status: Reviewed & tested by the community » Needs work

Also there are no tests.

wim leers’s picture

Every listener fires on every request - why should we slow down the request for something we are not using?

Yep, this is the main concern here.

We could use it in some of the test responses. But generally this would not be recommended to be used precisely because it'd lack cacheability metadata and therefore these responses would need to be uncacheable.

However, repeating what I wrote in #7:

I think we can leave it up to catch to decide whether this is acceptable overhead or not. If we don't do it here, we'd need to do it in Renderer::doRender(), but this surely has much more overhead. But we're still only talking thirteen additional function calls (I quickly profiled it).

Thoughts?

Crell’s picture

Given that we're now in RC, we may need to just improve the error message instead. :-(

That said, it's fewer function calls to just stick an if() statement inside the early-rendering wrapper class, is it not? I too defer to catch on the cleanliness vs. micro-op question here, but I do think we still need one or the other before 8.0.0.

wim leers’s picture

It doesn't belong in EarlyRenderingControllerWrapperSubscriber.

Isn't the real problem that Symfony's error message sucks? (When no VIEW subscriber for a controller result that is not a Response.)

Crell’s picture

Symfony's error message is very generic, because it's a generic error handler. We can/should do better in this particular case, either way.

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

Drupal 8.0.6 was released on April 6 and is the final bugfix release for the Drupal 8.0.x series. Drupal 8.0.x will not receive any further development aside from security fixes. Drupal 8.1.0-rc1 is now available and sites should prepare to update to 8.1.0.

Bug reports should be targeted against the 8.1.x-dev branch from now on, and new development or disruptive changes should 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.

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

Drupal 8.1.9 was released on September 7 and is the final bugfix release for the Drupal 8.1.x series. Drupal 8.1.x will not receive any further development aside from security fixes. Drupal 8.2.0-rc1 is now available and sites should prepare to upgrade to 8.2.0.

Bug reports should be targeted against the 8.2.x-dev branch from now on, and new development or disruptive changes should 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.2.x-dev » 8.3.x-dev

Drupal 8.2.6 was released on February 1, 2017 and is the final full bugfix release for the Drupal 8.2.x series. Drupal 8.2.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.3.0 on April 5, 2017. (Drupal 8.3.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.3.x-dev branch from now on, and new development or disruptive changes should 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.

dawehner’s picture

Status: Needs work » Needs review
StatusFileSize
new4.51 KB

So what about doing something like this?

borisson_’s picture

Issue tags: -Needs tests

This has a test, so removing the tag.

  1. +++ b/core/lib/Drupal/Core/EventSubscriber/ControllerStringExceptionSubscriber.php
    @@ -0,0 +1,38 @@
    +   * @throws \InvalidArgumentException
    +   *   Throwns when the
    

    I think this needs more words?

  2. +++ b/core/lib/Drupal/Core/EventSubscriber/ControllerStringExceptionSubscriber.php
    @@ -0,0 +1,38 @@
    +    // Choose a quite low priority so all other view subscribers run first.
    +    $events[KernelEvents::VIEW] = ['onView', -256];
    

    This eventsubscriber runs for every request, right? So why would we want this to run with low prio? Shouldn't we do this with high prio to reduce stack traces when this fails and make debugging easier?

    If that's not the case, can we document why we want the low priority?

dawehner’s picture

StatusFileSize
new4.54 KB
new741 bytes

Thank you for your review!

If that's not the case, can we document why we want the low priority?

Let me quote the code:

+ // Choose a quite low priority so all other view subscribers run first.
Doesn't this make sense?

borisson_’s picture

Let me quote the code:

+ // Choose a quite low priority so all other view subscribers run first.
Doesn't this make sense?

Well, I don't understand why the others are running first. Can the other view subscribers transform a string into a correct response? I guess they could and we should just say that's why instead?

dawehner’s picture

Well, I don't understand why the others are running first. Can the other view subscribers transform a string into a correct response

Well right, this is the purpose of this event. Subscribers can convert the controller result into a proper response.

The last submitted patch, 22: 2559491-22.patch, failed testing.

borisson_’s picture

Ok, thanks for holding my hand there :). Do you think it would make sense to expand the comment to:

Other view subscribers can transform the controller result from a string into something that the rendering system understands. Use a low priority so that those other view subscribers can run first.

I don't think we should mention 'all' view subscribers, since we can't be sure what weight contrib/custom modules use for their view subscribers.

Status: Needs review » Needs work

The last submitted patch, 24: 2559491-24.patch, failed testing.

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

Drupal 8.3.6 was released on August 2, 2017 and is the final full bugfix release for the Drupal 8.3.x series. Drupal 8.3.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.4.0 on October 4, 2017. (Drupal 8.4.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.4.x-dev branch from now on, and new development or disruptive changes should 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.4.x-dev » 8.5.x-dev

Drupal 8.4.4 was released on January 3, 2018 and is the final full bugfix release for the Drupal 8.4.x series. Drupal 8.4.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.5.0 on March 7, 2018. (Drupal 8.5.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.5.x-dev branch from now on, and new development or disruptive changes should 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.

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

Drupal 8.5.6 was released on August 1, 2018 and is the final bugfix release for the Drupal 8.5.x series. Drupal 8.5.x will not receive any further development aside from security fixes. Sites should prepare to update to 8.6.0 on September 5, 2018. (Drupal 8.6.0-rc1 is available for testing.)

Bug reports should be targeted against the 8.6.x-dev branch from now on, and new development or disruptive changes should 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.

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

Drupal 8.6.x will not receive any further development aside from security fixes. Bug reports should be targeted against the 8.8.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.9.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: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.7 was released on June 3, 2020 and is the final full bugfix release for the Drupal 8.8.x series. Drupal 8.8.x will not receive any further development aside from security fixes. Sites should prepare to update to Drupal 8.9.0 or Drupal 9.0.0 for ongoing support.

Bug reports should be targeted against the 8.9.x-dev branch from now on, and new development or disruptive changes should 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: 8.9.x-dev » 9.2.x-dev

Drupal 8 is end-of-life as of November 17, 2021. There will not be further changes made to Drupal 8. Bugfixes are now made to the 9.3.x and higher branches only. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

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

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

Drupal 9.3.15 was released on June 1st, 2022 and is the final full bugfix release for the Drupal 9.3.x series. Drupal 9.3.x will not receive any further development aside from security fixes. Drupal 9 bug reports should be targeted for the 9.4.x-dev branch from now on, and new development or disruptive changes should 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.4.x-dev » 9.5.x-dev

Drupal 9.4.9 was released on December 7, 2022 and is the final full bugfix release for the Drupal 9.4.x series. Drupal 9.4.x will not receive any further development aside from security fixes. Drupal 9 bug reports should be targeted for the 9.5.x-dev branch from now on, and new development or disruptive changes should 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.

Version: 9.5.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. 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 work » Postponed (maintainer needs more info)
Issue tags: +stale-issue-cleanup

Thank you for reporting this problem. We rely on issue reports like this one to resolve bugs and improve Drupal core.

Since there has been no activity here for over 8 years we are asking if this problem persists on a currently supported version of Drupal. To help, add a comment explaining if the problem still occurs or not. Any extra detail you can provide can help others who experienced this.

Since we need more information to move forward with this issue, the status is now Postponed (maintainer needs more info). If we don't receive additional information to help with the issue, it may be closed after three months.
Thanks!

borisson_’s picture

Status: Postponed (maintainer needs more info) » Needs work
Issue tags: +Novice

I think this still makes sense to do. To me it seems like the next steps are rerolling and adding a merge request + improving docs. Tagging as novice to do that.

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

acbramley’s picture

Status: Needs work » Postponed (maintainer needs more info)

Testing this on HEAD and the error message is much more helpful in 2025:

Symfony\Component\HttpKernel\Exception\ControllerDoesNotReturnResponseException: The controller must return a "Symfony\Component\HttpFoundation\Response" object but it returned a string ("string"). in () (line 98 of core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php). 

This comes from symfony's HttpKernel.

IMO this is even more helpful than the error message in the patch ("The result of a controller cannot be a string.") because it gives instructions on what it should be.

With that in mind, maybe we can just close this out now?

borisson_’s picture

Status: Postponed (maintainer needs more info) » Closed (outdated)

I agree with @acbramley, I think this can be closed as outdated because that new error seems like it makes a lot of sense.

Now that this issue is closed, please review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, please credit people who helped resolve this issue.