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.
| Comment | File | Size | Author |
|---|---|---|---|
| #24 | interdiff-2559491.txt | 741 bytes | dawehner |
| #24 | 2559491-24.patch | 4.54 KB | dawehner |
| #22 | 2559491-22.patch | 4.51 KB | dawehner |
| #7 | string_view_subscriber-2559491-7.patch | 2.17 KB | wim leers |
Issue fork drupal-2559491
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
wim leersA
KernelEvents::VIEWsubscriber 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.
Comment #4
wim leersOh, #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.
Comment #5
Crell commented:-)
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?
Comment #6
dawehnerMh, 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.
IMHO an out of scope change :)
Comment #7
wim leers#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 inRenderer::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--
Comment #8
borisson_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.Comment #9
borisson_Comment #12
borisson_PIFR Fail, back to RTBC.
Comment #13
alexpottEvery listener fires on every request - why should we slow down the request for something we are not using?
Comment #14
alexpottAlso there are no tests.
Comment #15
wim leersYep, 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:
Thoughts?
Comment #16
Crell commentedGiven 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.
Comment #17
wim leersIt doesn't belong in
EarlyRenderingControllerWrapperSubscriber.Isn't the real problem that Symfony's error message sucks? (When no
VIEWsubscriber for a controller result that is not aResponse.)Comment #18
Crell commentedSymfony's error message is very generic, because it's a generic error handler. We can/should do better in this particular case, either way.
Comment #22
dawehnerSo what about doing something like this?
Comment #23
borisson_This has a test, so removing the tag.
I think this needs more words?
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?
Comment #24
dawehnerThank you for your review!
Let me quote the code:
+ // Choose a quite low priority so all other view subscribers run first.Doesn't this make sense?
Comment #25
borisson_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?
Comment #26
dawehnerWell right, this is the purpose of this event. Subscribers can convert the controller result into a proper response.
Comment #28
borisson_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.
Comment #40
smustgrave commentedThank 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!
Comment #41
borisson_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.
Comment #43
acbramley commentedTesting this on HEAD and the error message is much more helpful in 2025:
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?
Comment #44
borisson_I agree with @acbramley, I think this can be closed as outdated because that new error seems like it makes a lot of sense.