Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds a no_cache tag to support dynamic content within cached pages #5575

Closed
wants to merge 5 commits into from
Closed

Adds a no_cache tag to support dynamic content within cached pages #5575

wants to merge 5 commits into from

Conversation

JohnathonKoster
Copy link
Contributor

This feature was inspired by work started here: #4893

This PR introduces a no_cache tag that interacts closely with the existing half-cache static caching strategy. This tag can be used like so:

Template contents that should be cached.
{{ now format="Y-m-d H:i:s" }}


Wrap all template parts that should remain dynamic inside a `no_cache` tag pair:

{{ no_cache }}

{{ now format="Y-m-d H:i:s" }}
This section will be re-evaluated, while the rest remains cached.

{{ /no_cache }}

The following considerations were made with this implementation:

  • Implement a way to replace content such as the CSRF token (the implementation was modeled after https://github.com/spatie/laravel-responsecache)
  • Nested no_cache tags (such as those within included partials) should "just work" without completely breaking things. This is achieved by disabling the tag for all nested no_cache tag pairs, and re-enabling it again to support multiple dynamic regions outside the tag pair
  • Cache tags within no_cache tag pairs should remain unaffected
  • Follow the same query parameter and HTTP method rules as the existing half-cache measure
  • Multiple parts of the template should be able to use no_cache tag pairs

Because this does not leverage anything specifically provided by the Runtime parser, this implementation will work on both the Regex and Runtime Antlers parsers.

Implementation Details

This implementation works by introducing a cache layer on top of the existing half-cache. If a no_cache tag is encountered while producing the response, the no cache manager will handle the response and subsequent requests instead of the existing cache middleware logic.

The no cache maintains a few items for each URL that is cached:

  1. A modified template that will be used on subsequent requests. This is an Antlers template file.
  2. A no cache "manifest" that remembers what the dynamic parts of the content are. This is a simple JSON file.
  3. A context file that remembers what the context/state was for each dynamic region extracted from the original response. All rehydrated context variables are merged in with a fresh copy of the Cascade data (for things such as now, csrf token, etc.). This is a serialized file containing an array with the state of each dynamic section present in the manifest file. Each stored context is diff'd against the original context to reduce the final file size (this missing information is added back later on subsequent requests).

The modified template will have the original no_cache tag pair removed, and will be replaced with a tag similar to the following:

{{ no_cache:evaluate region="e0f09803f757eabe8ff05cd1828a08b66a69ed63" }}

The e0f09803f757eabe8ff05cd1828a08b66a69ed63 in the example above is the dynamic region's identifier, and will also be present within the manifest (to retrieve the original Antlers), and within the rehydrated context (to restore the original data state).

The evaluate method will locate the dynamic content from the restored manifest file, and load in the contextual data that was available at the time the response was originally generated. This allows state data to be referenced within the extracted dynamic regions, without having to reload everything from tag calls/etc. (if the no_cache tag pair is inside another tag pair).

State data is serialized, and all Closure instances are removed before saving.

As an example, the following usage of no_cache would remember the results of the collection tag call, without calling the collection tag again on the next request:

{{ collection:articles }}

<p>This part of the tag output is cached.</p>

{{ no_cache }}
    <p>This part is not, and you will have access to the original tag results.</p>
    {{ now format="Y-m-d H:i:s" }}

    {{ title }}

{{ /no_cache }}

{{ /collection:articles  }}

The modified template that is stored within the cache may look something like this (assuming five entries were returned originally). The extracted dynamic regions are inserted back into the static output (as no_cache:evaluate calls):



<p>This part of the tag output is cached.</p>

{{ no_cache:evaluate region="24548e7529fd947a19dc0faa5218afd62c772bf1" }}



<p>This part of the tag output is cached.</p>

{{ no_cache:evaluate region="365489f9b40d444a17ee18469a9bb8630683fdfb" }}



<p>This part of the tag output is cached.</p>

{{ no_cache:evaluate region="705b86f66f833cbe7be910187b614c6d162ba339" }}



<p>This part of the tag output is cached.</p>

{{ no_cache:evaluate region="d585da04ef27c091ea038a1110a5be6de5eb630b" }}



<p>This part of the tag output is cached.</p>

{{ no_cache:evaluate region="034d29a94490b5dc95d31cfab84a7805288ea28d" }}

In contrast, if we had supplied the following template instead:

<p>Content to be cached: before.</p>

{{ no_cache }}

{{ collection:articles }}

    {{ now format="Y-m-d H:i:s" }}

    {{ title }}

{{ /collection:articles  }}
{{ /no_cache }}

<p>Content to be cached: after.</p>

The collection tag itself will become part of the dynamic output, and the cached template file would instead look similar to the following:

<p>Content to be cached: before.</p>

{{ no_cache:evaluate region="f3fe9614b4b7e5951c7dab811a1d1f9be6f8204f" }}

<p>Content to be cached: after.</p>

Cache Invalidation

The no_cache cache can be completely cleared by invoking the php artisan cache:clear Artisan command. Additionally, the existing invalidator will call the no cache's invalidateUrl method.

@jasonvarga
Copy link
Member

Closing in favor of #6231

@jasonvarga jasonvarga closed this Jun 20, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants