Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5

Simple PHP Template Engine

#1
Hello all, 

EDIT: See the GitHub version. A new version is available with significant improvements. https://github.com/Darth-Apple/simple-php-templates

I've done quite a few small CMS-style projects for university and for random purposes. Each time, we ended up having to theme the project and it was always easier to do so by separating the HTML from the PHP. Fortunately, there are many template engines available, but some are very tedious to implement and are not suitable for small projects. 

Luckily, it's incredibly easy to do templates with no trouble whatsoever. This template engine is incredibly simple and can be added to your project in less than 5 minutes. I use it on every raw project I've ever done, and it's a great time-saver and makes our code much cleaner and easier to maintain. 

I've included the template_engine.php file with this post. It's licenced under the GPL 2, but if you need a different license, please PM me! 



1. Create a file called template_engine.php with the downloaded file attached here. Include (or require_once) this file into every file/page of your project (such as index.php, etc). You may want to use your require_once("template_engine.php") in your init or global.php instead, depending on how your project is structured.

2. Now, create a templates folder inside your project. This folder should be in your project root and will contain all HTML template files. 

3. Instantiate the template engine in each of these pages, or in your init file. Use the following: 

Code:
$templates = new template_engine(); 


4. Start setting template variables/tags in your code. These variables will be replaced with the values they are set to when the template engine outputs your page. For example, in your index.php, you could set variables like this: 

Code:
$templates->set("some_variable", "1234");
$templates->set("username", "Steve"); 

Now, the [@some_variable] tag will be replaced by "1234" by the template engine when it's ready to parse. Likewise, [@username] will be replaced with "Steve". Create as many variables as you like.

5. Create your index.html template in your templates folder. Use something like the following:

Code:
<head> 
<title>My Index Page </title>
</head>

<body>
<h1> Hello, [@username]. Today's magic variable: [@some_variable] </h1> 
</body>

As you can see, we've used our new template variables directly in the template file. These variables will be replaced by their corresponding values on the fly by the template engine.

6. Parse the template and echo it. This will process all template tags and will send the resulting page to the browser. 

Code:
echo $template->parse("index"); 

And now, with a simple template engine and 5 minutes of implementation, you can abstract away all HTML from your PHP code. This allows you to completely refactor the style and hTML structure of your theme/page without changing any of your PHP files, and keeps the project much cleaner than it would be with interleaved HTML/PHP. 

Simple template engines such as these are perfect for small projects, university/college assignments, etc.

Hope this helps! Feel free to use it in your own projects as needed. Smile

More Information (click spoiler):
[spoiler]

Can I change a variable/tag after it's already been set?
Yes! So long as you haven't parsed your template (sent it to the browser) yet, you can use the $templates->set function as many times as you need. It will overwrite the previous value of the tag with your new value.

How should I handle template headers/footers?

I recommend having a separate header.html and footer.html template. At the very beginning of your PHP page, parse these using the following:
Code:
$header = $templates->parse("header");
$templates->set("header", $header);

$footer = $templates->parse("footer");
$templates->set("footer", $footer);

Now, in your index.html template, you may use the header/footer tags as normal:

Code:
[@header]

<h1> My page content </h1>
[@footer]

This allows you to use the same header/footer template for all of your pages, preventing you from having to duplicate the HTML in every page of your website.

Can I use templates inside of templates?

Yes! In fact this is recommend and is standard practice for simple template engines such as these. See the example above on how to do this.

What about the license?
Feel free to use this in your own projects, that's what it's for! I wouldn't have licensed this at all, but I don't want someone stealing it, saying they wrote it, and then licensing it under some restrictive license. Finna If you need a different license, please PM me!
[/spoiler]


Attached Files
.php   template_engine.php (Size: 2.58 KB / Downloads: 268)

Reply
#2
Hello Darth, I'll test that right away, it's going to be fun, I'm looking forward to it, THANK YOU great idea


 
[Image: autism4all.png]
[x] <= Drive in nail here for new display!
Reply
#3
I was working on a project recently, and I realized this could be shrunk down quite a bit while keeping the exact same functionality. Reduced it by over 80% without even employing typical minification (I need to start coding a little more efficiently!) Finna

Lines: 17 (from 79)
Bytes: 408 (from 2.65KB)

Code:
<?php
//Template Engine. License: GPL v3

class template_engine {
    public $tags = array();

    public function set ($k,$v) {
        $this->tags["[@$k]"]=$v;
    }
    public function parse($tmpl) {
        return $this->parse_raw(file_get_contents("templates/$tmpl.html"));
    }
    public function parse_raw($htm) {
        return strtr($htm, $this->tags);
    }
    public function load($name) {
        $this->set($name, $this->parse($name));
    }
}

And there you have it. The world's most lightweight template engine. Big Grin

(I say "engine," but it's essentially a wrapper for template bindings that stores variables, loads templates, and does string replacements without having to keep track of functions, directories, or variables directly. I've used it in several projects, and it's been a great time saver.)


Edit: Pushed this further... Cool [spoiler]

Came back and did some real minification on it, and got it down to about 255 bytes. I must say it definitely gives an appreciation for every single byte. After all, there was once a time when computers only had 64K of RAM. Finna

Code:
<?php class template_engine{function set($k,$v){$this->b["[@$k]"]=$v;}function parse($t){return$this->parse_raw(file_get_contents("templates/$t.html"));}function parse_raw($d){return strtr($d,$this->b);}function load($m){$this->set($m,$this->parse($m));}}

7-zip failed to compress this at all (the file ended up expanding). That being said, XZ got it down to 228 bytes, and Gzip got it down to about 191 (so at least it wasn't totally uncompressable) Tongue

I'm curious to see if this can be made any smaller. If anyone has any ideas, feel free to share them! We might be able to pack this even tighter. Finna

Edit 2: Just how far can this go?
-- It turns out that by renaming all functions to s, l, r, and p, along with the class to t, and removing the template/[name].html hardcoded path (must provide full path on function call), it can be brought down to 193 bytes (178 with gz). Unfortunately, in doing so, compatibility was broken because functions and paths were renamed. Wasn't quite worth the 62 bytes, but this demonstrates just how far this can go. Looks like 255 is still the winner in the meantime.)

[/spoiler]


Attached Files
.php   template_min.php (Size: 409 bytes / Downloads: 213)

Reply
#4
I've been doing some work to make this a more fully-featured template engine. Its goal is still to be incredibly lightweight, but it now supports:

Loops:

This is perhaps one of the most useful features that is present in established engines such as Twig or Blade. It allows you to load a list of posts, for example, by "looping" the same HTML code for each post. This can be applied for pretty much any kind of list imagineable. This engine has native support for simple for loops, such as:

Code:
[@loop: people as person]
Person's name: [@person:name] <br />
Person's age: [@person: age]<br />
[/loop]

HOST CODE (PHP)
$templates->set_array("people", $people_array); // Sets the "people" variable for the looping engine

This was implemented with a BBcode-like Regex parser that supports nesting via recursive callbacks. It first looks for loops and creates a recursive "tree" - and it evaluates the inner loops first to determine if it needs to execute them. It works its way back up the "tree", finishing them one-by-one as to support multiple consecutive loops or complex nested structures. The "inner-loop first" structure is a bit unorthodox as far as parsing is concerned, but but was a concession made to support more complex structures using plain regex.

Currently, only "FOR" loops are supported. No plans are in place to implement "while" loops.

Language strings

This engine will now parse and manage basic language strings. It keeps track of a "locale" that looks into a specific folder for the core language file. You may load additional language files for specific pages as needed, which saves performance by not loading every single language string for every page.

Code:
$templates->lang_load("users"); // Loads all language strings in Languages/[locale]/users.lang.php

TEMPLATE:

<div class="user">
[@lang:username] - Insert stuff here <br />
</div>

IF/ELSE statements

These features are almost universally present in other established template engines, and allow conditions to be evaluated directly in the template. This results in cleaner code and less PHP evaluation work.

Code:
Before IF statement: <br />

[@if: $logged_in == true]
  - Display logged out link
[/if]
[@else]
  - Display logged in link
[/else]

Developing support for these proved to be unusually tricky. Recursive Regex callbacks were used for implementation (an unusual choice for this type of job). Pure Posix-compliant regex actually cannot parse XML-like languages in this manner (tokenizers and specialized parse trees are used instead). However, PHP uses the PCRE regex engine (one of the most powerful regex engines in existence) that has the necessary features to make this possible.

Unfortunately, it has drawbacks. Performance being one of them, among strict syntax requirements built around the requirements of the regex. It is also easy to break with mismatched tags. However, with properly formatted syntax, nested if/elseif/else blocks are fully supported. Most of the StackOverflow examples lacked support for nesting, so this implementation is a unique Regex implementation.

Standard Template Variables
What would a template engine be without template variables? Thankfully, we aren't using straight PHP, nor are we using eval. This reduces the risk of PHP code injection and improves security over using flat PHP templates.

Code:
$templates->set("Username", "Steve");

TEMPLATE:
<strong>Username: </strong> [@username]

Right now, it's sizing out around 10KB of code or so. It will likely expand a little as I debug things, but the goal is to keep it fully contained within about 15KB or so. If it proves to be stable enough to be used in real world applications, I'll post the updated version on Github and will probably try to use it in a few projects of my own. I'll post some explanations on the code as a reference on how to do syntax parsing with Regex as well. Big Grin

Edit: It now compiles templates to pure PHP for native performance - After doing all of the work using regex to write a full interpreter for the basic template conditionals/loops, I wrote a compiler to do the exact same thing. It writes pure PHP template cache files and saves them for future use. The template engine will now run at native speeds, and has an interpreter as a fallback. Unfortunately, this exploded it to about 20KB in size. Going to trim this and see what can be done. Finna

Reply
#5
I have the new version up on Github (in beta). It's bare-bones, approximately 6.55KB, and is fully functional for basic templating applications. A mere 166 lines in whole, it is likely one of the smallest template engines in existence! Big Grin

It's also lightning fast. It compiles the templates down to pure PHP files and caches them, so there is near-zero overhead with parsing the templates and rendering them. This proved to be much more stable and much easier than the previous interpreter-based approach.

https://github.com/Darth-Apple/simple-php-templates

Still working out a few rough edges and bugs, but as of yet, it's managed to beat the 10KB goal, and it's even implemented more features than originally planned. I'm looking forward to using it for future projects and university assignments. Finna

Edit: Just like last time, we tried our minification experiment to squash it into the smallest size possible. Click the spoiler to see stats.

[spoiler]
This time, the final tally was 2,550 bytes. (3,922 bytes unminified). This was exactly 10x the size of the 255 bytes we got from the last version, but being that this was a much more advanced, fully fledged template compiler, we can call it a success. Finna

Furthermore, XZ (a 7-zip like LZMA container) got it down to a mere 996 bytes, and gzip got down to 918 bytes with a standard ZIP format. This version proved to be significantly more compressible than the last. The hefty regex probably deserves a fair portion of the credit for XZ's ability to squash the file this time. (And this is interesting, given that we minified it to total uncompressibility last time around. No compression algorithm could shrink it any further. Finna)


Code:
<?php class template_engine{public $b=array();public $l=array();private $loc;private $cachePath="templates/cache/";private $templatePath="templates/";private $enable_cache=1;public function __construct($l){$this->loc=$l;}public function set($k,$v){$this->b[$k]=$v;}public function load_lang($f){require "languages/".$this->loc."/".$f.".lang.php";$this->l=$this->l+$l;}public function parse($t){return $this->render($t,1);}public function parse_raw($t){ob_start();eval("?>".$this->compile($t,0,1)."<?php");return ob_get_clean();}public function load($t){$this->set($t,$this->parse($t));}protected function format_expression($e){return preg_replace('/\$([A-Za-z0-9_]*)/','$this->b["$1"]',$e);}public function compile($tpl,$c=1,$r=0){$d=$tpl;if(!$r){$d=file_get_contents("Styles/default/Templates/".$tpl.".html");}$d=str_replace("[@else]",'<?php else: ?>',$d);$d=str_replace("[/if]",'<?php endif; ?>',$d);$d=preg_replace('/\[@template:([a-zA-Z0-9_]+)\]/','<?php echo $this->render("$1", 1); ?>',$d);$d=preg_replace('/\[@template:@([a-zA-Z0-9_]+)\]/','<?php echo $this->render($this->b[\'$1\'], 1); ?>',$d);$d=preg_replace("/\[@([a-zA-Z0-9_]+)\]/",'<?php echo htmlspecialchars($this->b[\'$1\']); ?>',$d);$d=preg_replace('/\[\$([a-zA-Z0-9_]+)\]/','<?php echo $this->b[\'$1\']; ?>',$d);$d=preg_replace('/\[@lang:([a-zA-Z0-9_]+)\]/','<?php echo $this->l[\'$1\']; ?>',$d);$d=preg_replace_callback("#\[@loop: *(.*?) as (.*?)]((?:[^[]|\[(?!/?@loop:(.*?))|(?R))+)\[/loop]#",function($l){$i=preg_replace("#\[@loop: *(.*?) as (.*?)]((?:[^[]|\[(?!/?@loop:(.*?))|(?R))+)\[/loop]#",'<?php foreach(\$this->b[\'$1\'] as \$$2): ?> $3 <?php endforeach; ?>',$l[0]);$i=preg_replace('/\[@'.$l[2].':([a-zA-Z0-9_]+)\]/','<?php echo htmlspecialchars($'.$l[2].'[\'$1\']); ?>',$i);return preg_replace('/\[\$'.$l[2].':([a-zA-Z0-9_]+)\]/','<?php echo $'.$l[2].'[\'$1\']; ?>',$i);},$d);$d=preg_replace_callback('#\[@(if|elif|else if): *(.*?)]#',function($ex){if($ex[1]=="elif"||$ex[1]=="else if"){$ct="elseif";}else{$ct="if";}return "<?php ".$ct." (".$this->format_expression($ex[2])."): ?>";},$d);if($c){file_put_contents($this->cachePath.$template.".php",$d);}else{return $d;}}public function render($tpl,$r=0){$cF=$this->cachePath.$tpl.".php";$tF=$this->templatePath.$tpl.".html";if($this->enable_cache){if(file_exists($cF)){if(filemtime($cF)<filemtime($tF)){$this->compile($tpl);}if($r){ob_start();require($this->cachePath.$tpl.".php");return ob_get_clean();}else{require($cF);}}else{$this->compile($tpl);$this->render($tpl);}}else{eval("?>".$this->compile($tpl,0)."<?php");}}}
[/spoiler]

Reply
#6
Thank you, and test it right away :-) Big Grin


 
[Image: autism4all.png]
[x] <= Drive in nail here for new display!
Reply
#7
I've done some more work refining this, and am now using it in a homegrown project of my own. It's actually managed to get the attention of a few folks I know from various places, so I'm glad to see that it seems to be in use so far. Tongue

Information Page: https://github.com/Darth-Apple/simple-ph...lates/wiki

Features implemented:
  • Template inheritance (extensions)
    • Allows a parent page to contain basic layout (header, footer, etc) with a child template containing page-specific data.
    • Allows blocks to be injected into a parent template at predefined locations
  • Listeners/Events (template hooks)
    • These allow functions to be called at predefined locations within the templates.
    • These are very useful for menus or sidebar blocks, where the content may need to be appended dynamically within PHP.
  • If/else blocks
  • Language packs/locale strings
  • Basic substitution variables
  • Template loops (PHP foreach)
  • Autoescape variables (XSS protection)
  • Template backreferences (include another template within a template)
  • Compiles to vanilla PHP (for native performance)

Footprint:

Lines: 231
Size: 8.3KB
Files: 1

Though it has grown, its lightweight spirit has remained a top priority during the development. It still weighs in under 10KB and is fully functional, containing many of the same features offered by the larger, more popular engines!

Reply
#8
Wink  PERFEKT !!


 
[Image: autism4all.png]
[x] <= Drive in nail here for new display!
Reply




Users browsing this thread: 3 Guest(s)

Dark/Light Theme Selector

Contact Us | Makestation | Return to Top | Lite (Archive) Mode | RSS Syndication 
Proudly powered by MyBB 1.8, © 2002-2024
Forum design by Makestation Team © 2013-2024