Invalidate Volt template cache when updating parent layout.

I'm wanting to invalidate/clear the cache of all child templates (or completely recompile everything) when a change occurs on the parent layout template. I am aware of the Volt setting "compileAlways" => true but I'm looking for a solution that can be used in a production environment and isn't susceptible to race conditions. I'll provide some example templates below and attempt to clearly explain the problem at hand.

{# main.volt #}
<!DOCTYPE html>
<html>
    <head>
        <title>Title</title>
    </head>

    <body>
        {% block content %}{% endblock %}
    </body>
</html>
{# layout.volt #}
{% extends "main.volt" %}

{% block content %}

    <h1>Table of contents</h1>

{% endblock %}
{# child.volt #}
{% extends "main.volt" %}

{% block content %}

    <h1>Table of contents</h1>

{% endblock %}
/view/layout/main.volt
/view/layout/layout.volt
/view/project1/child.volt
/view/project2/child.volt
/view/project3/child.volt
/view/project4/child.volt
/view/project5/child.volt
...

Given the templates above, lets assume we have hundreds of child templates that extend layout.volt and all templates have been compiled and cached. The problem is that updating the layout.volt or base.volt templates will have no effect on the hundreds of child templates extending it. I've been racking my brain trying to come up with a solution, it's difficult because as far as I can tell, during compilation & caching no information relating the child template and base template (or vise versa) is ever used in Phalcon.

Lets say the /view/project1/child.volt template is requested and not cached, during the compilation process at some point the relation between child.volt, layout.volt & base.volt must be known when processing the extends statement? If it's possible to gather gather that information, then storing it in cache, filenames etc could help determine whether the cache is invalid.

/cache/template.relations.php

return [
    '/view/layout/main.volt' => [
        'children' => [
            '/view/layout/layout.volt',
        ]
    ],
    '/view/layout/layout.volt' => [
        'parent' => '/view/layout/main.volt',
        'children' => [
            '/view/project1/child.volt',
            '/view/project2/child.volt',
            '/view/project3/child.volt',
            '/view/project4/child.volt',
            '/view/project5/child.volt',
        ]
    ],
    '/view/project1/child.volt' => [
        'parent' => '/view/layout/layout.volt',
    ],
    '/view/project2/child.volt' => [
        'parent' => '/view/layout/layout.volt',
    ],
    '/view/project3/child.volt' => [
        'parent' => '/view/layout/layout.volt',
    ],
    '/view/project4/child.volt' => [
        'parent' => '/view/layout/layout.volt',
    ],
    '/view/project5/child.volt' => [
        'parent' => '/view/layout/layout.volt',
    ],
];


2.7k

I have the same question. In my case, I used a database table to index the templates and can search the index for parents. Another messy approach could be a naming convention withnames reflecting the parent template.

I looked at recording the templates used when processing templates. What happens when a template is included conditionally but not included when you record the processing?

I might just add a database table to indicate "related" templates. If a template contains "extends", add the related template. How many ways are there to include another template?

Looking through View and Volt, there are some options for adding extra functions and callbacks. I did not find one that would work per template. You might have to extend Volt with a new class and add the new class as the Volt service.



2.7k

volt->render() has the template path as the first parameter. If you extended Volt with your own render(), you could record the template path. I presume the first item recorded would be the parent then you would see the children.

edited Jun '17

I believe you have to dig a little deeper than the engine class, it's actually the compiler that needs extending/re-factoring. It doesn't matter whether you're compiling a string or a file, each corresponding method will delegate to _compileSource().

Just after the compilation phase there's a check to see whether the template is extending another, this is determined by the boolean property this->_extended which happens to be set when creating a _statementList(). The value this->_extended is only set to true if encountering the statement type extends.

It's actually at this particular stage in the code, the parent/child relationship tree would need to be built and stored for later use, given that this is where the extending parent file path is known, see the finalPath variable. I'm still unsure how this is information can be passed back and utilised in the compiledPath closure, or whether Volt could potentially invalidate the template cache itself.