How to make a php template engine? - php

I need to make a small and simple php template engine I searched a lot and many of them were too complex to understand and I don't want to use smarty and other similar engines, I have got some idea from Stack Overflow like this:
$template = file_get_contents('file.html');
$array = array('var1' => 'value',
'txt' => 'text');
foreach($array as $key => $value)
{
$template = str_replace('{'.$key.'}', $value, $template);
}
echo $template;
Now instead of echo the template I just want to add include "file.html" and it will display the file with correct variable values and I want to put the engine in a separate place and just include it in the template what I want to use it declare the array and at the end include the html file like phpbb. Sorry I am asking to much but can anyone just explain the basic concept behind this?
EDIT: Well let me be frank i am making a forum script and i have got tons of ideas for it but i want make its template system like phpbb so i need a separate template engine custom one if you can help then please you are invited to work with me. sorry for the ad.. :p

file.html:
<html>
<body>
<h3>Hi there, <?php echo $name ?></h3>
</body>
</html>
file.php:
<?php
$name = "Keshav";
include('file.html');
?>
Doesn't get simpler than this. Yes, it uses global variables, but if simple is the name of the game, this is it. Simply visit 'http://example.com/file.php' and off you go.
Now, if you want the user to see 'file.html' in the browser's address bar, you'd have to configure your webserver to treat .html files as PHP scripts, which is a little more complicated, but definitely doable. Once that's done, you can combine both files into a single one:
file.html:
<?php
$name = "Keshav";
?>
<html>
<body>
<h3>Hi there, <?php echo $name ?></h3>
</body>
</html>

What if, for a script easier to maintain, you move those to functions?
something like this:
<?php
function get_content($file, $data)
{
$template = file_get_contents($file);
foreach($data as $key => $value)
{
$template = str_replace('{'.$key.'}', $value, $template);
}
return $template;
}
And you can use it this way:
<?php
$file = '/path/to/your/file.php';
$data = = array('var1' => 'value',
'txt' => 'text');
echo get_content($file, $data);

Once you iron out all bugs, fix huge performance problem you're getting yourself into, you'll end up with template engine just like Smarty and otheres.
Such find'n'replace approach is much slower than compilation to PHP. It does not handle escaping very well (you'll run into XSS problems). It will be quite difficult to add conditions and loops, and you will need them sooner or later.

<?php
class view {
private $file;
private $vars = array();
public function __construct($file) {
$this->file = $file;
}
public function __set($key, $val) {
$this->vars[$key] = $val;
}
public function __get($key, $val) {
return (isset($this->vars[$key])) ? $this->vars[$key] : null;
}
public function render() {
//start output buffering (so we can return the content)
ob_start();
//bring all variables into "local" variables using "variable variable names"
foreach($this->vars as $k => $v) {
$$k = $v;
}
//include view
include($this->file);
$str = ob_get_contents();//get teh entire view.
ob_end_clean();//stop output buffering
return $str;
}
}
Here's how to use it:
<?php
$view = new view('userprofile.php');
$view->name = 'Afflicto';
$view->bio = "I'm a geek.";
echo $view->render();

Related

PHP template engine for loops

I have a simple template engine that works fine the simple template, but I don't know how to adapt it to make it work with loops :
class Template {
public $template;
function getFile($file) {
$this->template = file_get_contents($file);
}
function set($tag, $content) {
$this->template = str_replace("{".$tag."}", $content, $this->template);
}
function ouput() {
eval("?>".$this->template."<?");
}
}
That's the loop I want to parse and display:
{{#each Stuff}}
{{Thing}} are {{Desc}}
{{/each}}
I dont want use any SMARTY or Twig engine.
Any idea please?
Ok, keep in mind this is just for learning purposes. You can't ask on SO for the whole code, you need to try and post question about your tries.
This code parse a string for a foreach and then executes it:
<?php
$var = array(2, 4);
$str = 'for i in var';
$a = explode(' ', $str);
foreach (${$a[3]} as $i => $value)
{
echo $value;
}
Read this part from PHP docs to understand what i did.

how to implement a custom foreach template view method

I was coding a TemplateView Class which is replacing #somevariable# placeholders with it's controller set correspondent. ViewHelpers are called via placeholders that simulate a function like #headerFiles()# for example.
The thing missing is a foreach loop definition like for example the smarty
{foreach from=$var key=index item=value} definition.
Until now I was iterating over a database object inside the controller and having a heredok or string concatenation assigned to the view. So there is a lot of html inside the controllers which I think should not be the controllers job.
I already implemented smarty into the app for testing and it's working fine - But when I decide to really use it I would have to change so many things like all partial templates would have to be .tpl files and all template directories would have to be merged ? or in one place and so on.
So my question is:
Are there any good examples or codesnippets I could use to implement a custom foreach loop method ? Before I have to change everything to use smarty. I also want to avoid using plain php like <?php foreach(): endforeach; ?>
UPDATE:
what concerns would one have going this direction ?
In a template:
#foreach array#
if($key !== 'fart'){
echo $val;
}
#endforeach#
class Foreacher {
private $template;
protected $array = array('fart' => 'poop', 'foo', 'bar');
public function __construct($template){
$this->template = file_get_contents($template);
}
public function parse(){
if(preg_match_all('/#foreach ([\w]+)#(.*?)#endforeach#/is', $this->template, $matches, PREG_SET_ORDER)){
$var = $matches[0][1];
$freach = "<?php foreach(\$this->$var as \$key => \$val){";
$freach .= $matches[0][2];
$freach .= "} ?>";
$parsed = str_replace($matches[0][0], $freach, $this->template);
$this->render($parsed);
}
}
public function __get($var){
if(isset($this->$var)){
return $this->$var;
}
return null;
}
protected function render($string){
$tmp = tmpfile();
fwrite($tmp, $string);
fseek($tmp, 0);
ob_start();
$file = stream_get_meta_data($tmp);
include $file['uri'];
$data = ob_get_clean();
fclose($tmp);
echo $data;
}
}

How to secure a template engine in PHP from injection

I am currently trying to create a simple template engine in PHP. The main thing I care about is security, however template tutorials do not. Lets say I have a database table with a username and his description. The user can type whatever he wants there.
My guess would be to use htmlspecialchars() function, to prevent javascript and html injection. But what about 'template code injection'? If my template rule is to replace [#key] to "value", the user can update his description that interferes with my template handler. Should I treat "[", "#", "]" as special characters and replace them with their ascii code when using my set method?
template.php:
class Template {
protected $file;
protected $values = array();
public function __construct($file) {
$this->file = $file;
}
public function set($key, $value) {
$this->values[$key] = $value;
}
public function output() {
if (!file_exists($this->file)) {
return "Error loading template file ($this->file).";
}
$output = file_get_contents($this->file);
foreach ($this->values as $key => $value) {
$tagToReplace = "[#$key]";
$output = str_replace($tagToReplace, $value, $output);
}
return $output;
}
}
example.tpl:
Username: [#name]
About me: [#info]
index.php:
include 'template.php';
$page = new Template('example.tpl');
$page->set('info', '[#name][#name][#name]I just injected some code.');
$page->set('name', 'Tom');
echo $page->output();
This would display:
Username: Tom
About me: TomTomTomI just injected some code.
The code I used is based on:
http://www.broculos.net/2008/03/how-to-make-simple-html-template-engine.html
Change your function to search in the unchanged template only once for the known keys:
public function output() {
if (!file_exists($this->file)) {
return "Error loading template file ($this->file).";
}
$output = file_get_contents($this->file);
$keys = array_keys($this->values);
$pattern = '$\[#(' . implode('|', array_map('preg_quote', $keys)) . ')\]$';
$output = preg_replace_callback($pattern, function($match) {
return $this->values[$match[1]];
}, $output);
return $output;
}
I was thinking about it and I think this solution is fastest and simplest:
foreach ($this->values as $key => $value) {
$tagToReplace = "[#$key]";
if (strpos($output, "[#$value]") !== FALSE)
$value = '['.substr($value,1,-1).']';
$output = str_replace($tagToReplace, $value, $output);
}
It replaces brackets in value with html entity string if [$value] is in output.
Used this html entity list
For future adopters:
This kind of solution is OK if Template Parser is implemented by loading non-interpeted/non-evaled file (as is OP's case by loading local file using file_get_contents). However, if it's implemented by INCLUDING PHP view, beware that this check won't handle the case when you put some user-modifiable data from database into view directly (without using parser, e.g. <?=$var;?>) and then use template parser for this view. In that case, parser has no way to know if these data are part of template structure and this check won't work for them. (I don't know how should this case be handled properly.) Anyhow, I think best practice is to never pass sensitive data to template parser, even if you don't use them in your template. When attacker then tricks parser to eval his custom data, he won't get information he didn't already have. Even better, don't use template parser.

php str_replace template with placeholders

I have one array for data
$data = array(title=>'some title', date=>1350498600, story=>'Some story');
I have a template
$template = "#title#, <br>#date(d)#<br> #date(m)#<br>#date(Y)#<br> #story#";
All i want is to fit data into template and i know that can be done by str_replace but my problem is the date format. date format is coming from the template not from the data, in data date is stored as php date.
yesterday i tried to ask the same question but i think my question wasn't clear.
Anybody please help me.
i think it won't work with str_replace easily so i'm going to use preg_replace
$data = array('title'=>'some title', 'date'=>1350498600, 'story'=>'Some story');
$template = "#title#, <br>#date(d)#<br> #date(m)#<br>#date(Y)#<br> #story#";
$result = preg_replace_callback('/#(\w+)(?:\\((.*?)\\))?#/', function ($match) use($data) {
$value = isset($data[$match[1]]) ? $data[$match[1]] : null;
if (!$value) {
// undefined variable in template throw exception or something ...
}
if (! empty($match[2]) && $match[1] == "date") {
$value = date($match[2], $value);
}
return $value;
}, $template);
Instead of using date(m) or date(Y) you could also do things like
date(d-m-Y) using this snippet
This has the disadvantage that you can format only the date variable using this mechanism. But with a few tweaks you can extend this functionality.
Note: If you use a PHP version below 5.3 you can't use closures but you can do the following:
function replace_callback_variables($match) {
global $data; // this is ugly
// same code as above:
$value = isset($data[$match[1]]) ? $data[$match[1]] : null;
if (!$value) {
// undefined variable in template throw exception or something ...
}
if (! empty($match[2]) && $match[1] == "date") {
$value = date($match[2], $value);
}
return $value;
}
$data = array('title'=>'some title', 'date'=>1350498600, 'story'=>'Some story');
$template = "#title#, <br>#date(d)#<br> #date(m)#<br>#date(Y)#<br> #story#";
// pass the function name as string to preg_replace_callback
$result = preg_replace_callback('/#(\w+)(?:\\((.*?)\\))?#/', 'replace_callback_variables', $template);
You can find more information about callbacks in PHP here
I'd suggest using a templating engine like so:
https://github.com/cybershade/CSCMS/blob/master/core/classes/class.template.php
And then your templates turn out like this:
https://github.com/cybershade/CSCMS/blob/master/themes/cybershade/site_header.tpl
and
https://github.com/cybershade/CSCMS/blob/master/modules/forum/views/viewIndex/default.tpl
Download this file: http://www.imleeds.com/template.class.txt
Rename the extension to .PHP from .TXT
This is something I created years ago, I keep my HTML away from my PHP, always. So see an example below.
<?php
include("template.class.php");
//Initialise the template class.
$tmpl = new template;
$name = "Richard";
$person = array("Name" => "Richard", "Domain" => "imleeds.com");
/*
On index.html, you can now use: %var.name|Default if not found% and also, extend further, %var.person.Name|Default%
*/
//Output the HTML.
echo $tmpl->run(file_get_contents("html/index.html"));
?>

PHP Looping Template Engine - From Scratch

For a group project I am trying to create a template engine for PHP for the people less experienced with the language can use tags like {name} in their HTML and the PHP will replace that tag with a predefined variable from an array. As well as supporting loops.
This is well beyond the expectations of the project, but as I have experience with PHP I thought it would be a good challenge to keep me busy!
My main questions are, how do I do the loop part of the parser and is this the best way to implement such a system. Before you just recommend an existing template system, I would prefer to create it myself for experience and because everything in our project has to be our own.
At the moment the basic parsing is done with regex and preg_replace_callback, it checks if $data[name] exists and if it does replaces it.
I have tried to do the loop a variety of different ways but am not sure if I am on the correct track!
An example if the data the parsing engine was given is:
Array
(
[title] => The Title
[subtitle] => Subtitle
[footer] => Foot
[people] => Array
(
[0] => Array
(
[name] => Steve
[surname] => Johnson
)
[1] => Array
(
[name] => James
[surname] => Johnson
)
[2] => Array
(
[name] => josh
[surname] => Smith
)
)
[page] => Home
)
And the page it was parsing was something like:
<html>
<title>{title}</title>
<body>
<h1>{subtitle}</h1>
{LOOP:people}
<b>{name}</b> {surname}<br />
{ENDLOOP:people}
<br /><br />
<i>{footer}</i>
</body>
</html>
It would produce something similar to:
<html>
<title>The Title</title>
<body>
<h1>Subtitle</h1>
<b>Steve</b> Johnson<br />
<b>James</b> Johnson<br />
<b>Josh</b> Smith<br />
<br /><br />
<i>Foot</i>
</body>
</html>
Your time is incredibly appreciated with this!
Many thanks,
P.s. I completely disagree that because I am looking to create something similar to what already exists for experience, my well formatted and easy to understand question gets down voted.
P.p.s It seems there is a massive spread of opinions for this topic, please don't down vote people because they have a different opinion to you. Everyone is entitled to their own!
A simple approach is to convert the template into PHP and run it.
$template = preg_replace('~\{(\w+)\}~', '<?php $this->showVariable(\'$1\'); ?>', $template);
$template = preg_replace('~\{LOOP:(\w+)\}~', '<?php foreach ($this->data[\'$1\'] as $ELEMENT): $this->wrap($ELEMENT); ?>', $template);
$template = preg_replace('~\{ENDLOOP:(\w+)\}~', '<?php $this->unwrap(); endforeach; ?>', $template);
For example, this converts the template tags to embedded PHP tags.
You'll see that I made references to $this->showVariable(), $this->data, $this->wrap() and $this->unwrap(). That's what I'm going to implement.
The showVariable function shows the variable's content. wrap and unwrap is called on each iteration to provide closure.
Here is my implementation:
class TemplateEngine {
function showVariable($name) {
if (isset($this->data[$name])) {
echo $this->data[$name];
} else {
echo '{' . $name . '}';
}
}
function wrap($element) {
$this->stack[] = $this->data;
foreach ($element as $k => $v) {
$this->data[$k] = $v;
}
}
function unwrap() {
$this->data = array_pop($this->stack);
}
function run() {
ob_start ();
eval (func_get_arg(0));
return ob_get_clean();
}
function process($template, $data) {
$this->data = $data;
$this->stack = array();
$template = str_replace('<', '<?php echo \'<\'; ?>', $template);
$template = preg_replace('~\{(\w+)\}~', '<?php $this->showVariable(\'$1\'); ?>', $template);
$template = preg_replace('~\{LOOP:(\w+)\}~', '<?php foreach ($this->data[\'$1\'] as $ELEMENT): $this->wrap($ELEMENT); ?>', $template);
$template = preg_replace('~\{ENDLOOP:(\w+)\}~', '<?php $this->unwrap(); endforeach; ?>', $template);
$template = '?>' . $template;
return $this->run($template);
}
}
In wrap() and unwrap() function, I use a stack to keep track of current state of variables. Precisely, wrap($ELEMENT) saves the current data to the stack, and then add the variables inside $ELEMENT into current data, and unwrap() restores the data from the stack back.
For extra security, I added this extra bit to replace < with PHP echos:
$template = str_replace('<', '<?php echo \'<\'; ?>', $template);
Basically to prevent any kind of injecting PHP codes directly, either <?, <%, or <script language="php">.
Usage is something like this:
$engine = new TemplateEngine();
echo $engine->process($template, $data);
This isn't the best method, but it is one way it could be done.
Ok firstly let me explain something tell you that PHP IS A TEMPLATE PARSER.
Doing what your doing is like creating a template parser from a template parser, pointless and to be quite frank it iterates me that template parser's such as smarty have become so well at a pointless task.
What you should be doing is creating a template helper, not a parser as there redundant, in programming terms a template file is referred to as a view and one of the reasons they was given a particular name is that people would know there separate from Models, Domain Logic etc
What you should be doing is finding a way to encapsulate all your view data within your views themselves.
An example of this is using 2 classes
Template
TemplateScope
The functionality of the template class is for the Domain Logic to set data to the view and process it.
Here's a quick example:
class Template
{
private $_tpl_data = array();
public function __set($key,$data)
{
$this->_tpl_data[$key] = $data;
}
public function display($template,$display = true)
{
$Scope = new TemplateScope($template,$this->_tpl_data); //Inject into the view
if($display === true)
{
$Scope->Display();
exit;
}
return $Scope;
}
}
This is extreamly basic stuff that you could extend, oko so about the Scope, This is basically a class where your views compile within the interpreter, this will allow you to have access to methods within the TemplateScope class but not outside the scope class, i.e the name.
class TemplateScope
{
private $__data = array();
private $compiled;
public function __construct($template,$data)
{
$this->__data = $data;
if(file_exists($template))
{
ob_start();
require_once $template;
$this->compiled = ob_get_contents();
ob_end_clean();
}
}
public function __get($key)
{
return isset($this->__data[$key]) ? $this->__data[$key] : null;
}
public function _Display()
{
if($this->compiled !== null)
{
return $this->compiled;
}
}
public function bold($string)
{
return sprintf("<strong>%s</strong>",$string);
}
public function _include($file)
{
require_once $file; // :)
}
}
This is only basic and not working but the concept is there, Heres a usage example:
$Template = new Template();
$Template->number = 1;
$Template->strings = "Hello World";
$Template->arrays = array(1,2,3,4)
$Template->resource = mysql_query("SELECT 1");
$Template->objects = new stdClass();
$Template->objects->depth - new stdClass();
$Template->display("index.php");
and within template you would use traditional php like so:
<?php $this->_include("header.php") ?>
<ul>
<?php foreach($this->arrays as $a): ?>
<li><?php echo $this->bold($a) ?></li>
<?php endforeach; ?>
</ul>
This also allows you to have includes within templates that still have the $this keyword access to then include themselves, sort of recursion (but its not).
Then, don't pro-grammatically create a cache as there is nothing to be cached, you should use memcached which stores pre compiled source code within the memory skipping a large portion of compile / interpret time
If I'm not worried about caching or other advanced topics that would push me to an established template engine like smarty, I find that PHP itself is a great template engine. Just set variables in a script like normal and then include your template file
$name = 'Eric';
$locations = array('Germany', 'Panama', 'China');
include('templates/main.template.php');
main.tempate.php uses an alternative php tag syntax that is pretty easy for non php people to use, just tell them to ignore anything wrapped in a php tag :)
<h2>Your name is <?php echo $name; ?></h2>
<?php if(!empty($locations)): ?>
<ol>
<?php foreach($locations as $location): ?>
<li><?php echo $location; ?></li>
<?php endforeach; ?>
</ol>
<?php endif; ?>
<p> ... page continues ... </p>
I had a very basic answer to something KINDA like this back before I started using DOMXPath.
class is something like this (not sure if it works quite like what you want but food for thought as it works very simple
<?php
class template{
private $template;
function __CONSTRUCT($template)
{
//load a template
$this->template = file_get_contents($template);
}
function __DESTRUCT()
{
//echo it on object destruction
echo $this->template;
}
function set($element,$data)
{
//replace the element formatted however you like with whatever data
$this->template = str_replace("[".$element."]",$data,$this->template);
}
}
?>
with this class you would just create the object with whatever template you wanted and use the set function to place all your data.
simple loops after the object is created can probably accomplish your goal.
good luck
Smarty :) ...
php:
$smarty->assign("people",$peopleArray)
smarty template:
{foreach $people as $person}
<b>{$person.name}</b> {$person.surname}<br />
{/foreach}
Couple other things to do but that's what smarty will be like essentially.
Use Smarty.

Categories