📜 ⬆️ ⬇️

Another php template engine

Good day,

I want to talk about my template engine for PHP projects.
I understand that I risk being accused of inventing a bicycle, so I will explain my motives: Most of template engines do not suit me initially, among them Smarty , Quicky and all the like, the reason is that it seems to me that template should eliminate the use of logic in templates, and not its 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 template engines, xtemplate meets my requirements most of all, but it has a number of shortcomings that annoy me, for example, that all pages should be framed in blocks, or that it interprets templates, but does not compile, thanks to which it boasts speed can not. And finally - I decided to write a template engine so that there are no problems with adding functionality, and also that it is compatible with the native template engine that I 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 it a lot more than something like:
  1. $ thl -> assign ( 'assigned_var' , 'abc' ) ;
At one point, I realized that it was easier to write my template engine than to search for the one that suits me. And, I think, it turned out to be right, because the case was spent several evenings.
Generally speaking, the process seemed to me quite interesting, and there were many points that I would like to discuss with the community.

I'll start by describing the syntax:


1) Variables:

It's all as usual, except that no "{$"
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 of the type {foreach name = my from = array ('One', 'Two', 'Three') key = "i" item = "text"}
Like xtpl, that's just slightly automated, namely, to parse a block (they also say spread), you just need to pass an array of 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. <! - begin: block_name ->
  2. { block_name. num }
  3. <! - end: block_name ->
  1. $ tpl -> words [ 'block' ] = array (
  2. O => array ( 'word' => 'A' ) ,
  3. 1 => array ( 'word' => 'B' ) ,
  4. 2 => array ( 'word' => 'C' ) ,
  5. ) ;
  1. <! - begin: words.block ->
  2. { words. block . word }
  3. <! - end: words.block ->
To display a block variable inside - you need to name it {block_name.variable_name}
This allows access to both block variables and external variables from the inside.
A block can be absolutely any variable, for example, in the second example, a block is built on the block element of the words array
The same block can be inside another block, for example, 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. <table>
  2. <! - begin: table ->
  3. <tr>
  4. <! - begin: table.row ->
  5. <td> { table. row . num } </ td>
  6. <! - end: table.row ->
  7. </ tr>
  8. <! - end: table ->
  9. </ table>

3) Checks:

In fact, a kind of blocks. It is necessary to, on the basis of some variable, either to show what is inside or not. It will become clearer by example:
Business logicTemplate
  1. $ tpl -> f_text = true ;
  1. <! - if: f_text ->
  2. For now f_text == true we will see this text.
  3. <! - end: f_text ->
  1. $ tpl -> f_text = false ;
  1. <! - if: f_text ->
  2. Here you can write anything, because the customer will not see
  3. <! - end: f_text ->

4) Functions:

This is rather 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 } text to make BIG { / up }
I pay attention that functions should be added to the template class.
At the moment, the functions work with only one parameter, and I think how to expand the number of parameters - like this:
  1. { func ( param2, param3 ) } param1 { / func }
, or so:
  1. { func } param1 | param2 | param3 { / func }
or something else. While inclined to the first option, it is easier to implement!

Now about the principles:


I decided to split the template engine into two parts:

1) The template engine itself (as compact as possible, all the most necessary)
2) Compiler (and here is everything else)
This is necessary to improve performance, because it does not make any sense in the 8 kb compiler code, if the template has already been compiled and has not changed since.
')

Thinking a lot made the process of inclusion inside the templates:

At first glance, the moment may seem trivial, but it is not. Generally speaking, the Incluses had to be divided into two parts - static and dynamic. Static includ is a normal includ, for example
  1. <! - include: some_page.html ->
Such an insert will be processed as follows: the code from some_page.html will be inserted in its place, the modification time of the compiled template file will be 1 second longer than the template itself, and from this the template engine will know that you need to connect a special file also created by the compiler added the following line:
  1. if ( filemtime ( './some_page.html' ) ! = 1237369507 ) $ needCompile = true ;
Thus, when changing this file - the entire template will be recompiled.
Why is it necessary, why not just insert the insert? And what if you need to display a block of 1000 lines, inside of which, for convenience, an insert will be inserted? Then such a focus will greatly help performance!
Now about another type of inclodes - dynamic. This miracle looks in my template engine like this:
  1. <! - include: {page_name} .html ->
That is, we do not include any pre-specified file, but take its name or part of the name from a variable! Sometimes it can be very convenient, but the old way with this approach is no longer a ride, because it is necessary that when a change in the business logic of a variable occurs, another file is included, so this construct will be compiled into the following code:
  1. <? php $ this -> render ( '' . $ this -> page_name . '.html' ) ; ?>
I note that at the moment such an incloud will not work inside the block, that is, it will work, but inside the connected file the block variables will not be available, but I think it is not very scary, because unlike xtpl, I only need blocks for cyclic output array.

Php code inside:

You can safely use it, and thought well, but decided not to prohibit php code in templates.
I understand, it will cause a lot of controversy, but I think that there is no point in banning php, because 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 deciding what the syntax would be:

1) Minimum of logic, all logic is business logic
2) Everything is as natural as possible.
3) Less code
4) Maximum capacity

The code for the template itself:

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

As you can see, not a lot, but fast!
At first glance it may seem that this autochange:
  1. $ tplName = 'tpl_' . str_replace ( array ( '/' , '\\' ) , '.' , $ path ) . '.php' ;
It works quite a long time and it is better to use a hash, but I tested it, the hash works longer.

Code Comparison:

I decided in a convenient way to bring the code listings in different template engines doing the same thing so that we can quickly compare the readability and convenience of the 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 'path_to_the_the_ template of the organizer / tpl.class.php' ;
  2. $ tpl = new tpl ( 'path_to_folder_ with_shablonami' , 'path_to_folder_s_key_shym' ) ;
Do not forget to give the right folder with the cache!

Download:

While the template engine is here to download , while this is just a Beta version, so you should not test for serious projects, I just wanted to hear comments and ideas on this topic!
If the experiment succeeds and such a hybrid of the native and the usual template maker will be needed by someone, surely, I will develop it. By the way, most likely it will be called "LL".
For bugs, please unsubscribe to oleg <dog> emby.ru

Conclusion:

In conclusion, I will not make loud statements, like "In some cases, this template engine is faster than php native." We all understand that in some cases the Belarus tractor may be faster than the new Porshe Panamera , in any case, the template engine will be slower, at least because it needs to compare the dates of the template change and its compiled version, and these are two unnecessary references to the file system. With respect to optimizations, no one bothers to optimize and native code.
Of course, like all template engines, mine runs slower than native php, but just to a small extent, I cite the test results as proof:
Comparing the performance of template engines
All tests were carried out several times in order to make sure that the UFO did not affect the results. If that put them here .

Source: https://habr.com/ru/post/55207/


All Articles