Smarty caching components - php

The Smarty FAQ suggests one way to handle cacheable fragments, but it needs each page controller to do tons of work up-front, instead of encapsulating things properly.
We want to get to a stage where we can do something like:
<div id="header">
{our_categories}
{our_subcategories category=$current_category}
</div>
The output of the our_ prefixed functions should be completely cacheable, only relying on the named parameters (if any.) If we referred to {our_categories} in more than one template, they should all refer to the same cached content.
(it's probably worth mentioning that we tried using {insert name="..."} and coding up our own functions, but the results weren't cacheable, and we ended up hand-cranking the HTML retunred, rather than benefiting from Smarty's template processing.)
Our first crack at this uses a custom function smarty_function_our_categories, but the caching's going horribly wrong. Here's what our function looks like:
function smarty_function_our_categories($params, &$smarty) {
$smarty->caching = 2;
$smarty->cache_lifetime = 3600; # 1 hour
if (!$smarty->is_cached(...)) {
// ... do db access to fetch data for template...
$smarty->assign(....);
}
return $smarty->fetch(...);
}
The problem is: calling $smarty->fetch() within a function confuses smarty, making it lose track of which templates have insert-tags, and which don't. The end result is that Smarty forgets to replace certain markers when serving up cached content (markers it puts there to say: "replace this with the results of some non-caching {insert ...} call.) In our case, this manifests itself with our site showing a couple of md5 checksums and a php-serialized memento where our main menu should be - that's not good.
We assume we've made a mistake in how we're building our components, so the question finally becomes:
How do you safely create a caching component using Smarty to render itself?

You should not change caching parameters from inside Smarty function. Wheither or not the result of the plugin output is cacheable is defined when you register plugin.
http://www.smarty.net/manual/en/caching.cacheable.php
To create uncachable content inside cachable template just use {dynamic} blocks like this:
//Registering dynamic non-caching block with Smarty
$template->register_block('dynamic', 'smarty_block_dynamic', false);
function smarty_block_dynamic($param, $content, &$smarty) {
return $content;
}

Related

Templating with anonymous functions and usage of output buffer in PHP

I figure I'd better ask than make a fool of myself some time when I get my code reviewed.
I am making a website, trying to follow MVC design architecture, although it might not even be MVC but I figured it works nice.
Currently, I am doing something like this;
I have a render() function that takes a string $template and array of arguments,
render($template, $vars=[]){
extract($vars);
ob_start();
include($template.'.php');
ob_flush();
}
and outputs the view. In a controller, I then do this,
Class WhicheverController
public static function defaultAction(){
$article=Article::getOneByID(array('id'=>$_GET['id'], 'user'=>User::currentUser())); //returns database result as object
render('_header', array('title'=>$article->title));
render('header', array('user'=>User::currentUser()));
render('sidebar', array('user'=>User::currentUser()));
$content=Comment::getByArticleID(array('article'=>$article->id)); //returns object
//of root comments, highest level in comment-replies tree
$comments=function() use ($content){
foreach($content as $comment){
$threads[]=function() use ($comment){
render('comment', array('comment'=>$comment));
}
}
render('pagination', array('content'=>$threads)); //pagination paginates based on number of
//items in content, and echoes it or runs it if it is_callable
}
render('article', array('article'=>$article, 'comments'=>$comments));
}
And finally in article.php;
//some html with echoing $article->variablethis or variablethat
<div class="comments">
<?= $comments(); ?>
</div>
Now, the way I figured it out, ob_flush() sends to client prematurely, so I get headers with styles and header and sidebar printed as I am getting the neccessary variables. That is good (if that's how it works).
Then, I want to do the same with the main content - article and comments. First I get the comments and article data, and pass it to article.php. But I have to do some pagination logic first before sending it away, and that would delay rendering of article.php. So I create an anonymous function which does that, and pass that as a variable function $comments(); which does it's logic when it is called.
This is only one example. In one controller I first get top rated articles of today, yesterday and current week, and create anonymous function for each one, before passing it to frontpage.php. Frontpage.php does this, essentialy;
//some html markup
<?= $articles24h(); ?>
//more html
<?= $articles48h(); ?>
//and so on
Now, when I thought of this I thought it was brilliant but as I started to add more variable functions I noticed my code was starting to become longer and I presume anyone other than me illegible.
My question is: is this bad practice; this usage of anonymous functions, variable functions and ob_flush() (more importanntly, does it do what I think it does)?
EDIT: The code itself works. In here I might've made some logical mistakes, but the point is to display the usage of functions.
EDIT 2: Okay, so I realized I can have one anonymous function display $articles24h and $articles48h any paginating content just by passing each as $content, like so function($content) use ($something_else, $but_common){ //yada yada }, which helps a lot, but I still don't know if this is desirable. i.e. there is a better way.
EDIT 3: I created 3 View classes, one View class with static render() method that just takes a template name and array with variables and prints out a template, and two others, PaginationView and PartialView; both have their own render() method and don't extend View class (even thought if I was a better programmer yadda yadda I' do it differently). Pagination paginates partial views that PartialView stores in a class variable and creats when constructed. If I don't need to paginate data, I just call PartialView->render()
Neat (IMO), but it looks kinda ugly in code, like so:
$content=Article::getByAuthorID(array('author'=>$profile->id), array('order'=>$order,'column'=>'time_submitted'));
$user_articles=new PartialView('_article', $content, array('length'=>60));
$user_articles=new PaginationView($user_articles->views, 10, 1);
$user_articles is then sent to
echo View::render('user_page', array('articles'=>$user_articles, 'profile'=>$profile, 'comments'=>$user_comments));
As you can see, I also do this for comments so the code gets lengthy. I don't want to change anything it I am not breaking anything, although I feel like this is a violation of DRY in some way (?)
Is there a better way to do this, without sacrificing clarity but making it more compact?

create user define tag in smarty and how to get the content within it

in smarty Smarty_Compiler.class.php performed some operation between two tags like {if}{/if}
if i want to get text within the new tag then how to proceedi tried inside
function _compile_tag($template_tag)
{
....
switch ($tag_command) {
-----
case 'newtag':
break;
case '/newtag':
break;
}
How can i get the content of tpl within the new tag
You should create a Smarty plugin. You can read documentations here (about extending Smarty) and here (more specific, about create block functions plugins).
Basically, you have to create your smarty_make_pdf() PHP function (see parameters in the second link I gave you), place it in a file called block.make_pdf.php (see here) and tell Smarty to search for plugins in the folder you created that file using $smarty->addPluginsDir() (see here).
PS: I'm supposing you are using Smarty 3.
You really shouldn't be editing the core Smarty code to achieve this.
Look into registerPlugin() if you're using Smarty 3 (or register_block() if you're on Smarty 2).
These methods will allow you to create your own Smarty tags and write handler functions that implement them.

Design Pattern with Javascript and PHP

i'm creating a website in PHP, that has Javascript elements as well. This one, will be having what is like a plugin system where a plugin can be dynamically added to the website.
I've created the plugin system, but i don't like certain elements of the design. In particular, i'm using an MVC pattern, but there is a problem with javascript abstraction.
I have a certain file that loads all the plugins. Then, there is a javascript file that dynamically adds boxes to a window, depending on the selection that was made, for what plugin should be used.
It goes like this in a js file :
if (SelValue == 'image_list')
image_list(form_name, new_div, parent_div);
if (SelValue == 'multiple_columns')
multiple_columns(form_name, new_div, parent_div);
Then, right below, follows the declaration of imagelist() and so on. Of course, this is pretty cumbersome and certainly does not look like a good practice. I would like to have all of these abstracted and isolated, so that a plugin is just a simple step to add to my code, if possible.
Do you know of any design pattern or practice that could fit this scenario ?
You could create a Object holding all functions like image_list and multiple_columns. Using those would look like:
plugins[SelValue](form_name, new_div, parent_div);
Adding a new function would look like this:
plugins.image_list = function (form_name, new_div, parent_div) {
/* … */
}
This definition could go in a different file. Is that what you meant?
Edit: Pretty single-file version:
plugins = {
image_list: function (form_name, new_div, parent_div) {
/* … */
},
multiple_columns: function (form_name, new_div, parent_div) {
/* … */
},
};
plugins[SelValue](form_name, new_div, parent_div);
If you're not going to have more than 2 IF statements, that would be sufficient. However, if you're going to be extending it (or could foresee it expanding), then I would suggest using a Factory/Command design pattern. Implementation details would include a table or a dictionary to replace the multiple IF-statements that you would have.
The module pattern would probably fit your needs very well. This pattern basically would treat each "plugin" as a module, which itself should be fully functional and independent. Then you have a module loader/controller, which is responsible for loading the correct modules, and enabling modules to communicate with each other.
http://www.adequatelygood.com/2010/3/JavaScript-Module-Pattern-In-Depth
http://www.yuiblog.com/blog/2007/06/12/module-pattern/

Ignore a Smarty Templates Fetch() for one page

I've just been passed an application to work on written in smarty templates so I'm unfamiliar with how the whole thing works.
So my problem is smarty is fetching a template from a file at application level so it affects every page on the site. I need a way of telling a single template to ignore the application level fetch.
So at application level it is echo $smarty->fetch('layout/main.html.tpl'); I just want to ignore that on one template. Can anyone help?
You'd want to add some logic to whatever point in the application is fetching that template. The issue isn't smarty, it's the application. A smarty template has no way of interfering with the php that renders it, nor can it introduce logic within the php script.
You would call $smarty->fetch() from a script, not a template. You can use logic to choose a different template name and fetch whatever template is appropriate, so a single script can easily call any of your templates.
For instance...
$template = 'error.tpl';
if($conditions =='right')
{
$template = 'normal.tpl';
}
echo $smarty->fetch("layout/$template");
Also, note that you can use the display() method rather than echo with fetch():
$smarty->display("layout/$template");
This way you're not storing the template into a variable you're just going to output.
If it's a simple case where one script calls template "A" and another calls template "B"...
//call in template A
$smarty->display("layout/templateA.tpl");
//call in template B
$smarty->display("layout/templateB.tpl");
No need for extra logic in that case.

Templates in PHP, and the best way to notify the application that one exists?

I'm using CodeIgniter, and will likely use their template library as I want to keep things extremely simple to use. The content for the template variables will come from the database, but I want the business admins to know what content areas are available. Basically the names of the parameters when they choose a specific template. For instance, Joomla uses an extra XML file that defines each area, whereas Wordpress uses comments within a page template to inform the system that the PHP file is a template. I like the Joomla approach because you don't have to parse the PHP file to find the areas, but I like the Wordpress approach because you don't have an extra XML file associated with every template. Are there other approaches that I'm missing?
I think the nicest way would be to add a small hack to the template parser class. The code looks quite readable and clean in system/libraries/Parser.php. You could insert a hook in that class that can be used to keep track of the variables. I don't know, if it works, but here's a snippet:
class CI_Parser {
var $varCallback;
function setVarCallback($callbackFunction) {
$this->varCallback = $callbackFunction;
}
...
function _parse_single(...) {
$callback = $this->varCallback;
$callback($key);
}
...
//Somewhere in your code
function storeVarName($variableName) {
// Persist the variable name wherever you want here
}
$this->parser->setVarCallback('storeVarName');
You could do this directly in the controller:
// in the controller
print_r($data);
$this->load->view("main", $data);
Or a little more rudimentary, but you could pass to the template a PHP array of variables (or an object):
// in the controller
$data = array();
$data["namespace"] = array(
"title" => "My website",
"posts" => array("hi", "something else")
);
$this->load->view("main", $data);
And then in the view, have a flag to print_r the namespace to show all the vars available, so that business admins know exactly what to use.
// in the view
if(isset($namespace["showAllVars"])) print_r($namespace);
One option would be to call token_get_all on the PHP file (only when your business admins are loading it up), and parse the output of that.
The best approach, in my opinion, is to keep the variable definitions in another place (such as a database table, or a separate file). This will help with testing (i.e., a programmer can't just remove a tag and it's gone) and making sure things are still working as you move on with the application development in time.
Another advantage is that your application logic will be independent from the templating engine.
On a side note, if you expect a lot of traffic, you may consider using smarty instead. We have done extensive testing with most of the templating engines around and smarty is the fastest.

Categories