-
Notifications
You must be signed in to change notification settings - Fork 42
Basics
The "view" component of your application will be built by templates and views ("views" are also referred as template controllers. Let's start with the following example:
$View = new Blitz();
$View->load('Where is the {{ $what }}, Lebowski?');
$View->display(array('what' => 'money'));
This code produces the string 'Where is the money, Lebowski?'. Here we have an anonymous template loaded as string and a template controller $View
.
Normally you'll load templates from files:
$View = new Blitz('some.tpl');
But load()
method is very usefull to write tests quickly - no need to create separate template file.
Template is an HTML file with some very simple syntax. No complex code mixed with HTML. Controller is a template logic master, an instance of Blitz class which operates template from opening to the final result. Template controller is not your application controller which stands for "C" in MVC. No HTML code inside template controller is needed.
The reason why Blitz objects are called "template controllers" is simple. From the very early days template language in Blitz was designed to be as simple ("non-programming") as possible. For example, there is still no for
or foreach
statement in Blitz. This surely doesn't mean you can't do any looping :) But you have to loop from your PHP-code, this is called "passive" templates (in Blitz you can do a lot of "active" templating as well - conditions, callbacks, plugins - but loops like foreach
, complex expressions, all these "programming" statements are still under the law).
To make a loop you need to use blocks in your templates. Block, or context, is a part of template marked by {{ BEGIN }}
and {{ END }}
tags. Blocks can be populated on demand but hidden by default:
$View = new Blitz();
$View->load('hello {{ BEGIN block }} {{ $name }} {{ END }}');
$T->display(array('block' => array('name' => 'Dude')));
This template produces just a "hello " string by default and the block is hidden. When you set 'block' key into template variables Blitz executes the block and the result is "hello Dude ". The value of 'block' is a hash. Blitz treats this hash as parameters. If you put an array of hashes - Blitz executes the block for every array element. Blocks are also referred as contexts. An execution of block is called "iteration".
To make the code simpler to read one can put a block name after END statement: {{ END block }}
.
There is a special 'block' method that will perobably make your code a bit easier to read:
$View = new Blitz();
$View->load('hello {{ BEGIN block }} {{ $name }} {{ END block }}');
$View->block('/block', array('name' => 'Dude'));
$View->display();
To make lists you just need to iterate blocks several times:
foreach (array('Dude', 'Donny', 'Sobchak') as $i_name) {
$T->block('/block', array('name' => $i_name);
}
Being placed instead of a single block call in the previous example this will output
"Hello Dude Donny Sobchak "
Note the number of spaces between names. This is not a mistake, everything is absolutely correct: for "Dude" first space comes from hello_
and second space comes from _{{ $name
; "Donny" has two spaces from previous iteration ($name }}_
= Dude_
) and an additional space from _{{ $name
; and finally "Sobchak" has two spaces before for the same reasons as "Donny" and one space after becase of $name }}_
.
Every block is identified by it's path like "/block". A block placed inside another has path /parent/child. Even things like '../../some/path' work.
As you could see there can be two ways of "controlling" template blocks. The first is to use "block" methods (there are also additional methods to set up active context, to iterate the current context and so on). The second is just to set up complex data structures with keys named equal to block names. In previous examples we set up a hash for 'block'. To produce a list you just need this hash to be an array of hashes:
$View = new Blitz();
$View->load('hello {{ BEGIN block }} {{ $name }} {{ END }}');
$T->display(
array('block' => array(
array('name' => 'Dude'),
array('name' => 'Donny'),
array('name' => 'Sobchak'),
))
);
Let's take a break and think what we have here. Single block iteraton was just like if
. Multiple block iteration was like foreach
. This is all quite tricky for the first time, but if you have "math" mind - this all becomes clear and simple very soon. Note that you don't need to add any language constructs into the template. What you do - just play with template controller, iterating template blocks.
Honestly, using blocks as if
substitution is crazy :) See what you have to do if you just need a simple comma separated list in the previous example. At first you have to add a special separator block:
hello {{ BEGIN block }}{{ BEGIN comma }},{{ END }} {{ $name }} {{ END }}
then you have to add this code to iterate this when needed:
$need_comma = FALSE;
foreach (array('Dude', 'Sobchak', 'Donny') as $i_name) {
if ($need_comma) {
$T->block('/block/comma');
} else
$need_comma = TRUE;
}
$T->block('/block', array('name' => $i_name);
}
When your logic is simple you can use a very basic if
statement to make things much easier:
hello {{ BEGIN block }}{{ if($_first,'',',') }} {{ $name }} {{ END }}.
And the code remains as simple as:
foreach (array('Dude', 'Donny', 'Sobchak') as $i_name) {
$T->block('/block', array('name' => $i_name);
}
You can use any variable name instead of $_first
but then you need to set this variable manually from controller. $_first
is a predefined variable, equal to 1 when it's the first block iteration and 0 otherwise.
Predefined loop variables are:
-
$_first
— 1 if the current iteration is the first, 0 otherwise -
$_last
— 1 if the current iteration is the last, 0 otherwise -
$_total
— number of context iterations -
$_num
— current number starting with 1 -
$_even
— 1 if the current iteration value is even, 0 otherwise -
$_odd
— 1 if the current iteration value is odd, 0 otherwise -
$_top
— first-in-the-list iteration item -
$_parent
— parent iteration item -
$_num
— current iteration item
Here we used a short form of if
as a "method". You can use condition blocks as well:
hello {{ BEGIN block }} {{ UNLESS $_first }}, {{ END }} {{ $name }} {{ END }}.
The full version of IF/UNLESS
block is a IF(UNLESS)/ELSEIF/.../ELSEIF/ELSE/END
set. UNLESS
means "if not" condition. Expressions are not currently supported, but simple expression support is included into the development plan.
Let's finally take a complex list example
$body = <<<BODY
{{ BEGIN list }}
==================================================
list #{{ \$_num }}, x = {{ \$x }}
{{ UNLESS sublist }}
empty
{{ ELSE }}
--------------------------------------------------
{{ BEGIN sublist }}
row #{{ \$_num; }} v = {{ \$v }}, x = {{ \$x }}
{{ END }}
{{ END }}
{{ END }}
BODY;
$T = new Blitz();
$T->load($body);
$data = array(
'list' => array(
0 => array(
'x' => 'first'
),
1 => array(
'x' => 'second',
'sublist' => array(
0 => array('v' => 'a'),
1 => array('v' => 'b'),
)
)
)
);
$T->display($data);
This code will produce:
==================================================
list #1, x = first
empty
==================================================
list #2, x = second
--------------------------------------------------
row #1 v = a, x =
row #2 v = b, x =
See that 'x' value is empty in a 'sublist' context. For the performance reasons Blitz checks only current iteration parameters and this hash doesn't have any 'x' key. To have variable inside block you have three options:
- pass it via params manually for every context iteration
- pass it as global once
- force Blitz to make "upper" lookups when parameter was not found
Globals are set by setGlobals()
method:
// body remains the same
...
$T = new Blitz();
$T->load($body);
// data remains the same
...
$T->setGlobals(array('x' => 'global x'));
$T->display($data);
This code produces:
==================================================
list #1, x = first
empty
==================================================
list #2, x = second
--------------------------------------------------
row #1 v = a, x = global x
row #2 v = b, x = global x
Now 'x' is not empty and filled with global value. But usually we need to pass variable from parent iteration to children. This is switched off by default but you can enable this by blitz.scope_lookup_limit
setting:
ini_set(blitz.scope_lookup_limit
, 1);
This setting defines the depth of these lookups: 1 means that only one parent lookup in parent params will be proceed when variable is not found in child params. Adding this set before display we get:
==================================================
list #1, x = first
empty
==================================================
list #2, x = second
--------------------------------------------------
row #1 v = a, x = second
row #2 v = b, x = second
These lookups were added in 0.7.5 version and are not supported in previous versions.Lookups are proceed only when variable can't be found. This means that if you have a lot of missing variables and your blitz.scope_lookup_limit
value is high - you will have a lot of useless hash lookups resulting in useless CPU usage. Be carefull playing with this setting.
In previous section you could see an example of so called "internal method" - if()
. These are statements like function calls in the template code. Blitz has several internal methods like this - and one of them is include()
. Just say: {{ include("some.tpl") }}
or {{ include($file) }}
- that's it. Works same way as PHP itself. How does variable scope work with include? Right the same way as if the include statement was replaced by the content from the included file. Let's see the example. Included template represents the list of names:
Cast: {{ BEGIN cast }}{{ UNLESS $_first }}, {{ END }}{{ $name }}{{ END }}
The script sets iteration data structure for the blocks from the included template
$T = new Blitz();
$T->load('{{ include("include_ctx.tpl") }}');
$data = array(
array('name' => 'Jeff Bridges'),
array('name' => 'John Goodman'),
array('name' => 'Julianne Moore')
array('name' => 'Steve Buscemi')
);
$T->set(array('cast' => $data));
$T->display();
This result is:
Cast: Jeff Bridges, John Goodman, Julianne Moore, Steve Buscemi
Everything is very simple. There's one tricky thing with includes though. This affects cases when you iterate included blocks using block()
or iterate()
methods. In this case you provide context path and Blitz checks internal template structure if this path is correct. But Blitz checks parent template, and includes are processed on the execution stage. This all happens for performance reasons. Include could be just put into block and not executed at all. In the worst case included file name could be passed throught parameters and Blitz just can't know included block names anyway. So at the check stage Blitz can't see blocks from included template and doesn't iterate anything. Unfortunately this happens silently with no warning. This gonna be redesigned - but we have this still. To fix this just use additional third parameter in block()
or iterate()
, which means "just do what I say and don't check anything". See the following example. We use the same template as in prevoius example:
Cast: {{ BEGIN cast }}{{ UNLESS $_first }}, {{ END }}{{ $name }}{{ END }}
But now we use block()
method
$T = new Blitz();
$T->load('{{ include("include_ctx.tpl") }}');
$list_names = array('Jeff Bridges', 'John Goodman', 'Julianne Moore', 'Steve Buscemi');
foreach ($list_names as $i_name) {
$T->block('/cast', array('name' => $i_name), TRUE);
}
$T->display();
With third parameter of block()
we have the same result:
Cast: Jeff Bridges, John Goodman, Julianne Moore, Steve Buscemi
Without TRUE third parameter we'll have no iterations:
Cast:
Internal representation of a template stage in Blitz is just a complex data structure with key names equal to variable or block names. This data is saved in template internally and can be dumped using method getIterations()
when debugging. In the next example we iterate blocks sequentially using block()
method, set some additional data with set()
method, get the iteration data with getIterations()
method, clean all the iterations and display the template using previously dumpled data:
$T = new Blitz();
$T->load('{{ include("test.tpl") }}');
$list_names = array('Jeff Bridges', 'John Goodman', 'Julianne Moore', 'Steve Buscemi');
foreach ($list_names as $i_name) {
$T->block('/cast', array('name' => $i_name), TRUE);
}
$T->set(array('film' => 'The Big Lebowski'));
$data = $T->getIterations();
var_dump($data);
$T->clean();
var_dump($T->getIterations());
$T->display($data);
The result is:
array(1) {
[0]=>
array(2) {
["cast"]=>
array(4) {
[0]=>
array(1) {
["name"]=>
string(12) "Jeff Bridges"
}
[1]=>
array(1) {
["name"]=>
string(12) "John Goodman"
}
[2]=>
array(1) {
["name"]=>
string(14) "Julianne Moore"
}
[3]=>
array(1) {
["name"]=>
string(13) "Steve Buscemi"
}
}
["film"]=>
string(16) "The Big Lebowski"
}
}
array(0) {
}
The Big Lebowski cast: Jeff Bridges, John Goodman, Julianne Moore, Steve Buscemi
Sometimes you have correct iterations but your code doesn't work properly still. Use getStruct()
/dumpStruct()
methods to check if there is everything OK with your template. See View API for examples.
When working with large templates you oftenly want to get a part of template. This is done by fetch()
method:
$body =<<<BODY
{{ BEGIN test }}
Hello, {{ $name }}!
{{ END }}
BODY;
$T = new Blitz();
$T->load($body);
echo $T->fetch('test', array('name' => 'world!'));
This code will output the contents of executed 'test' block:
Hello, world!
Fetch can be used in template body as well. Usually it's just a trick, but this works:
$body =<<<BODY
{{ fetch('/test') }}
{{ BEGIN test }}Hello, world!{{ END }}
BODY;
$T = new Blitz();
$T->load($body);
$T->display();
This code outputs "Hello, world!" as well.
Basic internal methods are: if()
, include()
, date()
and escape()
. Two first methods were mentioned above, and two last are new. Let's cover them briefly.
Method date(format, argument)
used to format date/time values just from your template. It works as follows: when argument is integer, date()
treats it as UNIX timestamp. Otherwise it's parsed with internal PHP function php_parse_date
which recognize a lot of date formats. When ARG is omitted - the current time is used. Format string has the same conversion specifiers as PHP function strftime
.
Method escape(string)
is equal to htmlspecialchars(string, ENT_QUOTES)
. You can use additional second parameter to specify quoting style which is passed as string, named as the corresponding PHP constant: escape(string, "ENT_QUOTES")
or escape(string, "ENT_NOQUOTES")
etc. Blitz supports all three quote styles: "ENT_COMPAT", "ENT_QUOTES" and "ENT_NOQUOTES".