Error:
<code>
Fatal error: Call to a member function getBodyAttributes() on a non-object in /var/www/shelepen/drupal/core/themes/seven/seven.theme on line 39
</code>
Is invoked when you translate configuration like:
admin/config/system/site-information/translate

Trace:
<code>
1 0.0003 130784 {main}( ) ../index.php:0
2 0.0109 527364 drupal_handle_request( ) ../index.php:15
3 0.1739 8434488 Drupal\Core\DrupalKernel->handle( ) ../bootstrap.inc:1866
4 0.1761 8558168 Drupal\Core\HttpKernel->handle( ) ../DrupalKernel.php:282
5 0.1761 8559048 Symfony\Component\HttpKernel\HttpKernel->handle( ) ../HttpKernel.php:52
6 0.1761 8559080 Symfony\Component\HttpKernel\HttpKernel->handleRaw( ) ../HttpKernel.php:61
7 3.2524 12665620 Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch( ) ../HttpKernel.php:122
8 3.2551 12803908 Symfony\Component\EventDispatcher\EventDispatcher->dispatch( ) ../ContainerAwareEventDispatcher.php:167
9 3.2552 12804300 Symfony\Component\EventDispatcher\EventDispatcher->doDispatch( ) ../EventDispatcher.php:53
10 3.2552 12804824 call_user_func ( ) ../EventDispatcher.php:164
11 3.2552 12804840 Drupal\Core\EventSubscriber\ViewSubscriber->onView( ) ../EventDispatcher.php:164
12 3.2553 12805064 Drupal\Core\EventSubscriber\ViewSubscriber->onHtml( ) ../ViewSubscriber.php:83
13 3.2567 12928696 drupal_render_page( ) ../ViewSubscriber.php:180
14 3.2928 14121276 drupal_render( ) ../common.inc:3675
15 3.2929 14122284 theme( ) ../common.inc:3883
16 3.4364 17377792 seven_preprocess_page( ) ../theme.inc:667
</code>

Look at:
<code>11 3.2552 12804840 Drupal\Core\EventSubscriber\ViewSubscriber->onView( ) ../EventDispatcher.php:164
12 3.2553 12805064 Drupal\Core\EventSubscriber\ViewSubscriber->onHtml( ) ../ViewSubscriber.php:83
</code>

For an example:
admin/config/system/site-information - It works but it has another trace.
<code>
1 0.0001 130708 {main}( ) ../index.php:0
2 0.0092 527236 drupal_handle_request( ) ../index.php:15
3 0.1466 8434340 Drupal\Core\DrupalKernel->handle( ) ../bootstrap.inc:1866
4 0.1487 8558024 Drupal\Core\HttpKernel->handle( ) ../DrupalKernel.php:282
5 0.1487 8558908 Symfony\Component\HttpKernel\HttpKernel->handle( ) ../HttpKernel.php:52
6 0.1488 8558940 Symfony\Component\HttpKernel\HttpKernel->handleRaw( ) ../HttpKernel.php:61
7 0.2453 11810144 Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch( ) ../HttpKernel.php:122
8 0.2463 11839412 Symfony\Component\EventDispatcher\EventDispatcher->dispatch( ) ../ContainerAwareEventDispatcher.php:167
9 0.2463 11839792 Symfony\Component\EventDispatcher\EventDispatcher->doDispatch( ) ../EventDispatcher.php:53
10 0.2464 11840112 call_user_func ( ) ../EventDispatcher.php:164
11 0.2464 11840128 Drupal\Core\EventSubscriber\HtmlViewSubscriber->onHtmlFragment( ) ../EventDispatcher.php:164
12 0.2464 11840192 Drupal\Core\Page\DefaultHtmlPageRenderer->render( ) ../HtmlViewSubscriber.php:49
13 0.8555 17732140 drupal_render( ) ../DefaultHtmlPageRenderer.php:52
14 0.8556 17733212 theme( ) ../common.inc:3883
15 3.3544 20403144 seven_preprocess_page( ) ../theme.inc:667
</code>

Look at:
<code>
11 0.2464 11840128 Drupal\Core\EventSubscriber\HtmlViewSubscriber->onHtmlFragment( ) ../EventDispatcher.php:164
12 0.2464 11840192 Drupal\Core\Page\DefaultHtmlPageRenderer->render( ) ../HtmlViewSubscriber.php:49
</code>

Where is the page is rendered and the variable is set.

I am a little bit confused. This two pages are rendered in different events: ViewSubscriber, HtmlViewSubsriber.

HtmlViewSubsriber looks modern.

