Smarty Forum Index Smarty
The discussions here are for Smarty, a template engine for the PHP programming language.

is_cached vs include with cache_id

 
Post new topic   Reply to topic    Smarty Forum Index -> Bugs
View previous topic :: View next topic  
Author Message
mimos
Smarty Rookie


Joined: 15 Oct 2016
Posts: 5

PostPosted: Sat Oct 15, 2016 1:37 pm    Post subject: is_cached vs include with cache_id Reply with quote

Hi,

I am using smarty 3.1.30 on php 7.0.9 and want to use includes with cache_ids. Because I didnt't completely understand smarty's code and was unsure about corner cases, I wrote some test cases to find out what was happening exactly. There I found what I would consider a bug:

I modified the demo files so that
- index.php assings one more value: "headercacheid" = "myheadercacheid"
- index.php only assigns cacheable values
- index.php only assigns values if isCached returns false, otherwise tries to render from cache
- index.tpl includes header.tpl with the cache_id provided in "headercacheid"

If I now load the page two times in a short order the second load leads to an error message. I think this is caused by the cached index.tpl still trying to load the value of headercacheid, which is not provided to index.tpl because it is loaded from cache. But because of isCached being true and headercacheid being cacheable I think it can be expected that its value is cached.

Now some suggestions for improvements, hoping that they wouldn't break anything.

A basic fix
- cache the cache_id in the cached index.tpl

A 'better' fix (uses more disk space but reduces number of file accesses):
- cache header.tpl in its own file (like before, so it can still be used if other templates include the same cached version of the header) but in addition also cache it's contents in index.tpl. This could propably also be done if no cache_id is specified, but I quess that could break some code.

I also noticed that the value of headercacheid is read indirectly by using ob_start(), echo and ob_get_clean() which could maybe be made faster by directly reading the value.


I hope it is ok that I posted here because the announcement message http://www.smarty.net/forums/viewtopic.php?t=400 sais otherwise but the provided link to google code seems outdated.

Appendix: error message and modified demo files:

error message (some newlines added):
Code:

Fatal error: Uncaught --> Smarty: unable to write file
F:\PortableApps\xampp_7.0.9\htdocs\smarty-3.1.30\demo\cache\_br_b_
Notice_b_Undefined_index_headercachid_
in_b_F_PortableApps_xampp_7_0_9_htdocs_smarty_3_1_30_demo_cache_83c01f0f0900091d3bc9973ca548fc312d54885f_index_tpl_php_b_
on_line_b_31_b_br_br_b_
Notice_b_Trying_to_get_property_of_non_object_
in_b_F_PortableApps_xampp_7_0_9_htdocs_smarty_3_1_30_demo_cache_83c01f0f0900091d3bc9973ca548fc312d54885f_index_tpl_php_b_
on_line_b_31_b_br_^29921fe56b3a070b84145412f4fc2cce179fb498.header.tpl.php <-- thrown in F:\PortableApps\xampp_7.0.9\htdocs\smarty-3.1.30\libs\sysplugins\smarty_internal_runtime_writefile.php on line 80


index.php
Code:

<?php
/**
 * Example Application
 *
 * @package Example-application
 */

require '../libs/Smarty.class.php';

$smarty = new Smarty;

//$smarty->force_compile = true;
$smarty->debugging = true;
$smarty->caching = true;
$smarty->cache_lifetime = 120;

if(!$smarty->isCached('index.tpl'))
{
   $smarty->assign("headercachid", "myheadercacheid");
   $smarty->assign("Name", "Fred Irving Johnathan Bradley Peppergill");
   $smarty->assign("FirstName", array("John", "Mary", "James", "Henry"));
   $smarty->assign("LastName", array("Doe", "Smith", "Johnson", "Case"));
   $smarty->assign("Class", array(array("A", "B", "C", "D"), array("E", "F", "G", "H"), array("I", "J", "K", "L"),
                   array("M", "N", "O", "P")));

   $smarty->assign("contacts", array(array("phone" => "1", "fax" => "2", "cell" => "3"),
                 array("phone" => "555-4444", "fax" => "555-3333", "cell" => "760-1234")));

   $smarty->assign("option_values", array("NY", "NE", "KS", "IA", "OK", "TX"));
   $smarty->assign("option_output", array("New York", "Nebraska", "Kansas", "Iowa", "Oklahoma", "Texas"));
   $smarty->assign("option_selected", "NE");
}


