Problem/Motivation
This is a child issue and a blocker of #2352155: Remove HtmlFragment/HtmlPage.
Part of untangling the (page) rendering pipeline is removing edge cases, and hence being more strict, so that the DX is better (i.e. developers getting useful exceptions).
The prime example is that for historical reasons, we still allow strings to be returned by _content
controllers.
Proposed resolution
Don't allow strings to be returned by _content
controllers, only allow render arrays to be returned. Since only tests still return strings, this does not worsen the DX! In fact, it improves it, by making things more strict.
Remaining tasks
Review.
User interface changes
None.
API changes
Not really any. Strings are no longer valid return values for _content
controllers, but that already was enforced by convention; the only things still using that are tests!
Comment | File | Size | Author |
---|---|---|---|
#13 | 2363139-13.patch | 19.37 KB | dawehner |
Comments
Comment #1
Wim LeersComment #2
moshe weitzman CreditAttribution: moshe weitzman commentedWill some other issue add the exception when a controller returns a string?
Comment #3
larowlanYes I was expecting to see the exception too
Comment #4
Wim LeersThat's because I'm dumb: I failed to create the patch correctly :/
#1 is 1:1 from the main patch.
In this interdiff, you see the changes I intended to make. Typehinting to array provides the necessary feedback. You'll see errors like:
(Give it a quick shot by applying the patch, modifying
UserController::userPage()
to return a string, go to/user
, BOOM!)Comment #6
Wim LeersOops. I applied the pattern from the main patch here, but that doesn't fly, because the main patch removes
HtmlFragment
, which of course isn't the case here.(It also means that the existing documentation is outrageously wrong.)
So, no more reliance on a typehint, sadly, but an exception instead. In the main patch, typehinting will suffice.
Comment #7
tim.plunkettShould this also contain Response as a potential type?
No reason to hold the patch up, but this is a perfect usage for \InvalidArgumentException
Comment #9
Wim LeersAlso fixed that last exception.
Comment #10
moshe weitzman CreditAttribution: moshe weitzman commentedNo longer true.
Otherwise looks good.
Comment #11
dawehnerAre we sure there is no documentation at all in Drupal core which documents this?
Comment #12
larowlanfixes #10, reviewed again - agree with @moshe - +1 rtbc
Comment #13
dawehnerLet's actually provide something helpful.
Comment #14
Wim LeersIf there is, I didn't find it.
HtmlFragmentInterface
is what I wanted to use, but the code specifically checks forinstanceof HtmlFragment
, notHtmlFragmentInterface
, hence I chose to typehint the same way.… but anyway, it's going to be removed, so it doesn't matter much. I'll roll with it.
Comment #15
Wim LeersAny other concerns, or is this RTBC? :)
Comment #16
moshe weitzman CreditAttribution: moshe weitzman commentedNo concerns from me.
Comment #17
alexpottCommitted 961f188 and pushed to 8.0.x. Thanks!
Unrelated and doesn't fix the code style bug there anyway - just makes it worse. Removed from the patch.
Comment #19
Wim LeersThat was probably accidental — it was added by dawehner in #13. Thanks!
Comment #20
joelpittetI've written a concern elsewhere but it's puzzling how this is a DX improvement, where before if you're doing a test you could just provide a string which is the end result response to the browser. And now we must wrap it to get it back to a string through a superfluous drupal_render call.
I'm probably missing things, sorry I didn't get a chance to ask before it was committed.
Other than the type consistency what are we gaining from this?
Comment #21
Fabianx CreditAttribution: Fabianx commentedRTBC + 1 (in retrospective)
Comment #22
Wim LeersThat's it: consistency. But also: less magic.
If we want developers to be able to just return a string, then the underlying system has to apply magic: detect that it's a string and convert it into a render array.
Both more consistency and less magic improve the DX. The fact that we have zero non-test controllers returning strings shows how rare this is, and hence how few developers will be affected.
Comment #23
moshe weitzman CreditAttribution: moshe weitzman commentedI'll add another: discoverability. When you can just return a string, you do so. And then you have less opportunity to learn that should not be theming stuff in your callback, but rather deferring theming via #theme. And then you learn that you should use #attached instead of adding css/js/library globally. And then you learn that you can render cache expensive things, with great control over cache keys, cache tags, expire time, bin, etc.
Comment #24
joelpittetThanks for the responses @Wim Leers and @moshe weitzman, just something I'll need to get used to a bit.
I liked the magic when it gets me from Point A to Point B with little resistance. I relate it to inline templating: it's really not something you should be doing, but for rapid prototyping it will get you there faster, then you can work on splitting up the concerns into the template.html.twig and #theme definitions once you know your idea has wings.
Comment #25
Wim LeersAre you really returning just a string that often?
It's a small price to pay for less magic/consistent DX/discoverability, to have the 1% use case of returning a string require returning
['#markup'=> $string]
instead.Comment #26
joelpittet@Wim Leers yes although I seem to be in the 1% minority... but it's important to me so I thought I'd speak up.
For workflow: I go with sanity test/minimal viable code: "Can I print something to the browser?" is a good start. Massage it till it does what I want. Then refactor a few times to add things #cache, #attached, #theme and all the other things that require extra files or configuration that are not needed at the beginning "hello world".
return ['#markup'=> $string]
feels unnecessary when what my end goal is to(Purposely changed that to
print
because that is what I did in 6/7 for a blank viewport on hook_menu, thoughreturn
from the controller is fine).Comment #27
Wim LeersIf you really just want a string to be printed, and nothing else, then just do
return new Response('string')
, which is what Symfony_controller
s are intended to return.The difference is that this will literally just print that string, no HTML, no blocks. But if what you're looking for is "to get something to the browser", then this is what makes most sense in a Symfony world.
Comment #28
joelpittet@Wim Leers oh thanks for clarifying the
Response()
bit I should know that actually, not sure why that didn't cross my mind... I'll likely use that in my little sanity checks:)I referenced this stackoverflow question before in the other issue discussing this:
http://stackoverflow.com/questions/553936/in-mvc-how-do-i-return-a-strin...
For the likely "Why doesn't it just work like this" type of question I'm expecting. The snippet is MVC.net, although they are similar concepts.
new Response()
will beContent()
but you still can if you want pass a "string" from the controller's action in MVC.net though most likely you'd use aView()
in MVC.net just like you'd use['#theme' => 'my_theme_hook']
here.