Another php template engine

    Good day,

    I want to talk about my template engine for projects in PHP.
    I understand that I run the risk of being accused of inventing a bicycle, so I’ll explain my motives: Most of the template engines do not suit me initially, among them  Smarty , Quicky and all the like, the reason is that it seems to me that the template engine should eliminate the use of logic in templates, and not impose own syntax for the same logic.
    In other words, this:
    1. {?$x = 2+2}
    or such
    1. {foreach name=my from=array('One','Two','Three') key="i" item="text"}
    approaches for me are absolutely unacceptable!
    Perhaps, of all the template engines, xtemplate satisfies my requirements the most, but it has a number of drawbacks that annoy me, for example, that all pages need to be framed in blocks, or that it interprets templates rather than compiles, so it boasts speed can not. Well, the last thing - I decided to write a template engine so that there were no problems with adding functionality, and also that it was compatible with the native template engine that I had used before, and which I was used to. The fact is that the design 
    1. $tpl->assigned_var='abc';
    which native template engines often use, I like much more than anything like:
    1. $thl->assign('assigned_var','abc');
    At one point, I realized that it’s easier to write your own template engine than to look for one that suits me. And, I think, he was right, because the matter cost a few evenings.
    Generally speaking, the process seemed rather interesting to me, and there were many points that I would like to discuss with the community.

    I'll start with a description of the syntax:


    1) Variables:

    Everything is as usual, except for any "{$"
    Business logicTemplate
    1. $tpl->var_name='...';
    1. {var_name}
    1. $tpl->var_name['sub_var']='...';
    1. {var_name.sub_var}

    2) Blocks:

    They are needed to get rid of constructions like {foreach name = my from = array ('One', 'Two', 'Three') key = "i" item = "text"}
    Like xtpl, it’s only slightly automated, namely To parse a block (they also say to replicate), it's enough just to pass an array with data to the template!
    Business logicTemplate
    1. $tpl->block_name[]['num']='4';
    2. $tpl->block_name[]['num']='8';
    3. $tpl->block_name[]['num']='15';

    1. {block_name.num}
    1. $tpl->words['block']=array(
    2.     O=>array('word'=>'A'),
    3.     1=>array('word'=>'B'),
    4.     2=>array('word'=>'C'),
    5. );

    1. {words.block.word}
    To display a block variable inside, you need to call it {block_name.variable_name}
    This allows you to access both the block variables and external variables from the inside.
    A block can be absolutely any variable, for example, in the second example, a block is constructed using the "block" element of the "words" array »
    Also, a block can be inside another block, for example, here is a simple way to build a multiplication table:
    Business logicTemplate
    1. for ($i=1; $i<10; $i++)
    2.     for ($j=1; $j<10; $j++)
    3.         $tpl->table[$i]['row'][$j]['num']=$i*$j;

    1.    
    2.    

    3.        
    4.            

    5.        
    6.    

    7.    
    8. {table.row.num}

    3) Checks:

    In essence, a kind of block. It is needed in order to either show what is inside or not based on some variable. It will become clearer with an example:
    Business logicTemplate
    1. $tpl->f_text=true;

    1. Пока f_text==true мы будем видеть этот текст
    1. $tpl->f_text=false;

    1. Тут можно писать что угодно, потому что заказчик не увидит

    4) Functions:

    This is more of an experimental feature, I would like to hear the opinion whether this approach has the right to life:
    Tpl.class.php fileTemplate
    1. function up($text) {
    2.     return strtoupper($text)
    3. }
    1. {up}текст который нужно сделать БОЛЬШИМ{/up}
    I draw your attention to the fact that functions should be added to the template class.
    At the moment, functions work with only one parameter, and I think how to expand the number of parameters as follows:
    1. {func(param2,param3)}param1{/func}
    , or so:
    1. {func}param1|param2|param3{/func}
    or something else. While leaning towards the first option, it is easier to implement!

    Now about the principles:


    I decided to divide the template engine into two parts:

    1) The template engine itself (as compact as possible, everything you need)
    2) The compiler (and here everything else)
    This is necessary to improve performance, because there is no point in 8 kb of compiler code if the template has already been compiled and has not changed since then .

    The inclusion process inside the templates made me think a lot:

    At first glance, the moment may seem trifling, but it is not. Generally speaking, inclusions had to be divided into two parts - static and dynamic. A static inclusion is a regular inclusion, for example
    This inclusion is processed as follows - the code from some_page.html is inserted in its place, the change time for the file of the compiled template will be 1 second longer than for the template itself, and from this the template engine learns that you need to connect a special file also created by the compiler, into which The following line has been added:
    1. if (filemtime('./some_page.html') != 1237369507) $needCompile=true;
    Thus, if you change this file, the entire template will be recompiled.
    Why is this needed, why not just insert include? But what if you need to display a block of 1000 lines, inside which an inclusion will be inserted for convenience? Then such a trick will greatly help performance!
    Now about another type of inclusions - dynamic. This miracle looks in my template engine like this:
    That is, we do not include any predefined file, but take its name or part of the name from a variable! Sometimes it can be very convenient, but the old method with this approach will not work anymore, because if you change the business logic of the variable, another file should be included, so this design will be compiled into the following code:
    1. render(''.$this->page_name.'.html');?>
    I note that at the moment such an inclusion will not work inside the block, that is, it will work, but the block variables will not be available inside the connected file, but I think this is not very scary, because in contrast to xtpl, blocks are needed only for cyclical output of some kind of array.

    Php code inside:

    You can safely use it, and thought well, but decided not to prohibit php code in the templates.
    I understand that it will cause a lot of controversy, but I think that it makes no sense to prohibit php, since I have not encountered such situations when it makes sense, and does not cause inconvenience in some situations.

    And finally, what principles I was guided by when deciding what the syntax would be:

    1) Minimum logic, all logic - business logic
    2) Everything is as natural as possible
    3) Less code
    4) Maximum possibilities

    Code of the template engine itself:

    1. class tpl {
    2.     function tpl($tplDir,$tmpDir) {
    3.         $this->tplDir=$tplDir;
    4.         $this->tmpDir=$tmpDir;
    5.     }
    6.     function Render($Path) {
    7.         $tmpName='tpl_'.str_replace(array('/','\\'),'.',$Path).'.php';
    8.         $tmpPath=$this->tmpDir.'/'.$tmpName;
    9.         if (file_exists($tmpPath))
    10.             $tmpChange=filemtime($tmpPath);
    11.         $tplChange=filemtime($Path);
    12.         if ($tplChange+1==$tmpChange) include($tmpPath.'.coll.php');
    13.         elseif ($tplChange!=$tmpChange) $needCompile=true;
    14.         if ($needCompile) {
    15.             # Вызов компилятора
    16.             include_once 'tcompiler.class.php';
    17.             $compiler = new tcompiler($this,$this->tmpDir);
    18.             $compiler->compile($this->tplDir.'/'.$Path,$tmpPath);
    19.         }        
    20.         include $tmpPath;
    21.     }
    22. }
    23. ?>

    As you can see, not a lot, but fast!
    At first glance, it might seem that such an autocorrect:
    1. $tplName='tpl_'.str_replace(array('/','\\'),'.',$path).'.php';
    It works for quite a while and it is better to use a hash, but I tested it, the hash lasts longer.

    Code Comparison:

    I decided to conveniently list the code listings in different template engines that do the same thing, so that you can quickly compare the readability and convenience of approaches.
    My
    1. $tpl->num=4815162342;
    2. $tpl->post['page']['id']=316;
    3. for ($i=1; $i<30; $i++) $tpl->bin[]=array('dec'=>$i, 'bin'=>decbin($i));
    4. for ($i=1; $i<10; $i++) for ($j=1; $j<10; $j++) $tpl->table[$i]['row'][$j]['num']=$i*$j;
    Smarty / quicky
    1. $smarty->assign("num",4815162342);
    2. $smarty->assign("post",array('page'=>array('id'=>316)));
    3. for ($i=1; $i<30; $i++) $bin[]=array('dec'=>$i, 'bin'=>decbin($i));
    4. $smarty->assign("bin",$bin);
    5. for ($i=1; $i<10; $i++) for ($j=1; $j<10; $j++) $table[$i]['row'][$j]['num']=$i*$j;
    6. $smarty->assign("table",$table);
    Xtemplate
    1. $xtpl->assign('num',4815162342);
    2. $post['page']['id']=316;
    3. $xtpl->assign('post',$post);
    4. for ($i=1; $i<30; $i++) $xtpl->insert_loop("page.bin",array("dec"=>$i,"bin"=>decbin($i)));
    5. for ($i=1; $i<10; $i++) {
    6.         for ($j=1; $j<10; $j++) $xtpl->insert_loop("page.table.row",'rownum',$i*$j);
    7.         $xtpl->parse("page.table");    
    8. }

    Connection:

    The template engine is connected as follows:
    1. require_once 'путь_до_шаблонизатоора/tpl.class.php';
    2. $tpl=new tpl('путь_к_папке_с_шаблонами','путь_к_папке_с_кешем');
    Do not forget to give the right folder with the cache!

    Download:

    While the template engine is right here to download , while it is only a  beta version, so do not test on serious projects, I just wanted to listen to comments and ideas on this topic!
    If the experiment succeeds and someone needs a similar hybrid of the native and ordinary template engine, I will definitely develop it. By the way, most likely it will be called "LL".
    Regarding bugs, please unsubscribe to oleg <dog> emby.ru

    Conclusion:

    В заключение не буду делать громких заявлений, вроде «В отдельных случаях данный шаблонизатор быстрее php native». Все мы понимаем, что в отдельных случаях трактор «Беларусь» может оказаться быстрее новенькой Porshe Panamera, в любом случае шаблонизатор будет медленнее, хотябы потому что ему нужно сравнивать даты изменения шаблона и его откомпилированной версии, а это два лишних обращения к ФС. Касатемо оптимизаций, никто не мешает оптимизировать и нативный код.
    Разумеется, как и все шаблонизаторы, мой работает медленнее нативного php, но на самую малость, в доказательство привожу результаты тестов:
    Template Performance Comparison
    Все тесты проводил по несколько раз, дабы убедиться, что НЛО не повлияло на результаты. Если что выложил их сюда.

    Also popular now: