Problem/Motivation
Several of the things that template_preprocess_html() does (because it must) should not be done in the theme preprocess layer. They actually belong on a HtmlResponse class; but since we don't have that, because HtmlRenderer::renderResponse() returns a plain Symfony Response, that's not yet the case.
(See #2368797-34: Optimize ajaxPageState to keep Drupal 8 sites fast on high-latency networks, prevent CSS/JS aggregation from taking down sites and use HTTP GET for AJAX requests.3.)
More importantly: HEAD makes implementing render strategies extremely difficult, i.e. it blocks #2469431: BigPipe for auth users: first send+render the cheap parts of the page, then the expensive parts (per #8 + #79) and allows for significant simplifications in #2429617: Make D8 2x as fast: Dynamic Page Cache: context-dependent page caching (for *all* users!).
So this patch paves the way for one of the most important steps towards BigPipe, and simplifies SmartCache.
Proposed resolution
Introduce a HtmlResponse class.
Remaining tasks
Review & commit.
User interface changes
None.
API changes
None. (Internal API changes only.)
Possibly: template_preprocess_html() is now called before the main 'page', 'page_top' and 'page_bottom' have been rendered. Not sure we need a CR for that as its not TRUE API, but rather a side effect.
Beta phase evaluation
| Issue category | Task because neither bug nor feature, but significantly paves the way for SmartCache/BigPipe. From the IS:
|
|---|---|
| Issue priority | Major because blocks BigPipe, both of which are major. |
| Prioritized changes | The main goal of this issue is performance — indirectly, by paving the way for BigPipe. |
| Disruption | None. All internal. |
| Comment | File | Size | Author |
|---|---|---|---|
| #122 | interdiff.txt | 4.05 KB | wim leers |
| #122 | move_attachment-2407195-122.patch | 72.93 KB | wim leers |
| #40 | interdiff.txt | 15.6 KB | fabianx |
| #105 | consider_removing-reroll-2407195-105.patch | 67.18 KB | wim leers |
| #120 | move_attachment-2407195-120.patch | 68.95 KB | wim leers |
Comments
Comment #1
wim leersComment #2
dawehnerWell, the HtmlResponse class should not be a response but rather a domain object which gets the necessary preprocess stuff from outside.
Comment #3
wim leersThe parent issue landed, we can work on this now.
Comment #4
wim leersMy original thinking, was that rendering
html.html.twigis a special case; it's not the regular theme system/render API that is being used here. Well, it is, but special rules apply, since this is the final level: there is no level above this to bubble things to. All of the "global" things are happening forhtml.html.twig: assets are bubbled up to the response-level "thing", which ishtml.html.twig. Which is why it's currentlytemplate_preprocess_html()'s responsibility to take that response-level metadata and turn it into a string of HTML.But if there were a
HtmlResponseclass, then we could bubble the assets to that, which both makes semantical sense and is analogous toAjaxResponse.This rough idea of a
HtmlResponsedates back as far as #2352155: Remove HtmlFragment/HtmlPage.But… today, when I tried to make it work that way, I got stuck: turns out that
html.html.twigis using certain things that the theme system provides (like$variables['logged_in']). It only became more clear 3 days though, with the commit of #2329753: Move html classes from preprocess to templates.I think it's still conceptually a good idea to have a
HtmlResponseclass to encapsulate a bunch of the "HTML response" things that have to happen (like transform the attached assets into<script>and<link rel="stylesheets">tags). But since such aHtmlResponseclass would also have to contain hacks — just liketemplate_preprocess_html()feels hacky today, I'm not super convinced anymore it's worth it.Plus, I also have no idea what dawehner means with #2 (could you provide a tiny prototype patch?), and AFAICT that indicates further that
HtmlResponseis not a good idea.But then what is left to be done here? (That prototype patch would be helpful!)
Comment #5
fabianx commented.
Comment #6
joelpittet@Wim Leers this idea you have sounds really promising. Is it still something you'd like to see happen for 8.0.x?
Not sure I could architect such a thing but i'm a big +1 to this. Maybe I can start a patch with something completely wrong as a starter:D or anything you think is more helpful to make this fly.
Then we can actually put assets in preprocess_html() no?
Comment #7
fabianx commentedYes and yes.
We still plan to do that, not sure we can get rid of template_preprocess_html() in full though ... (But could be of everything below '// Collect all attachments. This must happen in the preprocess function for')
The idea is to have CacheableHtmlResponse + AttachmentsResponseInterface and then just using placeholder string (for CSS / JS) instead.
In a FinishResponseSubscriber we then replace the static placeholders with the scripts (header / footer) and styles and html head.
And that is it.
That means to create a response with assets (in HtmlRenderer):
That is consistent with AjaxResponse then! (Tada - Consistency!)
Bonus points if you allow passing a render array in the constructor (or add a helper method):
or
Part of our overall performance plan.
Comment #8
fabianx commentedI needed this for my 'rendering strategies' work, so gone ahead and implemented the minimum needed for it, so could as well post it.
Strong WIP, only for test-bot.
Comment #10
fabianx commentedIt helps to commit the CacheableHtmlResponse class ...
Still only for testbot.
Comment #12
fabianx commentedStill has hacks, but this one is much nicer in terms of DX while being 100% BC compatible in terms of the Response class:
This is very close to what Crell and me envisioned - just renderRoot should return a cacheable render array directly. Then there would be no longer any gap between the render and the response system.
--
The only thing changing here is the BareHtmlPageRenderer, which needs to support some responses that are send directly for which a special BareHtmlPageRenderer::filterResponse is added to hide that complexity.
Comment #14
fabianx commentedBatch API wants some love, too ...
Comment #16
fabianx commentedAnd I missed the installer.
Comment #18
fabianx commented- Silly mistakes like removing the Response 'use' statement when its still used within the file.
- Forgetting to use CacheableHtmlResponse
- Wrong argument to renderBarePage due to internal re-factoring
But what is interesting about this one is the change to twig.engine as it immediately makes Twig wrapped exceptions more useful, could be split out into its own issue too, but is necessary to fix ErrorHandlerTest.
Comment #19
fabianx commentedAssigning to Wim for a review with the following caveats that I am myself still unhappy about, but that can be resolved by mere re-organization of the code:
- Hardcoding strings to replace later feels / is wrong
- Calling the FinishResponseSubscriber from BareHtmlPageRenderer feels very wrong, should really be its own service
- Adding some much new logic to FinishResponseSubscriber feels wrong, should be its own injected service
But what to name this service?
- HtmlAssets?
- AttachmentsResponseProcessor?
This issue paves the way to removing _all_ the globals and drupal_process_attached() in a follow-up.
So whatever it is called needs to take into account that we want to be able to retrieve the headers (set via ['#attached']['http_header']) from that Service, too.
Comment #20
star-szrAffects Twig land, tagging.
Comment #21
wim leersLooks great overall!
I think this is a debugging leftover? :) Because
install_display_output()already callsexit.This could also use the "Filter the response as it does not go via the usual chain […]" comment that you have elsewhere.
Too easy to create collisions, needs something like
Renderer::generateCachePlaceholder().Once this is in a service, we could move the burden to
CacheableHtmlResponseto call this.And we could also move this chunk of logic into a separate event subscriber if we'd like.
But most of this is actually specific to HTML responses, just like we have kinda-similar-but-really-quite-different-stuff for AJAX responses. Because HTML and AJAX responses handle/load attachments (out-of-band data) very differently.
So, IMO ideally this could live on
CacheableHtmlResponse.Except it then would not work for non-cacheable HTML responses, i.e. generic
Responseobjects. But… if you're sending those kinds of responses, you don't want our treatment anyway, because if you construct aResponse, you just want that to be sent.It's only when you're building a response using a render array, that you want this logic to run. But we can guarantee that that is always a
CacheableHtmlResponse. So, AFAICT we should be able to move this intoCacheableHtmlResponse, much likeAjaxResponsehas some attachment-handling logic?And that reminds me: why
CacheableHtmlResponseand not justHtmlResponse?Should be one newline, not two.
These are kind of ugly. Wouldn't it be better to manually trigger the
KernelEvents::RESPONSEevent in those places where we are currently manually calling these functions?2 -> 1
\n.80 cols.
Looks great!
Needs newline in between.
So this is the key change. And I want to make sure I understand it.
AFAICT the reason this is okay, is that assets are now no longer rendered by
Renderer/drupal_render(), but byFinishResponseSubscriber.Correct?
Wonderful simplification.
Why is this change necessary now?
Comment #22
fabianx commented#21
1. No, this is hardening and making that exit() explicit instead of relying on that code not changing and suddenly ending up with what had before been in an else case.
2. Yes, as discussed would probably use ->prepare().
3. Yes, as discussed, but for that we probably need a service, unless I render it and store it via #attached html_response_placeholders directly (have to try that - yes, thats likely better).
4. Yes, lets use HtmlResponse and as discussed in IRC, lets see if the logic can live in HtmlResponse::prepareAttachments(), called by ::prepare().
5. Will be fixed.
6. Better to call $response->prepare()
7. Will be fixed.
8. Will be fixed.
9. :-D
10. Will be fixed.
11. Yes, that is correct, this removes the chicken-egg problem by moving the asset rendering into the response realm.
12. That one is tricky, but in a nut-shell:
Before the renderer rendered the children directly, hence we got the original exception, but now 'page' is rendered as part of a twig template and hence Twig simplifies the Exception by showing the template and line wrapping the original Exception in a new Exception, but unfortunately we totally loose the function() that caused the Exception that way.
For generic twig that is the only option, but this being Drupal, we can do better and just show the twig message as a first message, then show the original Exception.
That makes the ErrorHandlerTest pass as now we show both:
- The twig template name and line
- The original Exception with the function name, line, etc.
So we get the best out of both worlds.
Comment #23
wim leersI don't fully grok point 12, but that's fine for now. I'll step through it with a debugger in a later review. Looking forward to the reroll :)
Comment #24
Crell commentedThis should not be in HtmlResponse. It should be its own service, as the @todo suggests.
Responses should be value objects. They represent data. They should not be doing anything interesting themselves. My experience working with value objects is that it is *extremely tempting* to let them have "simple" functionality. It's so easy, it's right there... But don't listen to that siren song. It will trap you and ensnare you and riddle your code with hard-to-fix design flaws. :-)
(FabianX noted in IRC that Symfony's Response class already has a fair bit of logic in it. That's true. Given the above, I am not going to defend that as a good design decision. Quite the opposite. That's why PSR-7's ResponseInterface has no prepare() method, or similar. That should be its own service, too.)
Especially in this case, there are calls out to various services. A value object should *never ever ever* call out to services, because it's complecting "be" and "do". Doing so is a very clear code smell. (Side note: An astute reader may note that Entities in Drupal 8 all have service dependencies. Take a look at the existence of DependencySerializationTrait. That proves my point.)
This code should either be its own service, or part of another existing render service. The main question there is whether this logic would ever reasonably change between different service implementations. Is this generic to all render strategies, or strategy-specific? If strategy specific, put it in the appropriate render strategy class. If it's universal, make it its own class/service. (It has its own dependencies so should not be a trait.)
Reading Wim's reply now, if AjaxResponse has too much attachment handling logic then perhaps that may need to get moved out, too? Definitely if it has any dependencies, although it didn't the last time I looked.
This ship may have sailed, but is there no way to break this out a bit more, and make the different types of attachments explicit? Just from the name, all I see here is "array of random stuff".
Unclear: Do I get back
(Related to previous note, #attached still has significant DX problems around discoverability. In this case it's probably a documentation question.)
A master list of the keys in attachments! It's Christmas! :-)
This seems a poor method name. It's very easy to configure with the FilterResponseEvent class that Symfony uses for the kernel.response event. (Why it uses such bizarre names for its event classes I don't know, but it does.)
There's also no, well, filtering going on here that I can see. (And I'd make the same comment about kernel.response, too.)
These lines make me warm and fuzzy inside.
Comment #25
wim leersI misread the "It" as "I", which makes it a whole lot more funny :D
Comment #26
wim leers#24:
HtmlResponseSubscriber, that does all of the HTML response-specific stuff. We should then also makeAjaxResponsehave a correspondingAjaxResponseSubscriber.All of this basically boils down to: .
Comment #27
fabianx commented1. I found some more resources in terms of value objects (https://pragprog.com/articles/tell-dont-ask + http://en.wikipedia.org/wiki/Law_of_Demeter)
The TL;DR is:
which gave me a way better understanding of why e.g. calling a service in a value object is bad, it couples the value object to a specific implementation.
However the HtmlResponseSubscriber::prepareAttachments() can be called by e.g. the bare_html_page_renderer service, because even though the main task of that service is to listen and filter responses, the bare_html_page_renderer can declare a dependency (composite) and hence use that service without violating LoD.
Nice, thanks for the information, Crell!
Comment #28
Crell commentedI cannot speak to Fabien's level of intoxication, other than observing that he is French. :-) prepare() doesn't have any external dependencies so it's less of an issue, but yes, PSR-7 deliberately eschewed even that, for a reason. I'd say more "don't ever mimic Response::prepare()".
Someone was listening to my DrupalCon San Francisco presentation on OOP... :-)
Comment #29
wim leersNope, the eyjafjallajökull eruption prevented me from doing so :(
Comment #30
fabianx commentedFiled #2497115: ajax_page_state is not taken into account for normal GET requests, not fixing in here, because of needed test coverage for this.
Comment #31
tim.plunkettIt would be great if this was a standalone part of a service. I've been copy/pasting this part for my own hacky purposes.
Comment #32
fabianx commented#31:
Yes, new patch coming soon. Just dealing with a contextual views links problem right now. Have converted all to nice injected services already ...
Hm, should probably just post the patch and see if any new tests fail ...
@Tim: Let me know if #32 helps you or if you need some more helper methods or such on the service.
Comment #34
fabianx commented- Addressed all points, generating unique placeholders now
- Added HtmlResponseSubscriber (yeah!)
- Fixed test failures (silly missing class)
- Fixed views contextual links (was the only preprocess_html callback that depended on the old behavior that page was rendered first, while it is now the opposite)
Comment #35
fabianx commentedComment #36
wim leersLooking great!
80 cols.
s/send/sent/
I think e.g.
<drupal-html-response-attachment-placeholder type="$type" token="$token">would make for a slightly better name.s/html/HTML/
Missing docs.
Because this representation of attachments is specific to HTML responses.
Missing trailing period.
80 cols.
That's unsure, because attached headers may apply to other response formats as well. Let's leave this @todo out, IMO it adds more confusion than anything.
Let's do this last, since it's the only non-placeholder one.
In fact, let's move this to a protected helper method. And let's move the placeholder replacement logic also to a protected helper method.
Then you can see the logical units in the code much better.
Needs newline in between.
Feels out of scope.
Could you create an issue to link to?
Comment #38
fabianx commented#2495001: [meta] Add a replacement for renderRoot() that returns a cacheable render array or object instead of a string is the issue for that, will link.
Comment #39
Crell commentedThen check the interface and the content-type header. Type hinting on a class is a bad idea.
Comment #40
fabianx commentedAll feedback addressed, also fixed the tests.
Notable:
This is future-proofing the code for when #markup is XSS filtered to ensure this patch does not conflict with that one.
Comment #41
fabianx commented#39: Yeah, its either:
A:
OR
B:
However given that its called HtmlResponseSubscriber AND it is not the most usual thing to override HtmlResponse at all (could subclass) AND Symfony does not provide a ResponseInterface either AND even if you really needed a different class you could easily subclass HtmlResponseSubscriber and just call $this->prepareAttachments() for yourself ...
I would agree with Wim in this special case that A) is better - unless you insist that we write a HtmlResponseInterface.
Comment #42
tim.plunkettI'd like this to be public. I mimicked this in some code I was working on for a hackathon: https://github.com/acquia/acquia_lift/blob/cardstack-hackathon/src/Contr...
Comment #43
wim leersI think this is almost ready now.
Clever!
Not just extra headers.
It feels like "process" would be more appropriate than "prepare". Or even "apply".
The first 3 quoted lines can be moved into the quoted if-statement.
80 cols.
s/$key/$type/
render()… that should probably beRenderer::renderPlain().Also, what happens if
$variables[$key]doesn't exist? e.g. if there are no header scripts.Comment #44
wim leers#42:
Isn't this enough?
Comment #45
fabianx commented#43:
3. Nope, else drupal_get_http_header() fails, need to move 'http_header' out of drupal_process_attached first.
---
Unassigning for now and tagging Novice for someone else to help bring this over the finish line.
Comment #46
wim leersThis feels hardly novice, but the remarks in #43 are doable, so I guess you're referring to that.
Comment #47
Crell commentedAre we not still moving this to a service? It's way too much code to put in a listener, IMO.
Also, when doing so I would strongly recommend returning the resulting response. Yes it's an object so it passes by handle so it would be slightly redundant, but we should be getting in the habit of returning values, not using input/output parameters. IMO in modern code those are a code smell, because that behavior is highly non-obvious. It also makes pipeing or chaining behavior much more difficult. Return values are your friend. :-)
You'll probably hate me for saying this, but..
setContent on Response() assumes a string, or string-able. An array is not itself stringable. we're providing the stringification. Should this instead be a setRenderContent() method, or similar, so that we can type hint the parameter to array?
... Actually... never mind. That would make it arguably harder to evolve the arrays into something more structured in the future. (Eg, the return of HtmlFragment :-) ).
Comment #48
dawehnernitpick ... Contains \...
I try to avoid using anything in constructors because this potentially triggers a chain of things. Especially a configfactory get, has potential for a lot of things going on. Pratically it doesn't matter because this service will just be initialized on KernelEvents::RESPONSE, but we could easily add a different event and then up with a hard to debug problem.
Is that hunk needed? Seems a little bit out of scope or at least some form of API change as part of teh patch
Comment #49
lauriiiRerolled & fixed #43 and #48.1
Comment #51
wim leersA bunch of changes broke this patch, most notably #2381277: Make Views use render caching and remove Views' own "output caching". The changes there are significantly complicating the reroll, because the Views contextual links hacks have been changed completely. Trying to devise a solution…
Comment #52
wim leersThis should do it. No interdiff, this is purely chasing HEAD.
Comment #53
wim leers#48.2: I bet this was just c/p'ed from
FinishResponseSubscriber, which does exactly the same thing. But, you're right of course. OTOH, there are at least 25 pre-existing occurrences of this (grep for= $config_factory->get), and I can't think of a cleaner solution… so keeping this for now.#48.3: yes, that hunk is needed, because of the last bullet in #34: in HEAD,
#type => pageis rendered first, then#type => html— this patch changes that. I.e. this bit:Comment #54
wim leersUgh, #52 was based on the green patch in #49. I didn't realize that was the straight reroll, without the interdiff applied. So, restoring the #49 interdiff…
Comment #56
wim leersGoing over recent reviews, to get this issue going again.
HtmlResponseSubscriberwas updated, butBareHtmlPageRendererand the various "direct rendering" edge cases were not. This is likely the cause of the failures in #49 (and the same failures should be present in #54). Fixed that.#45: okay, so #49 shouldn't have done #43.3. Restored previous code, with a clarifying comment.
#47:
However… given that we are now calling
$bare_html_page_renderer->processAttachments($response);in half a dozen places (which is in fact not on the interface of that service), and that itself also just redirects to\Drupal\Core\EventSubscriber\HtmlResponseSubscriber::processAttachments(), which cannot be an interface method… I've come around :)Let's just have an
HtmlResponseAttachmentsProcessorservice, plus an analogous one forAjaxResponse. These services respectively accept only anHtmlResponseand anAjaxResponse, plus the currentRequest(to be able to send only the additionally needed assets). I'll do that in the next reroll.#48: addressed by #49 + #53.
Code review:
(As mentioned above:
s/prepare/process/, fixed in this reroll.)We really need to improve this — I think it's the biggest remaining problem in the patch.
We inject the
RenderCacheservice just to be able to do this.#2495001: [meta] Add a replacement for renderRoot() that returns a cacheable render array or object instead of a string will fix that.
But I wonder if it's truly necessary? I guess the answer is "no", but we want to do it anyway because 1) it is harmless, 2) it keeps the door open for a value object rather than an ArrayPI?
Why not just use
AttachedAssetsInterface?i.e. instead of:
do:
class HtmlResponse extends Response implements CacheableResponseInterface, AttachedAssetsInterfaceI suspect that will be significantly cleaner.
…
Oh, I realized why: because
AttachedAssetsInterfaceonly supports libraries/settings, whereas responses also need to support non-asset attachments, such as headers, and HTML<head>tags. Gaahh!Yeah this is just awful. I hope to be able to get rid of this :)
We should also add this todo to
BareHtmlPageRenderer.Next: implementing the bits I pointed out above that need work.
Comment #57
wim leersI forgot to attach the patch that is associated with the above comment. It just addresses the tiny bits that went wrong in previous patches.
Comment #60
wim leers(Gah, testbot was terminated… the same 1 failure exists as in #54. Testbot--.)
\Drupal\Core\Render\AttachmentsResponseProcessorInterface\Drupal\Core\Render\HtmlResponseAttachmentsProcessor, which implements the above interface, and is the service that Crell has been asking for :)\Drupal\Core\Render\BareHtmlPageRendererInterface— which I think is completely acceptable since only the most esoteric/highly advanced use cases want to use this — we can get rid of the manual processing of attachments in all of those PHP/INC files, and instead let theBareHtmlPageRendererdo that: the API change makes it that we return aHtmlResponseobject rather than a string of HTML, which means we can already do the attachment processing in there.Comment #61
wim leersAnd now
AjaxResponsegets the same treatment! Finally, consistency WRT asset handling for different types of responses.Note that there is still some level of cruft in
\Drupal\Core\Ajax\AjaxResponseAttachmentsProcessor(notably the module handler altering), but that's pre-existing, and is out of scope to fix here. It's also too risky to change here, since there's so little test coverage for it.Comment #63
wim leersFixed the one failing test.
Self-review, with nitpicks already fixed:
Oopsie. Fixed.
This is strange, but it's pre-existing strangeness, so I'm keeping it. Improving this is out of scope.
If the above weirdness disappears, we could inline this method.
Nit: this should be renamed to
AjaxResponseSubscriberfor consistency.Thoughts?
This stuff was moved in here, because it applies to AJAX responses at a more general level: it is completely unrelated to attachment/asset handling, therefore I kept this bit out of the AJAX response attachment processor.
Missing leading backslash, fixed.
Apparently this is not a problem (at least I can't break it), so removing the @todo. Still not happy with all this master request business though, but it exists elsewhere as well, so… fixing this is out of scope.
2 newlines, should be 1, fixed.
This is wrong now, and is also quite useless info. Removed.
Silly newline, removed.
Wrong, fixed.
Is this really necessary? It's going to be stored in a
HtmlResponseanyway, which already removes the ability to drill down… so I'm not sure what we're gaining here?Thoughts?
Since #2477461: Move X-Generator header to its own listener, this is dead code, since it is handled elsewhere now. Removed.
Missing space at the end, fixed.
Unused, removed.
With that, this is now blocked on review.
Next: IS update.
Comment #64
wim leersFor clarity: I need feedback on points 4 and 12.
Comment #65
wim leersComment #67
wim leersLooks like #61 broke something in AJAX. Looking at that.
Comment #69
wim leersFailures due to tests still calling
AjaxResponse::prepare(). Fixed tests.Comment #70
fabianx commentedGreat changes!
Love the new patch! :)
Having BareHtmlRenderer directly return a Response is so much nicer and makes so much more sense!
#63.4:
Rename to AjaxResponseSubscriber makes sense to me. BC break should be limited as it is just a listener. If there is BC concerns can add class alias and service alias.
#63.12:
Yes, responses are cached via PageCache, which serializes the Response as is. Don't think we want to store the whole render tree, do we?
Little thing I found in my self-review:
I thought about this again:
It won't fly.
What if a form throws a RedirectResponse?
We surely don't want to display messages for that.
However Twig_Error_Runtime eats most of the useful information.
There is two ways:
a) Create a new \RuntimeException / custom Twig Exception with both messages concatenated.
b) Have the ErrorHandler unwrap all previous Exceptions and display them separately as messages.
I think unwrapping would be useful anyway.
c) Decide its out-of-scope here and just fix the test and open a follow-up to make Exception output more useful (again).
Comment #71
wim leersThis means that even if
$content(as passed to theHtmlResponseconstructor) is still a non-flattened render array (i.e. if we wouldn't do the::getCacheableRenderArray()step), that during the construction, we only keep the top-level#markupanyway; the rest is thrown away.So… that's why I don't understand what you're getting at.
\Twig_Error_Runtime: I vote option c, decide it is out of scope here, because I frankly don't understand this 100%, whereas the rest of this issue is very understandable. So if you think it is fine to defer it to a follow-up, I think that's preferable.Comment #72
Crell commentedOverall this is looking really good! At this point I feel safe retitling the issue as well, since we want to go through with this.
Some comments below. I don't fully understand the Twig issue so defer to FabianX on that front.
We've been actively trying to expunge the word "exit" from the code base, because it breaks many many things, like some testing strategies. Even in error handling. Why do we need to add one here?
Likely off-topic, but haven't we pushed Maintenance Mode into a container parameter or something by now? I know we moved some of our other constants there. A global constant here is sad-making.
This variable will always be set. It may be null, or some other falsy value, but it will always be set on line 122.
The comment seems out of sync, as the code right after it is handling the settings it says are later.
Minor point: Presumably $css_assets is an array, so an empty array is already false, so the !empty() is unnecessary. Same for the other blocks below.
This is an injected class. Just inject the Module Handler, too.
To improve predictability, shouldn't this merge the array rather than just overwrite it? That is, if someone passes in an array that has onl library and feed, then we end up with an array that loses those other properties. That makes the structure of the return value of getAttachments() unpredictable.
We can probably just do:
$this->attachments = $attachments + $default_array;
Where $default_array is the same as the, er, default value of $this->attachments. That way, $this->attachments (and thus getAttachments()'s return value) is always guaranteed to have all 6 keys.
Perhaps add a @todo that once we start using assertions this seems like a good place for one. (More involved type checking than PHP natively allows.)
Seems these lines don't need to be separate...
I'm sure I sound like a broken record by now, but... why are these functions still here? Isn't the whole point of the renderers and such that we can finally get rid of these damned test-breaking things?
The comment is incorrect. Status is not a header in the first place. It's PHP native and Drupal that get it wrong. :-) The comment should reflect that we're tracking status as if it were a header for, um, historical reasons I guess?
The kernel and processor can be mocked, making this a proper unit test of the subscriber. Also, faster. :-)
Kind of like this, in fact. :-)
Comment #73
wim leers#70: Did #63.4.
#72:
Glad you like it too, Crell :)
And thank you for the rename, that is a much, much better title :)
isset($ajax_page_state['libraries']).drupalSettings. Those are properly encapsulated, non-globally-polluting-crap-piles. But they've only been like for a number of months. It took that much effort to clean them out.Yes, we can get rid of it now. If only I had 100-hour days. But at least it is now contained crap, that we could perhaps clean up during the 8.x cycle.
So, I invite you to help get rid of them. They're significantly easier to refactor away than the asset libraries in any case. But they're simply not important enough, considering the other problems we need to fix to ship D8.
WebTestBasetest. I don't want to import PHPUnit'sgetMock()intoWebTestBase, kittens will be slaughtered en masse, WORSE EVEN: llamas will be too!(Now, this should be a unit test, no question. But doing that is out of scope.)
@Crell @Fabianx: AFAIK that leaves two more things:
\Twig_Error_Runtimething. What are the practical consequences of leaving that hunk out of this patch?Comment #74
fabianx commented#63.12: Can we leave the getCacheableRenderArray for now with a @todo on the renderRoot() issue. I realize it is superfluous, but putting the argument instead of the result into the response feels more wrong to me ...
2. Leave it out and see 1 test fail ;). You'll then know way more exactly of what I am talking about.
Comment #75
wim leersThat's an excellent point. Ok.
Comment #76
wim leersI just realized something:
AttachmentsResponseInterfaceshould probably be renamed to something likeAttachmentsInterface(i.e. not beResponse-specific — though the name I am using for now is also definitely not great), thenBubbleableMetadatacan implement that interface.Then we'd have a nice symmetry:
CacheableDependencyInterface: implemented byCacheableMetadata(andBubbleableMetadata) andHtmlResponseAttachmentsInterface: implemented byBubbleableMetadataandHtmlResponse+AjaxResponseIt allows us to deal with all of these objects in consistent ways.
Comment #77
Crell commented#76: +1!
#73.7: I think I wasn't clear about what is being merged then. With the current code, you get this:
What we want instead is for $after to contain the same 6 keys, 5 of which are empty arrays and one of which is the value provided by the caller. That way, code calling getAttachments() can rely on there always being 6 keys (no isset() calls) and that the unused keys have a falsy (empty array) value.
Comment #78
fabianx commented#76: +1 to AttachmentsInterface :)
#77: Lets call it:
addAttachments() then instead?
Comment #79
fabianx commentedThis makes implementing RenderStrategies (and hence BigPipe) much simpler, because the 3 separate renderings of the page array then one rendering afterwards make it really complicated to properly render placeholders after the main processing has been done. As essentially the render array is rendered 4 times, but three times with renderRoot, then once without ...
Comment #80
wim leersUpdating the IS per #79.
Comment #81
fabianx commentedIt also makes SmartCache simpler, because it just needs to change renderRoot() to render() and then just needs to execute the placeholders after the cache get instead of having to deal with the 3 regions, because template_preprocess_html() runs too early.
It also makes it possible to remove / deprecate drupal_process_attached() in a follow-up. (I found no more blockers for it.)
Comment #82
wim leers#77: RE: #73.7: Yes, you could get that, but the code example you give is exactly how I'd expect it to work too :) If you're setting
['headers' => []], then I'd also expect to only get back that just headers, regardless of what the prior value was. That's what "to set" means.The behavior you want, would have to live in a
addAttachments()method. And guess what, that's exactly whatBubbleableMetadatadoes today :)Having written that, I do see what you mean now though. But it'd be inconsistent with how we've done things in
BubbleableMetadataand elsewhere. I think it's better to be consistent for now, and use the same behavior as elsewhere in core; we can choose to change that everywhere (and thus remain consistent across core) in a follow-up.… and after I already wrote this, I see @Fabianx's #78, which is exactly what I am suggesting above as well :)
So, given that I've got two +1s for #76, plus this discussion with Crell and Fabian's + my stance, which Crell will hopefully agree with, this patch does:
AttachmentsResponseInterfacetoAttachmentsInterfaceAttachmentsInterface::addAttachments()AttachmentsInterfaceto match the order inCacheableMetadataandBubbleableMetadata(get, add, set)BubbleableMetadatato implementAttachmentsInterfaceAttachmentsResponseTraitto implement::addAttachments()Comment #87
fabianx commentedIf we move AttachmentsResponseTrait to AttachmentsTrait, could BubbleableMetadata use that then? :))))))
Also lets not forget we have that namespaced, it is actually \Drupal\Core\Render\AttachmentsInterface which is obviously way way more specific ...
Comment #93
Crell commented#87: I like the way you think. :-)
Wim: That we're doing it wrong elsewhere is no reason to keep doing it wrong. :-) But if that must get split to a follow-up, so be it. Can you file? (I don't know all the places that we'd need to change.) (Insert stock rant about value objects being better than arrays here.) An addAttachments() Is fine; I don't much care, but it's a separate question.
#73.12: Won't somebody think of the llamas!
Comment #98
wim leersI forgot to update
core.services.ymlcorrectly in #82. It should fail.#87: Hah :) I also considered this, but decided against it because we cannot do it elegantly until #2505171: Follow-up for #2483433 lands, which cleans up
BubbleableMetadata::addAttachments(). I should've known that you'd also think of this :DLooking again at that issue, I can actually make it work already, no need to block this on that issue. There will be some level of conflict, but it'll be very manageable. Let's do it :)
#93.A: true, but it's debatable whether it's actually wrong :) In any case, it's a detail, and in that case, consistency matters most. So yes, filed a follow-up to address that:
#93.B: :D
Comment #100
wim leersSo that one test failure is because I removed that pesky Twig hunk (the one that neither Crell nor I understand) from the patch in #75.
Working on it.
Comment #101
wim leersSo, HEAD does this:
(The reason is explained in the quoted comment, but basically: "because asset rendering".)
But this patch changes it to be simply:
This is part of the clean-up that is possible thanks to encapsulating HTML response logic properly.
However, as a side-effect of this clean-up, the
pagetemplate (#type => page) is now no longer rendered by theRendererdirectly, it is rendered by Twig, which then callsRenderer. (Because we invoke theRenderon#type => html, which contains#type => page, rather than calling it on#type => pagedirectly, like HEAD does.)So, rather than seeing the
Renderer's exception, we now seeTwig's exception, which is why the test fails.Fabian outlines 3 possible solutions at the bottom of #70. His problem with his patch was: . Which is totally true.
But I think there's a much simpler solution: just don't set that message :) Just re-throw the previous exception, and everything continues to work as it does today. (And if desired, we can still choose to improve upon that in a follow-up.)
Thoughts?
Comment #102
fabianx commentedSounds great, lets get a follow-up however to ensure that we don't regress for twig.
However overall the Exception will always be more helpful than the line where it occurred in the template.
"Yes, we printed a {{ content }} array, AND?"
--
nit:
We can now remove all get/set except addAttachments, which still uses the renderer, but could be follow-up or fixed on commit.
Comment #103
wim leersDone: #2508309: Improve Twig's handling of exceptions.
I truly have no idea what this means.
Comment #104
wim leersFabianx clarified what he meant. I simply forgot to remove
BubbleableMetadata's::addAttachments()implementation, which is now provided by the trait. Oops :)Comment #105
wim leersWorse, I actually forgot to remove
BubbleableMetadata'saddAttachments()and::getAttachments()too — i.e. I simply forgot to remove the code inBubbleableMetadatathat now lives in the trait thatBubbleableMetadatauses. Oops.Comment #106
fabianx commentedRTBC, thanks so much!
Comment #107
Crell commented#98 looks like you meant to paste a link to a follow up, but didn't? I think that point was my last blocking objection, so if the follow up exists then I'm +1 on committing here. Thanks Wim and Fabian!
Comment #108
wim leers#107: Oopsie, done: #2508543: [PP-1] Require that AttachmentsInterface implementations have all attachment types present by default.
Glad you +1'd the RTBC! :) Another step closer to BigPipe!
Comment #109
alexpottThere's quite a few unused use statements now amongst this lot.
I think the empty($content) check is superfluous - but I guess not part of this patch.
Not used.
Does each placeholder need a separate token? Is this more secure?
Is it necessary to for the result of this to be in the list of safe strings?
AttachedAssets is no longer used in this include.
I think this needs to return the response to conform to the interface.
This is deprecated for 8.0.x and this is fresh code - anything we can do here?
This is deprecated for 8.0.x and this is fresh code - anything we can do here?
CacheableMetadata is not used anymore.
This has to be both a response and an attachementsinterface - do we think it is worthwhile creating a ResponseAttachmentsInterface?
Comment #110
joelpittetHere's the re-roll.
Comment #111
joelpittetResolved the smaller bits: from #109
Comment #115
joelpittetHmm, didn't do much just removed use statements that PHP storm was pointing out in the files mentioned in #109 Sorry for breaking things:S
Comment #116
joelpittetHmm maybe I messed something up in that patch. This one seems to work better.
Comment #117
wim leersThanks, Joël!
theme.inc)ResponseInterface, only the default implementation… :( Next best thing: create anAttachmentsResponseBaseclass… except that won't help either, sinceAjaxResponse extends JsonResponse. So: we're doing the best we can, consider the circumstances.Back to RTBC for committer feedback, consider this was all small stuff.
Comment #118
wim leersOops, forgot the beta evaluation.
Comment #120
wim leers#2505171: Follow-up for #2483433 broke this, rerolled.
Note that that patch implemented the order of merging in
BubbleableMetadata::addAttachments()inversely, which actually makes less sense. So, tiny change to test coverage too.Comment #122
wim leersNow #1305882: drupal_html_id() considered harmful; remove ajax_html_ids to use GET (not POST) AJAX requests broke a few more things, because a few tests started referring to a constant in
AjaxSubscriber, andAjaxSubscriberis renamed in this patch. Hopefully this is the last one :)Comment #123
fabianx commentedBack to RTBC
Comment #124
Crell commentedTagging because this seems reroll-fragile based on the last 24 hours or so. :-)
Comment #125
alexpottCommitted 42c693a and pushed to 8.0.x. Thanks!
Thanks for adding the beta to the issue summary.
Comment #127
wim leersCreated a follow-up: #2512382: Follow-up for #2407195: #attached['http_header'] being added to Response in two places.
Comment #129
wim leersThis also allowed me to close #2407201: Consider introducing AjaxResponse::addAttachments().
Comment #131
cilefen commented#2655700: ajaxRender() is referenced in documentation but no longer exists