CommentFileSizeAuthor
#82 interdiff.txt1.77 KBWim Leers
#82 htmlpage-2167039-82.patch18.34 KBWim Leers
#76 2167039-diff-73-76.txt984 bytesvijaycs85
#76 2167039-regression-theme-render-76.patch16.93 KBvijaycs85
#73 htmlpage-2167039-73.patch16.82 KBdawehner
#73 interdiff.txt465 bytesdawehner
#62 interdiff.txt1.84 KBWim Leers
#62 htmlpage-2167039-62.patch17.82 KBWim Leers
#57 interdiff.txt6.54 KBdawehner
#57 htmlpage-2167039.patch15.75 KBdawehner
#53 interdiff.txt1.2 KBdawehner
#53 controller_cache-2167039.patch16.36 KBdawehner
#50 interdiff.txt6.34 KBWim Leers
#50 controller_cache_2167039-50.patch17.15 KBWim Leers
#50 controller_cache_2167039-50_FAIL.patch2 KBWim Leers
#47 interdiff.txt2.91 KBdawehner
#47 controller-cache-2167039.patch13.63 KBdawehner
#45 interdiff.txt642 bytesdawehner
#45 controller-cache-2167039.patch14.96 KBdawehner
#42 interdiff.txt4.37 KBdawehner
#42 controller-cache-2167039.patch14.49 KBdawehner
#33 2167039-31-32-interdiff.txt545 bytesvictor-shelepen
#33 2167039-32-content-vs-controller.patch7.58 KBvictor-shelepen
#32 2167039-20-31-intediff.txt2.26 KBvictor-shelepen
#31 2167039-31-content-vs-controller.patch6.89 KBvictor-shelepen
#27 interdiff.txt9.83 KBdawehner
#27 controller-2167039.patch14.07 KBdawehner
#20 2167039-20-content-vs-controller.patch4.34 KBtstoeckler
#20 2167039-13-20-interdiff.txt3.21 KBtstoeckler
#13 drupal-autocomplete_widget_bug-2156649-13.patch3.77 KBvictor-shelepen
#11 2167039-controller_to_content-11.patch2.12 KBvictor-shelepen
#6 2167039-controller_to_content-6.patch1.44 KBvictor-shelepen
#6 2167039-controller_to_content-6.patch1.44 KBvictor-shelepen
#5 2167039-controller_to_content_test-4.patch603 bytesvictor-shelepen
#3 2167039-controller_to_content_1.patch868 bytesvictor-shelepen
#1 2167039-controller_to_content_1.patch2.6 KBvictor-shelepen
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

victor-shelepen’s picture

victor-shelepen’s picture

#1 solves the problem. We also need create a test to cover this issue.

(16:21:01) alexpott: likin: we need to work out why ConfigTranslationUiTest is not failing.

victor-shelepen’s picture

FileSize
868 bytes
victor-shelepen’s picture

The template_preprocess_page has been modified to fail the test ConfigTranslationUiTest. The patch in the #3 comment should fix it.

victor-shelepen’s picture

Status: Needs work » Needs review
FileSize
603 bytes
victor-shelepen’s picture

This patch adds a failure to the test and solves the problem.

The last submitted patch, 5: 2167039-controller_to_content_test-4.patch, failed testing.

The last submitted patch, 6: 2167039-controller_to_content-6.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 6: 2167039-controller_to_content-6.patch, failed testing.

dawehner’s picture

You need to take care of the following piece of code:

system_test.cache_tags_page:
  path: '/system-test/cache_tags_page'
  defaults:
    _controller: '\Drupal\system_test\Controller\SystemTestController::system_test_cache_tags_page'
  requirements:
    _access: 'TRUE'

in system_test.routing.yml.

victor-shelepen’s picture

Status: Needs work » Needs review
FileSize
2.12 KB

Status: Needs review » Needs work

The last submitted patch, 11: 2167039-controller_to_content-11.patch, failed testing.

victor-shelepen’s picture

Status: Needs work » Needs review
FileSize
3.77 KB

Status: Needs review » Needs work

The last submitted patch, 13: drupal-autocomplete_widget_bug-2156649-13.patch, failed testing.

victor-shelepen’s picture

Status: Needs work » Needs review

The last submitted patch, 3: 2167039-controller_to_content_1.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 13: drupal-autocomplete_widget_bug-2156649-13.patch, failed testing.

tstoeckler’s picture

Awesome, thanks for fixing this.

I don't really think we wan't to add runtime code to template_preprocess_page() just to produce a test fail, though. I will look into this now and see if I can come up with something else.

tstoeckler’s picture

So "now" didn't really happen because I fell asleep :-)
Will look into this later today, though...

