Magento2 – How to Change Response Before Full Page Cache

full-page-cachemagento2

I know my question is strange and I should change the response in the Magento way.

I have an observer subscribed to controller_front_send_response_before event.

events.xml

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
    <event name="controller_front_send_response_before">
        <observer name="roland_controller_front_send_response_before"
                  instance="Roland\HelloWorld\Model\Observers\ResponseBefore"/>
    </event>
</config>

Model/Observers/ResponseBefore.php

<?php
namespace Roland\HelloWorld\Model\Observers;

use \Magento\Framework\Event\ObserverInterface;

class ResponseBefore implements ObserverInterface {

    public function execute(\Magento\Framework\Event\Observer $observer) {

        $this->playWithResponse($observer->getEvent()
                                   ->getData('response'));

    }

    public function playWithResponse($response) {

        $head = '<script>alert("'.time().'");</script>';
        $response->setBody(preg_replace('/<\/head>/', $head . '</head>', $response->getBody(), 1));
    }

}

It works fine when Full page cache not enabled. Every time I refresh the page, I get an alert with the current server time. When Full page cache is enabled, I would like to make my code cached into Full page cache. Currently my observer called on every page load as the Full page cache comes before the controller_front_send_response_before event.

So I checked how's Magento 2 Full page cache works in magento/module-page-cache/Observer/Process/LayoutRenderElement.php

    public function execute(\Magento\Framework\Event\Observer $observer)
{
    $event = $observer->getEvent();
    /** @var \Magento\Framework\View\Layout $layout */
    $layout = $event->getLayout();
    if ($this->isFullPageCacheEnabled() && $layout->isCacheable()) {
        $name = $event->getElementName();
        /** @var \Magento\Framework\View\Element\AbstractBlock $block */
        $block = $layout->getBlock($name);
        $transport = $event->getTransport();
        if ($block instanceof \Magento\Framework\View\Element\AbstractBlock) {
            $blockTtl = $block->getTtl();
            $output = $transport->getData('output');
            if (isset($blockTtl) && $this->isVarnishEnabled()) {
                $output = $this->_wrapEsi($block, $layout);
            } elseif ($block->isScopePrivate()) {
                $output = sprintf(
                    '<!-- BLOCK %1$s -->%2$s<!-- /BLOCK %1$s -->',
                    $block->getNameInLayout(),
                    $output
                );
            }
            $transport->setData('output', $output);
        }
    }
}

I saw that it caches every block, but I haven't got any further.

Here is the part of my $event->getElementName():
mailcheck_init
emailCapture
copyright
before.body.end
page.wrapper
root

I thought if I change the content in the observer before the page cache that might worth, but I have no luck.

if($event->getElementName() == 'root'){
    $transport = $event->getTransport();
    $transport->setData('output', 'test');
}

Do you have any suggestion how should I achieve the desired result?

Best Answer

So, finally I have a real solution. So I was able to track down Magento2 full page cache and turned out that it does the caching as a plugin: Magento\PageCache\Model\App\FrontController\BuiltinPlugin in the aroundDispatch.

$this->kernel->process($result);

Does the work of storing the serialized $response object.

So the solution was to create a plugin for the after event:

Roland/HelloWorld/etc/di.xml

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\Framework\Controller\ResultInterface">
        <plugin name="change-result-before-cache" type="Roland\HelloWorld\Model\Plugins\ChangeResult"/>
    </type>
</config>

Roland/HelloWorld/Model/Plugins/ChangeResult.php

<?php

namespace Roland\HelloWorld\Model\Plugins;

use Magento\Framework\App\Response\Http as ResponseHttp;


class ChangeResult {

    public function afterRenderResult(ResponseHttp $response) {
        $response->setBody(preg_replace('/<\/head>/', '<script>alert("' . time() . '");</script>' . '</head>', $response->getBody(), 1));
    }

}

The result

When page cache enabled, you should always see the same timestamp for every refresh.

Related docs

How Magento2 plugins work - Alan Storm Magento 2 Full page caching - Alan Storm

Related Topic