$smarty->display('index.tpl');


index.tpl
Code:

{config_load file="test.conf" section="setup"}
{include file="header.tpl" title=foo cache_id={$headercachid}}

<PRE>

{* bold and title are read from the config file *}
    {if #bold#}<b>{/if}
        {* capitalize the first letters of each word of the title *}
        Title: {#title#|capitalize}
        {if #bold#}</b>{/if}

    The current date and time is {$smarty.now|date_format:"%Y-%m-%d %H:%M:%S"}

    The value of global assigned variable $SCRIPT_NAME is {$SCRIPT_NAME}

    Example of accessing server environment variable SERVER_NAME: {$smarty.server.SERVER_NAME}

    The value of {ldelim}$Name{rdelim} is <b>{$Name}</b>

variable modifier example of {ldelim}$Name|upper{rdelim}

<b>{$Name|upper}</b>


An example of a section loop:

    {section name=outer
    loop=$FirstName}
        {if $smarty.section.outer.index is odd by 2}
            {$smarty.section.outer.rownum} . {$FirstName[outer]} {$LastName[outer]}
        {else}
            {$smarty.section.outer.rownum} * {$FirstName[outer]} {$LastName[outer]}
        {/if}
        {sectionelse}
        none
    {/section}

    An example of section looped key values:

    {section name=sec1 loop=$contacts}
        phone: {$contacts[sec1].phone}
        <br>

            fax: {$contacts[sec1].fax}
        <br>

            cell: {$contacts[sec1].cell}
        <br>
    {/section}
    <p>

        testing strip tags
        {strip}
<table border=0>
    <tr>
        <td>
            <A HREF="{$SCRIPT_NAME}">
                <font color="red">This is a test </font>
            </A>
        </td>
    </tr>
</table>
    {/strip}

</PRE>

This is an example of the html_select_date function:

<form>
    {html_select_date start_year=1998 end_year=2010}
</form>

This is an example of the html_select_time function:

<form>
    {html_select_time use_24_hours=false}
</form>

This is an example of the html_options function:

<form>
    <select name=states>
        {html_options values=$option_values selected=$option_selected output=$option_output}
    </select>
</form>

{include file="footer.tpl"}
[/code]
Back to top
View user's profile Send private message
AnrDaemon
Administrator


Joined: 03 Dec 2012
Posts: 1268

PostPosted: Sun Oct 16, 2016 11:42 am    Post subject: Reply with quote

The way you ask your question makes it clear you do not understand the behavior of Smarty cache.
And did not read the documentation.
It clearly states you MUST assign all nocache'd variables needed for page render, even if template itself is cached.
There's no "$cache_id vs. Smarty::isCached()", They both serve different purposes and work in unison.
If you hold on for a day, I'll provide some examples to make it more clear.
In the meantime, a note: assigning variables in the template often indicates poor code on your application's side. You can do it, if there's no better choice. But many times there IS better choice.
F.e. in this sample
Code:
{nocache}{$id=3}<p id="header_basket" class="main_menu{if $id == $pageId} main_menu_sel{/if}" title="У вас {if $basketSize == 0}нет{else}{$basketSize}{/if} товар(-ов) для заказа." data-size="{$basketSize}">{*
  FIXME HAX!! Should make a cleaner detection, not a hardcoded id.
*}<span id="header_basket_flash">{if $basketSize == 0 or $id == $pageId}Ваш заказ{else}<a href="/?page={$id}">Ваш заказ</a>{/if}</span></p>{/nocache}

i've used assignment, because at the moment there's no cleaner detection of website modules, and I did need a special behavior for this menu entry. But in the near future, when a new router is ready, I plan to be more explicit in my code BEFORE calling the template. So that I don't need crude hardcodes in the page rendering.


Last edited by AnrDaemon on Mon Oct 17, 2016 3:02 pm; edited 1 time in total
Back to top
View user's profile Send private message
AnrDaemon
Administrator


Joined: 03 Dec 2012
Posts: 1268

PostPosted: Sun Oct 16, 2016 11:46 am    Post subject: Reply with quote

Another possible solution is {include inline}, but only if that suits your needs. F.e. I'm using such includes for templated image snippets. They also rendered using Smarty, but when page loads, it won't sift through scattered cache files for easily a hundred of image links, but serve one big already rendered page with them embedded.
Back to top
View user's profile Send private message
mimos
Smarty Rookie


Joined: 15 Oct 2016
Posts: 5

PostPosted: Sun Oct 16, 2016 1:38 pm    Post subject: Reply with quote

Hi, thanks for your reply.

I am aware that noncahed variables need to be assigned for each render. I did not expect, that the variable I use as a cache_id is not cached, even if I set nocache to false in the assignment, though. I didn't set nocache anywhere explicitly so I assumed everything should be in the cache.
And, as expected, there is no access to tpl_vars in the whole cache-file for index.tpl - except for the cacheid.

Are you talking about assignments to "php variables" in templates? If I understand correctly I didn't use any. Most of the code is copied demo code and if I haven't overlooked anything it also doesn't assign "php variables" anywhere. Or is the cache_id in the include-tag treated as a "php variable" which would explain my problems.

Thanks for the hint about {include ... inline} I overlooked that before, I'll look into it.
Back to top
View user's profile Send private message
mimos
Smarty Rookie


Joined: 15 Oct 2016
Posts: 5

PostPosted: Mon Oct 17, 2016 1:28 am    Post subject: Reply with quote

Well... I discovered at least one flaw with my code: too many (nested) curly braces. Maybe this looked to you like "assigning variables in the template"?. Nontheless the basic issue still stays the same.

So here a new version of the two files from the demo templates that come with smarty that I modified. For better readability I also removed all the lines that are not relevant for this issue.

index.php
Code:
<?php
/**
 * Example Application
 *
 * @package Example-application
 */

require '../libs/Smarty.class.php';

$smarty = new Smarty;

//$smarty->force_compile = true;
$smarty->debugging = true;
$smarty->caching = true;
$smarty->cache_lifetime = 120;

if(!$smarty->isCached('index.tpl'))
{
   $smarty->assign("headercacheid", "myheadercacheid", false);
   $smarty->assign("Name", "Fred Irving Johnathan Bradley Peppergill");
}


$smarty->display('index.tpl');



index.tpl
Code:
{config_load file="test.conf" section="setup"}
{include file="header.tpl" title=foo cache_id=$headercacheid}

   Hello World!

{include file="footer.tpl"}



Maybe the cachefile which was generated helps to clarify:


index.tpl cache which was generated (only the interesting lines)
Code:
...
<?php $_smarty_tpl->_subTemplateRender("file:header.tpl", $_smarty_tpl->tpl_vars['headercacheid']->value, $_smarty_tpl->compile_id, 1, $_smarty_tpl->cache_lifetime, array('title'=>'foo'), 0, false);
?>
...


what I expected
Code:
...
<?php $_smarty_tpl->_subTemplateRender("file:header.tpl", "myheadercacheid", $_smarty_tpl->compile_id, 1, $_smarty_tpl->cache_lifetime, array('title'=>'foo'), 0, false);
?>
...
Back to top
View user's profile Send private message
AnrDaemon
Administrator


Joined: 03 Dec 2012
Posts: 1268

PostPosted: Mon Oct 17, 2016 4:24 pm    Post subject: Reply with quote

Got it now.
It is very likely that cache_id value for included templates is never cached, and I can see, why is that.
If for your code it is an acceptable approach, I suggest include inline.
Othervise, move assignment of child cache_id out of if(isCached()) ... block.
Back to top
View user's profile Send private message
mimos
Smarty Rookie


Joined: 15 Oct 2016
Posts: 5

PostPosted: Tue Oct 18, 2016 12:00 am    Post subject: Reply with quote

Thanks for your suggestions.

I'd find it more logical, if that cache_id was cached by default and only not cached if it is in a {nocache} block or the variable is nonchacheable by itself. But I can understand that other people may think otherwise. And a change would propably break some existing code...

Using inline would be acceptable. If it would still generate the seperate cache file in addition to inlining (for being used by other includes with the same cache id) it would even be exactly what I want, but sadly inline doesn't seem to have any effect. Isn't it the only case where it could have any effect? In all other cases I can think of includes are inlined by default. Or maybe it is only useful for includes with different cache lifetimes?

So there seem to be two options left which both cause unnecessary database reads:
- Caching the whole file without caching the included templates with their own cache_id. That way initial rendering of the page requires a lot of database reads but for subsequent ones I wont't need any.
- Providing the cache_ids every time which might reduce the database reads for initial rendering but sadly still requires reads for all subsequent render operations.

So I hope that at least one of these two things is considered a bug:
- the cache_id in includes not being cached - which in my eyes reduces it's usefulness a lot
- inline not working for includes with a cache_id

So is there anything I can do about it? Can I file a bug-report somewhere hoping that one of these things is actually considered a bug? Or is this forum already the right place now google code is inactive?

In any case I'd still like to see this behavior of the cache_id and inline to be documented so noone gets confused by it any longer. Or at least I can move on Smile
Back to top
View user's profile Send private message
AnrDaemon
Administrator


Joined: 03 Dec 2012
Posts: 1268

PostPosted: Tue Oct 18, 2016 5:02 am    Post subject: Reply with quote

"include inline" will generate a separate compiled template, per compile_id.
But it will not cache the result separately, hence "inline".
Back to top
View user's profile Send private message
U.Tews
Administrator


Joined: 22 Nov 2006
Posts: 5063
Location: Hamburg / Germany

PostPosted: Sun Oct 23, 2016 9:22 pm    Post subject: Reply with quote

If you use
Code:
{include 'foo.tpl' cache_id=$bar}

With an individual cache_id it will create an cached file which is independent from the calling template.
Smarty does generate code in nocache mode to call 'foo.tpl'.
I agree it's a short coming that the compiler was not detecting that variable 'headercacheid' was not assigned as a no cache variable.
The simplest solution is to move the assignment outside the isCached condition

Code:
<?php
/**
 * Example Application
 *
 * @package Example-application
 */

require '../libs/Smarty.class.php';

$smarty = new Smarty;

//$smarty->force_compile = true;
$smarty->debugging = true;
$smarty->caching = true;
$smarty->cache_lifetime = 120;
 
$smarty->assign("headercacheid", "myheadercacheid", false);
if(!$smarty->isCached('index.tpl'))
{
   $smarty->assign("Name", "Fred Irving Johnathan Bradley Peppergill");
}
$smarty->display('index.tpl');

as "headercacheid" is not assigned as nocache variable does not change any compiled template code, but it's defined when
Code:

<?php $_smarty_tpl->_subTemplateRender("file:header.tpl", $_smarty_tpl->tpl_vars['headercacheid']->value, $_smarty_tpl->compile_id, 1, $_smarty_tpl->cache_lifetime, array('title'=>'foo'), 0, false);
?>



other option is since version 3.1.30 the {make_nocache} tag:

index.tpl
Code:

{config_load file="test.conf" section="setup"}
{make_nocache $headercacheid}
{include file="header.tpl" title=foo cache_id=$headercacheid}

   Hello World!

{include file="footer.tpl"}
}

{make_nocache} does make sure that a variable which might not have been assigned as nocache is seen in the cache file with it's value when rendering the compiled template.

The inline option like {include file="header.tpl" inline} will merge the compiled code of the sub template into the main template compiled file.
So on calls of sub templates no additional reads of compiled files are required when rendering compiled template.
This has no effect for sub templates which are creating their own cache files. These template are treated as new main template which does have also its own compiled template file.

Anyway the inline option is related to your problem.
Back to top
View user's profile Send private message
mimos
Smarty Rookie


Joined: 15 Oct 2016
Posts: 5

PostPosted: Mon Oct 24, 2016 3:30 pm    Post subject: Reply with quote

Aah, thanks, this helped a lot: {make_nocache $headercacheid} achieves what I want Smile

Now I understand what include does, too. Previously I looked for changes in the cached files and obviously found none because it works for compiled files. But why doesn't it include the compiled code for subtemplates with cache_ids? Maybe it would require bigger changes to the codebase... maybe something for the future.



Propably something similar to include but just for the cache in case cache_ids are used would be useful for the same reasons it is useful for compiled files (less file access operations):

Suppose I have a template with a cache_id with multiple subtemplates also having (different) cache_ids being cached using make_nocache. Now I can display the template using nothing but the template's cache_id. But still multiple files are loaded. So as an optimization the subtemplates could still be cached in seperate files (for being used by other templates) but at the same time the caches could be copyed into the template's cache for reducing the number of file read operations to one. This would propably require a new include option and maybe would allow getting rid of the make_nocache for my application.


Well, this might be more appropriate in the Feature Requests-subforum
Back to top
View user's profile Send private message
AnrDaemon
Administrator


Joined: 03 Dec 2012
Posts: 1268

PostPosted: Mon Oct 24, 2016 6:16 pm    Post subject: Reply with quote

Even as you write it, it sounds overly complicated.
I dare not try to code it. Currently, Smarty covers 99% of my needs. And i didn't even use half of its features.
Back to top
View user's profile Send private message
U.Tews
Administrator


Joined: 22 Nov 2006
Posts: 5063
Location: Hamburg / Germany

PostPosted: Tue Oct 25, 2016 1:53 am    Post subject: Reply with quote

There is another important thing I did not realize yesterday in all consequence. There are several reasons why separately cached sub templates make sense. Most often is that some content has different life time.

Lets assume a theoretical case. main.tpl has a life time of 1000, sub1.tpl has a life time of 200 and sub2.tpl has a life time of 700 and a cache_id. So when main.tpl is called any of the 3 cached files may have been expired or not. So the script has to assign all required variables which can be sort of complex when some variables are needed in more then one sub template.

main.tpl

Code:

...
$smarty->caching = true;
$smarty->cache_lifetime = 1000;
// assign all nocache vars here
$smarty->assign('sub2_cacheid',$cacheidFoo);

$smarty->cache_lifetime = 1000;
$mainIsCached = Smarty->isCached('main.tpl');
$smarty->cache_lifetime = 200;
$sub1IsCached = Smarty->isCached('sub1.tpl');
$smarty->cache_lifetime = 700;
$sub2isCached = Smarty->isCached('sub2.tpl',$cacheidFoo);



if (!$mainIsCached || !$sub1IsCached) {
// assign variables need in main and sub1 here
}
if (!$mainIsCached) {
// assign variables need only in main here
}
if (!$sub2IsCached) {
// assign variables need only in sub2 here
}
if (!$sub2IsCached || !$sub1IsCached ) {
// assign variables need  in sub2 and sub1 here
}

Smarty->display('main.tpl');


The above combinations of the if conditions is just an example.

So also you will require both isCached calls isCached('index.tpl') and isCached('header.tpl', "myheadercacheid").
While experimenting with only one cache_id and using same life time for both templates it's likely that both templates expire at the same time, but this will not be the case in real life.



mimos
Quote:
Suppose I have a template with a cache_id with multiple subtemplates also having (different) cache_ids being cached using make_nocache. Now I can display the template using nothing but the template's cache_id. But still multiple files are loaded. So as an optimization the subtemplates could still be cached in seperate files (for being used by other templates) but at the same time the caches could be copyed into the template's cache for reducing the number of file read operations to one. This would propably require a new include option and maybe would allow getting rid of the make_nocache for my application.

The idea of merging several cached file into one does not make sense because each template does expire at a random time. And each time any has expired you have to merge all cache files together. Also if the sub templates could have cache_id's the cache_id of the main template must be a combination of all. Because of the number of cache_id combinations you will see an explosion of the number of cache files.
Also if one sub template does expire all cached templates using that sub template need to be newly merge and written to disk.
At the end this will have less performance.
Back to top
View user's profile Send private message
Display posts from previous:   
Post new topic   Reply to topic    Smarty Forum Index -> Bugs All times are GMT
Page 1 of 1

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum


Powered by phpBB © 2001, 2005 phpBB Group
Protected by Anti-Spam ACP