tstoeckler’s picture

Status: Needs work » Needs review
FileSize
3.21 KB
4.34 KB

So I did actually work on this yesterday, just forgot to upload the patch :-)
Here it is. It's just a slightly less hacky version of achieving some test-coverage.
I really could not think of any cleaner way of sending information from the test request to the testing request than an exception.
Let's see.
I also reverted the change to PageCacheTest, as that didn't seem to have fixed the test failure.

tstoeckler’s picture

Oh and I forgot to mention: I looked through all *.routing.yml files in core for whether they use _controller incorrectly and only found the aggregator one that is contained in the patch above.

victor-shelepen’s picture

Simpletest_preprocess_page is smart solution for tests.
In original version inalidatetags was used. It brocke the test. I moved to deleteTags. Locally that test passed but on drupal.org failed. I disscussed this replacement in irc.

Status: Needs review » Needs work

The last submitted patch, 20: 2167039-20-content-vs-controller.patch, failed testing.

tstoeckler’s picture

@likin: I just saw that it didn't fix the fail with the bot, so I reverted it. Did not try it locally yet, but will.

tstoeckler’s picture

Title: Page object is not set to page variables. Error invoked in seven_preprocess_page » Some routes incorrectly use _controller -> No page object in template variables -> Fatal in seven_preprocess_page()

Also renaming this, because I couldn't find it just now :-)

dawehner’s picture

We figured out the current problem. Before the HtmlPage issue drupal_render(drupal_prepare_page()) have been executed on the original controller render array,
which allowed to let cache tags to bubble up to #type page, and so gets called by drupal_post_render_cache_tags_page_set(). See system_element_info()

With the HtmlPage render pipeline we create a HtmlFragment first, so the cache tags no longer bubble up.

Crell, msonnabaum suggested to use the CacheableInterface from https://drupal.org/files/issues/blockcache-2158003-6.patch to pass the cache tags from the HtmlFragment to its parent and so to the page cache.

dawehner’s picture

Status: Needs work » Needs review
FileSize
14.07 KB
9.83 KB

There are really ugly parts here (converting things back and forth).

Crell’s picture

  1. +++ b/core/lib/Drupal/Core/Page/HtmlFragment.php
    @@ -39,9 +40,18 @@ class HtmlFragment {
    +      'max_age' => REQUEST_TIME,
    

    Shouldn't this be max_age 0? It's not expire time but "how long it should live from now". (At least I think that's what max_age is supposed to mean.)

  2. +++ b/core/modules/simpletest/simpletest.module
    @@ -877,3 +878,20 @@ function simpletest_phpunit_testcase_to_row($test_id, \SimpleXMLElement $testcas
    +function simpletest_preprocess_page(&$variables) {
    +  // If controllers accidentally use _controller instead of _content, but do not
    +  // return a response object or an HTML page object the site still renders
    +  // properly but using the page object in template_preprocess_page() will fail.
    +  // We therefore throw an exception on the test site in this case so that every
    +  // call to WebTestBase::drupalGet() automatically benefits from this coverage.
    +  if (drupal_valid_test_ua()) {
    +    $page_object = $variables['page']['#page'];
    +    if (!($page_object instanceof HtmlPage)) {
    +      throw new \Exception('The page object is not an instance of \Drupal\Core\Page\HtmlPage');
    +    }
    +  }
    +}
    

    Incidentally, I love putting this check here. +1!

The last submitted patch, 27: controller-2167039.patch, failed testing.

The last submitted patch, 27: controller-2167039.patch, failed testing.

victor-shelepen’s picture

victor-shelepen’s picture

FileSize
2.26 KB
victor-shelepen’s picture

The last submitted patch, 31: 2167039-31-content-vs-controller.patch, failed testing.

The last submitted patch, 33: 2167039-32-content-vs-controller.patch, failed testing.

moshe weitzman’s picture

The latest patch looks like it is converting cache keys to cache tags. That looks very fishy. Am I missing something?

victor-shelepen’s picture

Cache system becomes more complicated. It likes a puzzle. Keys are used to generate cid, cid is a cache identification. Tags are used to tag a cache. To make the cache system more flexible. To reset cache items invaliadateTags, deleteTags are used. But they do not affect to cache(in code). So I think keys, tags are the same things. For me it is more logical. For this time I do not see the way where we need keys, tags to be separated.

moshe weitzman’s picture

They aren't the same thing. Tags are only used for invalidation. Keys are used to distinguish different entries in the cache for hit/miss purposes. It is crucial to keep this distinction in mind.

msonnabaum’s picture

What moshe said.

THis is actually a security issue, because drupal_render_cache_by_query takes node_grants into account when generating a cache key, so you won't get a cache hit for two users with different node grants.

dawehner’s picture

@likin
I really think we should better continue with the approach of #2167039-27: Regression: page cache tags broken, also: some routes incorrectly use _controller -> No page object in template variables -> Fatal in seven_preprocess_page()
This won't introduce any security issue or something else.

moshe weitzman’s picture

#27looks quite sensible to me.

dawehner’s picture

Figured out that cache tags are indeed multi-dim so you cannot just use implode('--').
I went with serialize/unserialize as I wasn't sure about a better alternative.

The last submitted patch, 42: controller-cache-2167039.patch, failed testing.

The last submitted patch, 42: controller-cache-2167039.patch, failed testing.

dawehner’s picture

FileSize
14.96 KB
642 bytes

Let's see.

The last submitted patch, 45: controller-cache-2167039.patch, failed testing.

dawehner’s picture

This should fix the remaining test failures.

The last submitted patch, 47: controller-cache-2167039.patch, failed testing.

dawehner’s picture

47: controller-cache-2167039.patch queued for re-testing.

Wim Leers’s picture

Title: Some routes incorrectly use _controller -> No page object in template variables -> Fatal in seven_preprocess_page() » Regression: page cache tags broken, also: some routes incorrectly use _controller -> No page object in template variables -> Fatal in seven_preprocess_page()
Priority: Normal » Critical
Issue tags: +D8 cacheability, +Performance, +Spark, +sprint
FileSize
2 KB
17.15 KB
6.34 KB

(Coming to this because I'm working on #2124957: Replace 'content' cache tag with 'rendered' and use it sparingly, which was impossible to work on, the patch here is the solution.)

This is actually a huge regression introduced by [#2170551]: this completely broke cache tags bubbling up to cache entries in the page cache, which was introduced in #2094241: Cache tag the page cache. However, the test coverage added in #2094241 clearly was too superficial, or at least too artificial: if it would have a test case for e.g. the full node page and then checked that page cache entry's cache tags, this regression could've been avoided.

In this reroll:

  • Adding test coverage to prevent such regressions i/t future.
  • Removed drupal_post_render_cache_tags_page_set(), since that's being bypassed completely now: cache tags are passed to the page cache handling via Symfony response headers set in \Drupal\Core\EventSubscriber\HtmlViewSubscriber::onHtmlPage(), which are then retrieved by drupal_cache_tags_page_get().
  • DefaultHtmlPageRenderer::render() was setting both $cache_info['tags'] and $cache_info['#tags']; the latter was ignored, so removed it.
  • DefaultHtmlPageRenderer::render() was first getting the cache tags from the "page" HtmlFragment's cache info (as set on $page_content), but then it was also collecting the cache tags from $page_array after that was built from $page_content (confusing, right?!). AFAICT that's doing the same job twice.
  • DefaultHtmlPageRenderer::render() wanted to make sure to pass on any caching info from the 'page' HtmlFragment to the HtmlPage that's being built by it. However, that just results in a bunch of ugly code, that might set expectations (i.e. "surely this cache bin and this max age and cache key will be used!"), even though it's completely ignored by the logic responsible for actually filling the page cache (drupal_page_set_cache()). By only passing the caching info that's actually used, I was able to simplify the code, and reduce the diff in that function to a single line being changed.

The last submitted patch, 50: controller_cache_2167039-50.patch, failed testing.

The last submitted patch, 50: controller_cache_2167039-50_FAIL.patch, failed testing.

dawehner’s picture

Thank you for helping on this issue!

The failure was so simple, that I just went and fixed it. Yes, simpletests have issues with logged in users in the UI vs. run-tests.sh
#2176101: drupalCreateNode uses different UID depending on UI vs. run-tests.sh is a follow up for that.

DefaultHtmlPageRenderer::render() wanted to make sure to pass on any caching info from the 'page' HtmlFragment to the HtmlPage that's being built by it. However, that just results in a bunch of ugly code, that might set expectations (i.e. "surely this cache bin and this max age and cache key will be used!"), even though it's completely ignored by the logic responsible for actually filling the page cache (drupal_page_set_cache()). By only passing the caching info that's actually used, I was able to simplify the code, and reduce the diff in that function to a single line being changed.

I am not sure whether this is a good assumption. If the CacheInterface has this information we should leverage it. Maybe block cache could use it or some fancy implementation one day. Let's discuss that in a follow up?

Wim Leers’s picture

Haha, lovely! I was frustrated/puzzled on Friday night, I had no idea why it passed locally but failed on qa.d.o. Thanks a lot for fixing it! :)

I agree that we should pass all relevant cache metadata. But page caching is the top-level operation, which is governed by CMI settings + code, not by page-level metadata: cache keys depend on URL + content type, the cache bin is always the same and max age is governed by a CMI setting. Cache tags are passed. The patch I proposed is therefor the simplest patch possible.
So if you're saying we should figure out how to deal with the additional CacheableInterface metadata, then I agree :)

What's left to be done here? I think somebody that's not dawehner or I could mark this RTBC? tstoeckler perhaps? :)

mondrake’s picture

Just to confirm that patch in #53, tested manually in simpletest.me, solves the issue I reported in #2177291: WSOD when trying to access configuration translation tab.

sun’s picture

  1. +++ b/core/lib/Drupal/Core/CacheableInterface.php
    @@ -0,0 +1,45 @@
    + * Contains \Drupal\Core\CacheableInterface
    

    Can we move this into the subdirectory of whichever core component it relates to?

    It really concerns me that \Drupal\Core itself turns more and more into a dumping ground for arbitrary classes/interfaces, and is deemed to be a "thing" in the first place. It was a big mistake that DrupalKernel (the first of this kind) was originally placed in there — even that belongs to a HttpKernel component.

  2. +++ b/core/lib/Drupal/Core/CacheableInterface.php
    @@ -0,0 +1,45 @@
    +  public function cacheKeys();
    ...
    +  public function cacheTags();
    ...
    +  public function cacheBin();
    ...
    +  public function cacheMaxAge();
    

    It looks like all of these should be prefixed with "get"?

  3. +++ b/core/lib/Drupal/Core/Controller/HtmlControllerBase.php
    @@ -84,4 +86,11 @@ protected function createHtmlFragment($page_content, Request $request) {
    +  protected function drupal_render_collect_cache_tags($element, $tags = array()) {
    

    Let's use methodCase and no drupal_ prefix?

  4. +++ b/core/modules/simpletest/simpletest.module
    @@ -877,3 +878,20 @@ function simpletest_phpunit_testcase_to_row($test_id, \SimpleXMLElement $testcas
    +function simpletest_preprocess_page(&$variables) {
    +  // If controllers accidentally use _controller instead of _content, but do not
    +  // return a response object or an HTML page object the site still renders
    +  // properly but using the page object in template_preprocess_page() will fail.
    +  // We therefore throw an exception on the test site in this case so that every
    +  // call to WebTestBase::drupalGet() automatically benefits from this coverage.
    +  if (drupal_valid_test_ua()) {
    +    $page_object = $variables['page']['#page'];
    +    if (!($page_object instanceof HtmlPage)) {
    +      throw new \Exception('The page object is not an instance of \Drupal\Core\Page\HtmlPage');
    +    }
    +  }
    +}
    

    I'm afraid I do not understand why this change to simpletest is needed and what the test runner has to do with this issue?

    Simpletest module is not enabled in test child sites, so this code is not executed in tests in the first place. Only Simpletest module's own tests are enabling Simpletest module in tests (to test the test runner).

dawehner’s picture

FileSize
15.75 KB
6.54 KB

Lovely a review, thank you!

Can we move this into the subdirectory of whichever core component it relates to?

It really concerns me that \Drupal\Core itself turns more and more into a dumping ground for arbitrary classes/interfaces, and is deemed to be a "thing" in the first place. It was a big mistake that DrupalKernel (the first of this kind) was originally placed in there — even that belongs to a HttpKernel component.

I totally agree with you, so I went ahead and moved it to the cache namespace. I also agree with the DrupalKernel namespace issue, let's add an issue: #2177701: Move the DrupalKernel to a HttpKernel Component instead Drupal\Core

Let's use methodCase and no drupal_ prefix?

Camelcased it, though kept the drupal prefix, to make it as clear as possible what is going on. For example render() instead of drupalRender would be kind of confusing.

I'm afraid I do not understand why this change to simpletest is needed and what the test runner has to do with this issue?

This never worked ... and was kinda just there for debugging reasons. Let's just remove it again.

Wim Leers’s picture

#56.2: regarding the "get" prefixes: I asked the same question, but msonnabaum had good reasons to not have the "get" prefixes. I'll ask him to chime in.

msonnabaum’s picture

It looks like all of these should be prefixed with "get"?

Here's my reasoning for not using the "get" prefix.

The "get" prefix is used to communicate to the reader that the class is providing a public getter for it's internal state. Usually, but not always accompanied by a setter, the implication is that it maps to an instance property (whether or not that's actually true).

In this case, cacheKeys, cacheTags, etc, are part of a separate interface that implies no knowledge of the object's internal state. It's possible that those methods may simply return an instance variable, but that is an implementation detail that is not implied by the interface. It also makes no sense for these methods to ever have corresponding setters.

The get prefix here doesn't communicate anything useful to the reader. Is there anything ambiguous about what is returned from $object->cacheKeys()? I think it's pretty safe to assume that you're getting cache keys back.

Can we move this into the subdirectory of whichever core component it relates to?

It really concerns me that \Drupal\Core itself turns more and more into a dumping ground for arbitrary classes/interfaces, and is deemed to be a "thing" in the first place. It was a big mistake that DrupalKernel (the first of this kind) was originally placed in there — even that belongs to a HttpKernel component.

I disagree this is a bad thing at all. Namespaces aren't just a way to segregate code, they should communicate something. It is no clearer to have \Drupal\Core\Cache\CacheableInterface vs \Drupal\Core\CacheableInterface. On the other hand, DatabaseBackend clearly needs the Cache namespace to clarify it's context. It's a very common convention to only put classes/interfaces within a namespace when it clarifies the intent, not just as a grouping mechanism.

I don't want to start a bikeshed about the interface location, so I'm willing to concede here if others feel very strongly about.

However, I would ask that the method names be reverted.

sun’s picture

Ah-ha! :) Thanks for the explanation, @msonnabaum.

That made sense. Although — after reading it, my next best thought was: Because it's a generic interface, isn't the fact that it does not care how about the actual class internals actually an argument in the opposite direction? — For example, cache items are still anonymous objects today, but if they were based on a dedicated CacheItem class, wouldn't that implement this interface, too? Wouldn't we have getters and setters at that point? :)

In general, I always connected the get/set/is method prefixes primarily with simple data/state management operations — as opposed to methods without such prefix, which I always interpreted to "do" something (more complex). E.g., all of the Entity::id() & Co methods are a misnomer from my perspective. But it's perfectly possible that my interpretation is wrong.

In any case, my original remark was meant as a minor review issue only; where applicable, we can easily rename method names later on, me thinks. No reason to hold up this patch; either way works for me.

msonnabaum’s picture

That's an interesting example. I don't think that a cache item would implement this interface, but there would definitely be some naming overlap there. The difference would be that this interface is for things that can be cached using our cache api, providing the methods to create the cache section of a render array. A cache entry would implement an interface that documented it's identity as a value object.

Wim Leers’s picture

FileSize
17.82 KB
1.84 KB

I introduced a big flaw in #50. That flaw slipped through the cracks of the test coverage, because we only have cache tags for "content region" content so far. If we want to improve the page cache by removing the "content" cache tag (see #2124957: Replace 'content' cache tag with 'rendered' and use it sparingly), then we must ensure that cache tags from all regions are picked up, not just the one in the "content region" of course.

I said:

  • DefaultHtmlPageRenderer::render() was first getting the cache tags from the "page" HtmlFragment's cache info (as set on $page_content), but then it was also collecting the cache tags from $page_array after that was built from $page_content (confusing, right?!). AFAICT that's doing the same job twice.
  • That is not doing the same job twice. It certainly looks so, because the current code is in a kind of transitional phase and therefor extremely misleading. drupal_prepare_page() invokes hook_page_build(), which adds the other regions to the page array… and therefor the changes I made were incorrect.

    Adding test coverage is more onerous here than in #2179083: Rendered menus (e.g. menu blocks) should set cache tags to inform the page cache, which causes cache tags to be set on all menus, including the default "footer" menu, which would make it trivial to add test coverage. In fact, I've written the necessary test coverage in that issue (see \Drupal\system\Tests\Cache\PageCacheIntegrationTest in that patch). Since this patch blocks that patch, let us please get this in ASAP so we can continue there.

    Status: Needs review » Needs work

    The last submitted patch, 62: htmlpage-2167039-62.patch, failed testing.

    The last submitted patch, 62: htmlpage-2167039-62.patch, failed testing.

    Damien Tournoud’s picture

    The "get" prefix is used to communicate to the reader that the class is providing a public getter for it's internal state. Usually, but not always accompanied by a setter, the implication is that it maps to an instance property (whether or not that's actually true).

    You invented that, didn't you?

    Really, it's a convention. The usual convention in PHP is to have a get prefix when you retrieve something from an object, regardless of how this is internally implemented. Other languages have different conventions (some languages have a set prefix but omit the get one, etc.).

    Let's just stick to the convention.

    Wim Leers’s picture

    Passes fine locally.

    Wim Leers’s picture

    62: htmlpage-2167039-62.patch queued for re-testing.

    Wim Leers’s picture

    Status: Needs work » Needs review
    moshe weitzman’s picture

    Code looks fine. Its kinda off how we convert the fragment into a render array but thats not invented.

    +++ b/core/lib/Drupal/Core/Controller/HtmlControllerBase.php
    @@ -84,4 +86,18 @@ protected function createHtmlFragment($page_content, Request $request) {
    +  protected function drupalRender(&$elements, $is_recursive_call = FALSE) {
    

    What is this wrapping about?

    Crell’s picture

    On the whole this looks like a good approach. We're just passing along more cache data with the data objects in a structured fashion. A few comments:

    1. +++ b/core/lib/Drupal/Core/Controller/HtmlControllerBase.php
      @@ -84,4 +86,18 @@ protected function createHtmlFragment($page_content, Request $request) {
      +  /**
      +   * Wraps drupal_render().
      +   */
      +  protected function drupalRender(&$elements, $is_recursive_call = FALSE) {
      +    return drupal_render($elements, $is_recursive_call);
      +  }
      +
      +  /**
      +   * Wraps drupal_render_collect_cache_tags()
      +   */
      +  protected function drupalRenderCollectCacheTags($element, $tags = array()) {
      +    return drupal_render_collect_cache_tags($element, $tags);
      +  }
      +
      

      These don't really have anything to do with HtmlControllers. They belong in their own service(s). Especially with the weird $is_recursive_call handling that drupal_render() requires. Separate that out to somewhere we can clean it up post-8.0 if needs be.

    2. +++ b/core/lib/Drupal/Core/EventSubscriber/HtmlViewSubscriber.php
      @@ -65,6 +65,18 @@ public function onHtmlPage(GetResponseForControllerResultEvent $event) {
      +      if ($bin = $page->getCacheBin()) {
      +        $response->headers->set('cache_bin', $bin);
      +      }
      

      Putting the cache bin in the response seems quite weird to me, but I trust that Mark has some reason why we're doing that.

    3. +++ b/core/lib/Drupal/Core/Page/HtmlFragment.php
      @@ -123,4 +140,39 @@ public function getTitle() {
      +  /**
      +   * {@inheritdoc}
      +   */
      +  public function getCacheKeys() {
      +    return $this->cache['keys'];
      +  }
      

      If this gets rerolled again, can we leave a @todo in here somewhere about turning this code into a trait? It's a perfect use case for it.

    vijaycs85’s picture

    Coming from #2181427: Configuration translation only works with Stark. Trying to address #70.1 however not very sure where those 2 methods fit. Here is the options:

    1. Can be part of DefaultHtmlPageRenderer.php
    2. Can have a Page.php/default.php under Drupal\Core\Render and expose as service?

    since #70.3 is nice to have, we can move forward after #70.1 fixed.

    P.S As this is causing WSOD it most of non-default theme, getting lots of duplicate issues. Btw, any priority higher than 'critical'?

    Wim Leers’s picture

    #69: the wrapping is to prevent crazy problems when writing unit tests: without the wrapping, each unit test class would have to define its own version of that procedural function, but there can only exist one. Chaos ensues. Therefor AFAICT dawehner is to prevent such chaos in the future.

    Lots of duplicates, and blocking performance work too. Let us please get this in ASAP.

    dawehner’s picture

    Issue summary: View changes
    FileSize
    465 bytes
    16.82 KB

    Really, it's a convention. The usual convention in PHP is to have a get prefix when you retrieve something from an object, regardless of how this is internally implemented. Other languages have different conventions (some languages have a set prefix but omit the get one, etc.).

    +1

    These don't really have anything to do with HtmlControllers. They belong in their own service(s). Especially with the weird $is_recursive_call handling that drupal_render() requires. Separate that out to somewhere we can clean it up post-8.0 if needs be.

    Let's keep the signature 1to1 to just point out that it is really a wrapping. In general wrapping external functions is a tool to fix all kind of issues in unit tests.

    Putting the cache bin in the response seems quite weird to me, but I trust that Mark has some reason why we're doing that.

    We certainly should not use any kind of knowledge about the page caching here, maybe there are usecases we don't know yet.

    If this gets rerolled again, can we leave a @todo in here somewhere about turning this code into a trait? It's a perfect use case for it.

    Just added them.

    Gábor Hojtsy’s picture

    Yeah let's speed up getting this in. None of the config translation tabs/paths/local tasks, etc. work currently except if you are using Stark (which was the reason for no prior test fails). That is sort of a big deal :D Not surprising about the many duplicates.

    Crell’s picture

    I don't want to use the functions directly. I'm saying that those methods should be on injected object(s). If we can't do that here for speed/throughput/unbreak things reasons, we should at least add @todos to note that as future refactoring.

    vijaycs85’s picture

    vijaycs85’s picture

    Adding tags...

    Gábor Hojtsy’s picture

    Ready to get in then? :)

    Wim Leers’s picture

    In my opinion: yes. We can nitpick this more, but since it blocks other work, I think it should get in ASAP. No matter what one dislikes about it, this patch definitely improves current D8 HEAD.
    I can't RTBC it though: I worked on it.

    Gábor Hojtsy’s picture

    Status: Needs review » Reviewed & tested by the community

    My vested interest in this issue leads as far as to fix the controller/content callbacks :) Since all other issues were closed as duplicates on this and we need to fix that ASAP, let's get this in and argue about specifics in followups.

    tstoeckler’s picture

    Status: Reviewed & tested by the community » Needs work

    Sorry to downgrade this again, I didn't make the rules, but: 1. violates the docs gate. (Elsewhere as well.)

    1. +++ b/core/lib/Drupal/Core/Cache/CacheableInterface.php
      @@ -0,0 +1,44 @@
      +  /**
      +   * @return array
      +   *   An array of strings or cache constants, used to generate a cached id.
      +   */
      +  public function getCacheKeys();
      

      There should be a one-line description here.

      Also id -> ID

    2. +++ b/core/lib/Drupal/Core/Cache/CacheableInterface.php
      @@ -0,0 +1,44 @@
      + * Defines an interface for things which are potentially cacheable.
      

      Why "potentially"? Either they are cacheable or they aren't. And if they implement this interface I'd say they are. Is that incorrect?

    3. +++ b/core/lib/Drupal/Core/Cache/CacheableInterface.php
      @@ -0,0 +1,44 @@
      +   *   Returns TRUE if the object is cacheable, otherwise FALSE.
      

      "FALSE otherwise" instead "otherwise FALSE".

    Wim Leers’s picture

    Status: Needs work » Needs review
    FileSize
    18.34 KB
    1.77 KB

    #81: those are precisely the details I meant when I said "No matter what one dislikes about it […]" :)

    1. Fixed.
    2. Proof by counter example: most blocks are cacheable, but not all blocks are. Therefor: "potentially cacheable". That's also why there is the isCacheable() method.
    3. Fixed.
    jibran’s picture

    Status: Needs review » Reviewed & tested by the community

    All good now back to RTBC.

    webchick’s picture

    Assigned: Unassigned » catch

    Great work! This seems catch-ish, so assigning to him.

    catch’s picture

    Title: Regression: page cache tags broken, also: some routes incorrectly use _controller -> No page object in template variables -> Fatal in seven_preprocess_page() » Change notice: Regression: page cache tags broken, also: some routes incorrectly use _controller -> No page object in template variables -> Fatal in seven_preprocess_page()
    Priority: Critical » Major
    Status: Reviewed & tested by the community » Active
    Issue tags: +Needs change record

    No complaints. Committed/pushed to 8.x, thanks!

    Could use a change notice for the new interface.

    catch’s picture

    Category: Bug report » Task
    Wim Leers’s picture

    Thanks!

    This has now unblocked two important cache tags issues:

    1. #2179083: Rendered menus (e.g. menu blocks) should set cache tags to inform the page cache
    2. #2183017: Views doesn't bubble up rendered entities' cache tags

    msonnabaum is writing the change notice :)

    znerol’s picture

    Is it appropriate to rediscuss the design of CacheableInterface here, or do we need a follow up for that too?

    Wim Leers’s picture

    #88: let us please do that in a follow-up.

    msonnabaum’s picture

    Here's a change notice for the interface: https://drupal.org/node/2184553

    Never written one before, so I likely did something wrong.

    Wim Leers’s picture

    Title: Change notice: Regression: page cache tags broken, also: some routes incorrectly use _controller -> No page object in template variables -> Fatal in seven_preprocess_page() » Regression: page cache tags broken, also: some routes incorrectly use _controller -> No page object in template variables -> Fatal in seven_preprocess_page()
    Assigned: catch » Unassigned
    Priority: Major » Critical
    Status: Active » Fixed
    Issue tags: -Needs change record

    #90: hehe :) You didn't reference this issue, and you kept it as a draft (which was added somewhere in the last few days), hence it didn't show up at https://drupal.org/list-changes/drupal.
    Then, you should also update the metadata of this issue, like I'm doing here now.

    But those are the easy parts :) Thanks! :)

    Wim Leers’s picture

    Issue tags: -sprint

    Status: Fixed » Closed (fixed)

    Automatically closed - issue fixed for 2 weeks with no activity.