Use PHP value in multiple blocks

Crell
Member | 9
+
0
-

I have an expensive-to-compute value that I want used across multiple blocks. It's not a string value (that's covered by https://forum.nette.org/…tiple-places), but a PHP object. In particular, it's for pagination, and I want to put prev/next/first/last links both in the body and in the HTML header.

Something along the lines of:

{block links}
    {var $pageNum = $query['page'] ?? 1}
    {var $pageSize = 2}
    {var $pagination = $pageTree->expensive(...)}

    <link rel="first" href="{pageUrl($currentPage)}"/>
    <link rel="last" href="{pageUrl($currentPage, ['page' => $pagination->lastPageNum()])}"/>
    <link n:if="$pageNum < $pagination->lastPageNum()" rel="next" href="{pageUrl($currentPage, ['page' => $pageNum + 1])}"/>
    <link n:if="$pageNum > 1" rel="previous" href="{pageUrl($currentPage, ['page' => $pageNum - 1])}"/>
{/block}

{block content}
    {var $pageNum = $query['page'] ?? 1}
    {var $pagination = $pageTree->expensive(...)}

    { * ... *}

    <div>Showing page {$pagination->pageNum} of {$pagination->pageCount}</div>

    <ul class="paginator" n:foreach="range(1, $pagination->pageCount) as $i">
        <li><a href="?page={$i}">{$i}</a></li>
    </ul>
{/block}

I've not figured out how to avoid the duplicate calls to $pageTree->expensive(), though. Any pointers?

(I'm sure someone is going to say “well pass it in from the PHP code,” but for my purposes that won't work. The $pageTree object is passed in, but I want to be able to call methods on it once and reuse the result.)

Last edited by Crell (2025-02-10 17:28)

mskocik
Member | 74
+
0
-

I needed something similar and I solved it this way: I passed simple stdClass object into template and I set it's prop when needed to be able to read it later (It had some default value set). Because it was parameter of the template it was available globally in whole blocks of the template.

Or you can cache it directly inside $pageTree. Cache result in first run of expensive() and return cached result on subsequent calls. If that's undesired you can provide some wrapper class which will cache result of $pageTree->expensive().

nightfish
Member | 526
+
0
-

@Crell
You will need to somehow cache the return value of $pageTree->expensive(...). If I couldn't cache the value prior to rendering the template, I would go with custom Latte function that does the caching:

$cache = [];

$latte->addFunction('getPagination', function(PageTree $pageTree, int $pageNum, int $pageSize) use (&$cache): Pagination {
    $cacheKey = spl_object_hash($pageTree) . '-' . $pageNum . '-' . $pageSize;
    if (\array_key_exists($cacheKey, $cache) === false) {
        $cache[$cacheKey] = $pageTree->expensive($pageNum, $pageSize);
    }

    return $cache[$cacheKey];
});
{block links}
    {var $pageNum = $query['page'] ?? 1}
    {var $pageSize = 2}
    {var $pagination = getPagination($pageTree, $pageNum, $pageSize)}
    ...
{/block}

{block content}
    {var $pageNum = $query['page'] ?? 1}
    {var $pageSize = 2}
    {var $pagination = getPagination($pageTree, $pageNum, $pageSize)}
    ...
{/block}
Crell
Member | 9
+
0
-

Hm! Just from a usability perspective, I like the idea of wrapping $pageTree into a template function rather than injecting it. That feels cleaner, regardless of the current issue. The $pageTree service can then be injected into the extension rather than the template.

Would it be possible then to internalize the $pageNum handling? I'd love to be able to move that away from the template author, at least in the common case. (The $query is the HTTP query of the request, passed through, mainly for doing things like this.) Something like accessing all values passed to the template from the function rather than just the ones passed. (That may mean I need a tag or something instead, not sure.)

In practice there's almost a dozen parameters to expensive(). A mix of strings, ints, bools, arrays, and DateTimeImmutables. That is definitely memoizable, but could be a bit clunky.

@mskocik's idea of essentially a hidden template-global variable is interesting. I will have to experiment with that. If I can make it “feel” nice to use that may be the solution to the problem at hand, even if I still switch to a function for pageTree.

Like, if I define my own tag, could I do something like this (making up syntax):

{dataset $pagination pageSearch(pageSize: 5, hidden: false, tags: ['a', 'b'])}

{block links}
    <link rel="first" href="{pageUrl($currentPage)}"/>
    <link rel="last" href="{pageUrl($currentPage, ['page' => $pagination->lastPageNum()])}"/>
    <link n:if="$pageNum < $pagination->lastPageNum()" rel="next" href="{pageUrl($currentPage, ['page' => $pagination->nextPage()])}"/>
    <link n:if="$pageNum > 1" rel="previous" href="{pageUrl($currentPage, $pagination->prevPage())}"/>

    {var $pageNum = $query['page'] ?? 1}
    {var $pageSize = 2}
    {var $pagination = getPagination($pageTree, $pageNum, $pageSize)}
    ...
{/block}

{block content}
    {foreach $pagination as $page}
       // ...
    {/foreach}
    ...
{/block}

With the $pagination variable stored inside the extension class somewhere. (I'm sure the above isn't right, but hopefully conveys what I'm thinking of.)