Volt: Template Engine


    Volt is an ultra-fast and designer friendly templating engine written in C for PHP. It offers a set of helpers to write views easily. Volt is highly integrated with other components of Phalcon, but can be used as a stand alone component in your application.

    Volt is inspired by Jinja, originally created by .

    Many developers will be in familiar territory, using the same syntax they have been using with similar template engines. Volt’s syntax and features have been enhanced with more elements and of course with the performance that developers have been accustomed to, while working with Phalcon.

    Syntax

    Volt views are compiled to pure PHP code, so basically they save the effort of writing PHP code manually:

    compared to:

    1. <div class='row'>
    2. <div>
    3. ID: <?php echo $invoice->inv_id; ?>
    4. </div>
    5. <div>
    6. <?php if (1 === $invoice->inv_status_flag) { ?>
    7. Paid
    8. <?php } else { ?>
    9. Unpaid
    10. <?php } ?>
    11. </div>
    12. <div>
    13. <?php echo $invoice->inv_description; ?>
    14. </div>
    15. <div>
    16. <?php echo $invoice->total; ?>
    17. </div>
    18. </div>
    19. <?php } ?>

    Constructor

    1. public function __construct(
    2. ViewBaseInterface $view,
    3. DiInterface $container = null
    4. )

    The constructor accepts a Phalcon\Mvc\View or any component that implements the ViewBaseInterface, and a DI container.

    Methods

    There are several methods available in Volt. In most cases, only a handful of them are used in modern day applications.

    1. callMacro(string $name, array $arguments = []): mixed

    Checks if a macro is defined and calls it

    1. convertEncoding(string $text, string $from, string $to): string

    Performs a string conversion

    1. getCompiler(): Compiler

    Returns the Volt’s compiler

    1. getContent(): string

    Returns cached output on another view stage

    1. getOptions(): array

    Return Volt’s options

    1. getView(): ViewBaseInterface

    Returns the view component related to the adapter

    1. isIncluded(mixed $needle, mixed $haystack): bool

    Checks if the needle is included in the haystack

    1. length(mixed $item): int

    Length filter. If an array/object is passed a count is performed otherwise a strlen/mb_strlen

    1. partial(string $partialPath, mixed $params = null): string

    Renders a partial inside another view

    1. render(string $templatePath, mixed $params, bool $mustClean = false)

    Renders a view using the template engine

    1. setOptions(array $options)

    Set Volt’s options

    1. slice(mixed $value, int $start = 0, mixed $end = null)

    Extracts a slice from a string/array/traversable object value

    1. sort(array $value): array

    Sorts an array

    Activation

    As with other templating engines, you may register Volt in the view component, using a new extension or reusing the standard .phtml:

    1. <?php
    2. use Phalcon\Di\FactoryDefault;
    3. use Phalcon\Di\DiInterface;
    4. use Phalcon\Mvc\ViewBaseInterface;
    5. use Phalcon\Mvc\View;
    6. use Phalcon\Mvc\View\Engine\Volt;
    7. $container = new FactoryDefault();
    8. $container->setShared(
    9. 'voltService',
    10. function (ViewBaseInterface $view, DiInterface $container) {
    11. $volt = new Volt($view, $container);
    12. $volt->setOptions(
    13. [
    14. 'always' => true,
    15. 'extension' => '.php',
    16. 'separator' => '_',
    17. 'stat' => true,
    18. 'path' => appPath('storage/cache/volt/'),
    19. 'prefix' => '-prefix-',
    20. ]
    21. );
    22. return $volt;
    23. }
    24. );
    25. $container->set(
    26. 'view',
    27. function () {
    28. $view = new View();
    29. $view->setViewsDir('../app/views/');
    30. $view->registerEngines(
    31. [
    32. '.volt' => 'voltService',
    33. ]
    34. );
    35. return $view;
    36. }
    37. );

    To use the standard .phtml extension:

    1. <?php
    2. $view->registerEngines(
    3. [
    4. '.phtml' => 'voltService',
    5. ]
    6. );

    You don’t have to specify the Volt Service in the DI; you can also use the Volt engine with the default settings:

    1. <?php
    2. use Phalcon\Mvc\View\Engine\Volt;
    3. $view->registerEngines(
    4. [
    5. '.volt' => Volt::class,
    6. ]
    7. );

    If you do not want to reuse Volt as a service, you can pass an anonymous function to register the engine instead of a service name:

    1. <?php
    2. use Phalcon\Di\FactoryDefault;
    3. use Phalcon\Di\DiInterface;
    4. use Phalcon\Mvc\ViewBaseInterface;
    5. use Phalcon\Mvc\View;
    6. use Phalcon\Mvc\View\Engine\Volt;
    7. $container = new FactoryDefault();
    8. $container->set(
    9. 'view',
    10. function () {
    11. $view = new View();
    12. $view->setViewsDir('../app/views/');
    13. $view->registerEngines(
    14. [
    15. '.volt' => function (
    16. ViewBaseInterface $view,
    17. DiInterface $container
    18. ) {
    19. $volt = new Volt($view, $container);
    20. $volt->setOptions(
    21. [
    22. 'always' => true,
    23. 'extension' => '.php',
    24. 'separator' => '_',
    25. 'stat' => true,
    26. 'path' => appPath('storage/cache/volt/'),
    27. 'prefix' => '-prefix-',
    28. ]
    29. );
    30. return $volt;
    31. }
    32. ]
    33. );
    34. return $view;
    35. }
    36. );

    The following options are available in Volt:

    The compilation path is generated according to the options above. You however, have total freedom in defining the compilation path as an anonymous function, including the logic used to generate it. The anonymous function receives the relative path to the template in the predefined views directory.

    Appending extensions

    Append the .php extension to the template path, leaving the compiled templates in the same directory:

    1. <?php
    2. $volt->setOptions(
    3. [
    4. 'path' => function ($templatePath) {
    5. return $templatePath . '.php';
    6. }
    7. ]
    8. );

    Different directories

    The following example will create the same structure in a different directory

    1. <?php
    2. $volt->setOptions(
    3. [
    4. 'path' => function (string $templatePath) {
    5. $dirName = dirname($templatePath);
    6. if (true !== is_dir('cache/' . $dirName)) {
    7. mkdir(
    8. 'cache/' . $dirName,
    9. 0777,
    10. true
    11. );
    12. }
    13. return 'cache/' . $dirName . '/' . $templatePath . '.php';
    14. }
    15. ]
    16. );

    Usage

    Volt uses specific delimiters for its syntax. {% … %} is used to execute statements such as for-loops or assign values and {{ … }} prints the result of an expression to the template. The view files can also contain PHP and HTML should you choose to.

    Below is a sample template that illustrates a few basics:

    1. {# app/views/posts/show.phtml #}
    2. <!DOCTYPE html>
    3. <html>
    4. <head>
    5. <title>{{ title }} - An example blog</title>
    6. </head>
    7. <body>
    8. {% if true === showNavigation %}
    9. <ul id='navigation'>
    10. {% for item in menu %}
    11. <li>
    12. <a href='{{ item.href }}'>
    13. {{ item.caption }}
    14. </a>
    15. </li>
    16. {% endfor %}
    17. </ul>
    18. {% endif %}
    19. <h1>{{ post.title }}</h1>
    20. <div class='content'>
    21. {{ post.content }}
    22. </div>
    23. </body>
    24. </html>

    Using Phalcon\Mvc\View you can pass variables from the controller to the views. In the above example, four variables were passed to the view: showNavigation, menu, title and post:

    1. <?php
    2. use MyApp\Models\Menu;
    3. use MyApp\Models\Post;
    4. use Phalcon\Mvc\Controller;
    5. use Phalcon\Mvc\View;
    6. /**
    7. * @property View $view
    8. */
    9. class PostsController extends Controller
    10. {
    11. public function showAction()
    12. {
    13. $post = Post::findFirst();
    14. $menu = Menu::findFirst();
    15. $this->view->showNavigation = true;
    16. $this->view->menu = $menu;
    17. $this->view->title = $post->title;
    18. $this->view->post = $post;
    19. // Or...
    20. $this->view->setVar('showNavigation', true);
    21. $this->view->setVar('menu', $menu);
    22. $this->view->setVar('title', $post->title);
    23. }
    24. }

    If you are using you will need to change the interpolators in Vue itself:

    1. new Vue(
    2. {
    3. el: '#app',
    4. data: data,
    5. delimiters: ["<%","%>"]
    6. }
    7. );

    Angular

    If you are using you can set the interpolators as follows:

    1. var myApp = angular.module('myApp', []);
    2. myApp.config(
    3. function ($interpolateProvider) {
    4. $interpolateProvider.startSymbol('<%');
    5. $interpolateProvider.endSymbol('%>');
    6. }
    7. );

    Variables

    Object variables may have attributes which can be accessed using the syntax: foo.bar. If you are passing arrays, you have to use the square bracket syntax: foo['bar']

    1. {{ post.title }} {# for $post->title #}
    2. {{ post['title'] }} {# for $post['title'] #}

    Filters

    Variables can be formatted or modified using filters. The pipe operator | is used to apply filters to variables:

    1. {{ post.title | e }}
    2. {{ post.content | striptags }}
    3. {{ name | capitalize | trim }}

    The available built-in filters are:

    FilterDescription
    absApplies the abs PHP function to a value.
    capitalizeCapitalizes a string by applying the PHP function to the value
    convert_encodingConverts a string from one charset to another
    defaultSets a default value in case the evaluated expression is empty, not set or evaluates to falsy value
    eApplies Phalcon\Escaper->escapeHtml() to the value
    escapeApplies to the value
    escape_attrApplies Phalcon\Escaper->escapeHtmlAttr() to the value
    escape_cssApplies to the value
    escape_jsApplies Phalcon\Escaper->escapeJs() to the value
    formatFormats a string using
    json_encodeConverts a value into its JSON representation
    json_decodeConverts a value from its representation to a PHP representation
    joinJoins the array parts using a separator join
    keysReturns the array keys using
    left_trimApplies the ltrim PHP function to the value. Removing extra spaces
    lengthCounts the string length or how many items are in an array or object, equivalent of
    lowerChange the case of a string to lowercase
    nl2brChanges newlines \n by line breaks (<br />). Uses the PHP function nl2br
    right_trimApplies the PHP function to the value. Removing extra spaces
    sortSorts an array using the PHP function asort
    stripslashesApplies the PHP function to the value. Removing escaped quotes
    striptagsApplies the striptags PHP function to the value. Removing HTML tags
    trimApplies the PHP function to the value. Removing extra spaces
    upperApplies the strtoupper PHP function to the value.
    url_encodeApplies the PHP function to the value

    Examples

    1. {# e or escape filter #}
    2. {{ '<h1>Hello<h1>'|e }}
    3. {{ '<h1>Hello<h1>'|escape }}
    4. {# trim filter #}
    5. {{ ' hello '|trim }}
    6. {# striptags filter #}
    7. {{ '<h1>Hello<h1>'|striptags }}
    8. {# slashes filter #}
    9. {{ ''this is a string''|slashes }}
    10. {# stripslashes filter #}
    11. {{ '\'this is a string\''|stripslashes }}
    12. {# capitalize filter #}
    13. {{ 'hello'|capitalize }}
    14. {# lower filter #}
    15. {{ 'HELLO'|lower }}
    16. {# upper filter #}
    17. {{ 'hello'|upper }}
    18. {# length filter #}
    19. {{ 'invoices'|length }}
    20. {{ [1, 2, 3]|length }}
    21. {# nl2br filter #}
    22. {{ 'some\ntext'|nl2br }}
    23. {# sort filter #}
    24. {% set sorted = [3, 1, 2]|sort %}
    25. {# keys filter #}
    26. {% set keys = ['first': 1, 'second': 2, 'third': 3]|keys %}
    27. {# join filter #}
    28. {% set joined = 'a'..'z'|join(',') %}
    29. {# format filter #}
    30. {{ 'My real name is %s'|format(name) }}
    31. {# json_encode filter #}
    32. {% set encoded = invoices|json_encode %}
    33. {# json_decode filter #}
    34. {% set decoded = '{'one':1,'two':2,'three':3}'|json_decode %}
    35. {# url_encode filter #}
    36. {{ post.permanent_link|url_encode }}
    37. {# convert_encoding filter #}
    38. {{ 'désolé'|convert_encoding('utf8', 'latin1') }}

    Comments may also be added to a template using the {# … #} delimiters. All text inside them is just ignored in the final output:

    1. {# note: this is a comment
    2. {% set price = 100; %}
    3. #}

    Control Structures

    Volt provides a set of basic but powerful control structures for use in templates:

    For

    Loop over each item in a sequence. The following example shows how to traverse a set of invoices and print each title:

    1. <h1>Invoices</h1>
    2. <ul>
    3. {% for invoice in invoices %}
    4. <li>
    5. {{ invoice.inv_title | e }}
    6. </li>
    7. {% endfor %}
    8. </ul>

    You can get the element keys as in the PHP counterpart using the following syntax:

    1. {% set numbers = ['one': 1, 'two': 2, 'three': 3] %}
    2. {% for name, value in numbers %}
    3. Name: {{ name }} Value: {{ value }} <br />
    4. {% endfor %}

    An if evaluation can be optionally set:

    1. {% set numbers = ['one': 1, 'two': 2, 'three': 3] %}
    2. {% for value in numbers if value < 2 %}
    3. Value: {{ value }} <br />
    4. {% endfor %}
    5. {% for name, value in numbers if name !== 'two' %}
    6. Name: {{ name }} Value: {{ value }} <br />
    7. {% endfor %}

    If an else is defined inside the for, it will be executed if the expression in the iterator result in zero iterations:

    1. <h1>Invoices</h1>
    2. {% for invoice in invoices %}
    3. Invoice: {{ invoice.inv_number | e }} - {{ invoice.inv_title | e }} <br />
    4. {% else %}
    5. There are no invoices to show
    6. {% endfor %}

    Alternative syntax:

    1. <h1>Invoices</h1>
    2. {% for invoice in invoices %}
    3. Invoice: {{ invoice.inv_number | e }} - {{ invoice.inv_title | e }} <br />
    4. {% elsefor %}
    5. There are no invoices to show
    6. {% endfor %}

    Loops

    The break and continue statements can be used to exit from a loop or force an iteration in the current block:

    1. {# skip the even invoices #}
    2. {% for index, invoice in invoices %}
    3. {% if index is even %}
    4. {% continue %}
    5. {% endif %}
    6. ...
    7. {% endfor %}
    1. {# exit the foreach on the first even invoice #}
    2. {% for index, invoice in invoices %}
    3. {% if index is even %}
    4. {% break %}
    5. {% endif %}
    6. ...
    7. {% endfor %}

    If

    As PHP, an if statement checks if an expression is evaluated as true or false:

    1. <h1>Paid Invoices</h1>
    2. <ul>
    3. {% for invoice in invoices %}
    4. {% if invoice.inv_paid_flag === 1 %}
    5. <li>{{ invoice.inv_title | e }}</li>
    6. {% endif %}
    7. {% endfor %}
    8. </ul>

    The else clause is also supported:

    1. <h1>Invoices</h1>
    2. <ul>
    3. {% for invoice in invoices %}
    4. {% if invoice.inv_paid_flag === 1 %}
    5. <li>{{ invoice.inv_title | e }}</li>
    6. {% else %}
    7. <li>{{ invoice.inv_title | e }} [NOT PAID]</li>
    8. {% endif %}
    9. {% endfor %}
    10. </ul>

    The elseif control flow structure can be used together with if to emulate a switch block:

    1. {% if invoice.inv_paid_flag === constant('MyApp\Constants\Status::PAID') %}
    2. Invoice is paid
    3. {% elseif invoice.inv_paid_flag === 2 %}
    4. Invoice is not paid
    5. {% else %}
    6. Invoice is paid status is not defined
    7. {% endif %}

    Switch

    An alternative to the if statement is switch, allowing you to create logical execution paths in your application:

    1. {% switch foo %}
    2. {% case 0 %}
    3. {% case 1 %}
    4. {% case 2 %}
    5. "foo" is less than 3 but not negative
    6. {% break %}
    7. {% case 3 %}
    8. "foo" is 3
    9. {% break %}
    10. {% default %}
    11. "foo" is {{ foo }}
    12. {% endswitch %}

    The switch statement executes statement by statement, therefore the break statement is necessary in some cases. Any output (including whitespace) between a switch statement and the first case will result in a syntax error. Empty lines and whitespaces can therefore be cleared to reduce the number of errors .

    case without switch

    1. {% case EXPRESSION %}

    Will throw Fatal error: Uncaught Phalcon\Mvc\View\Exception: Unexpected CASE.

    switch without endswitch

    1. {% switch EXPRESSION %}

    Will throw Fatal error: Uncaught Phalcon\Mvc\View\Exception: Syntax error, unexpected EOF in …, there is a 'switch' block without 'endswitch'.

    default without switch

    1. {% default %}

    Will not throw an error because default is a reserved word for filters like {{ EXPRESSION | default(VALUE) }} but in this case the expression will only output an empty char '' .

    nested

    1. {% switch EXPRESSION %}
    2. {% switch EXPRESSION %}
    3. {% endswitch %}
    4. {% endswitch %}

    Will throw Fatal error: Uncaught Phalcon\Mvc\View\Exception: A nested switch detected. There is no nested switch-case statements support in … on line …

    a switch without an expression

    1. {% switch %}
    2. {% case EXPRESSION %}
    3. {% break %}
    4. {% endswitch %}

    Will throw Fatal error: Uncaught Phalcon\Mvc\View\Exception: Syntax error, unexpected token%}in … on line …

    A special variable is available inside for loops providing you information about

    VariableDescription
    loop.firstTrue if in the first iteration.
    loop.indexThe current iteration of the loop. (1 indexed)
    loop.index0The current iteration of the loop. (0 indexed)
    loop.lengthThe number of items to iterate
    loop.lastTrue if in the last iteration.
    loop.revindexThe number of iterations from the end of the loop (1 indexed)
    loop.revindex0The number of iterations from the end of the loop (0 indexed)

    Example:

    1. {% for invoice in invoices %}
    2. {% if loop.first %}
    3. <table>
    4. <tr>
    5. <th>#</th>
    6. <th>Id</th>
    7. <th>Title</th>
    8. </tr>
    9. {% endif %}
    10. <tr>
    11. <td>{{ loop.index }}</td>
    12. <td>{{ invoice.inv_id }}</td>
    13. <td>{{ invoice.inv_title }}</td>
    14. </tr>
    15. {% if loop.last %}
    16. </table>
    17. {% endif %}
    18. {% endfor %}

    Assignments

    Variables may be changed in a template using the instruction set:

    1. {% set fruits = ['Apple', 'Banana', 'Orange'] %}
    2. {% set title = invoice.inv_title %}

    Multiple assignments are allowed in the same instruction:

    1. {% set fruits = ['Apple', 'Banana', 'Orange'], name = invoice.inv_title, active = true %}

    Additionally, you can use compound assignment operators:

    1. {% set price += 100.00 %}
    2. {% set age *= 5 %}

    The following operators are available:

    OperatorDescription
    =Standard Assignment
    +=Addition assignment
    -=Subtraction assignment
    *=Multiplication assignment
    /=Division assignment

    Expressions

    Volt provides a basic set of expression support, including literals and common operators. A expression can be evaluated and printed using the {{ and }} delimiters:

    1. {{ (1 + 1) * 2 }}

    If an expression needs to be evaluated without be printed the do statement can be used:

    1. {% do (1 + 1) * 2 %}

    Literals

    The following literals are supported:

    Arrays

    You can create arrays by enclosing a list of values in square brackets:

    {# Simple array #}
    {{ ['Apple', 'Banana', 'Orange'] }}
    
    {# Other simple array #}
    {{ ['Apple', 1, 2.5, false, null] }}
    
    {# Multi-Dimensional array #}
    {{ [[1, 2], [3, 4], [5, 6]] }}
    
    {# Hash-style array #}
    {{ ['first': 1, 'second': 4/2, 'third': '3'] }}
    

    Curly braces also can be used to define arrays or hashes:

    {% set myArray = {'Apple', 'Banana', 'Orange'} %}
    {% set myHash  = {'first': 1, 'second': 4/2, 'third': '3'} %}
    

    Math

    You may make calculations in templates using the following operators:

    OperatorDescription
    +Perform an adding operation. {{ 2 + 3 }} returns 5
    -Perform a subtraction operation {{ 2 - 3 }} returns -1
    Perform a multiplication operation {{ 2 3 }} returns 6
    /Perform a division operation {{ 10 / 2 }} returns 5
    %Calculate the remainder of an integer division {{ 10 % 3 }} returns 1

    Comparisons

    The following comparison operators are available:

    OperatorDescription
    ==Check whether both operands are equal
    !=Check whether both operands aren’t equal
    <>Check whether both operands aren’t equal
    >Check whether left operand is greater than right operand
    <Check whether left operand is less than right operand
    <=Check whether left operand is less or equal than right operand
    >=Check whether left operand is greater or equal than right operand
    ===Check whether both operands are identical
    !==Check whether both operands aren’t identical

    Logic

    Logic operators are useful in the if expression evaluation to combine multiple tests:

    OperatorDescription
    orReturn true if the left or right operand is evaluated as true
    andReturn true if both left and right operands are evaluated as true
    notNegates an expression
    ( expr )Parenthesis groups expressions

    Additional operators seen the following operators are available:

    The following example shows how to use operators:

    {% set fruits = ['Apple', 'Banana', 'Orange', 'Kiwi'] %}
    
    {% for index in 0..fruits | length %}
        {% if invoices[index] is defined %}
            {{ 'Name: ' ~ invoices[index] }}
        {% endif %}
    {% endfor %}
    

    Tests

    Tests can be used to test if a variable has a valid expected value. The operator is is used to perform the tests:

    {% set invoices = ['1': 'Apple', '2': 'Banana', '3': 'Orange'] %}
    
    {% for position, name in invoices %}
        {% if position is odd %}
            {{ name }}
        {% endif %}
    {% endfor %}
    

    The following built-in tests are available in Volt:

    TestDescription
    definedChecks if a variable is defined (isset())
    divisiblebyChecks if a value is divisible by other value
    emptyChecks if a variable is empty
    evenChecks if a numeric value is even
    iterableChecks if a value is iterable. Can be traversed by a ‘for’ statement
    numericChecks if value is numeric
    oddChecks if a numeric value is odd
    sameasChecks if a value is identical to other value
    scalarChecks if value is scalar (not an array or object)
    typeChecks if a value is of the specified type

    More examples:

    {% if invoice is defined %}
        The invoice variable is defined
    {% endif %}
    
    {% if invoice is empty %}
        The invoice is null or is not defined
    {% endif %}
    
    {% for key, name in [1: 'Apple', 2: 'Banana', 3: 'Orange'] %}
        {% if key is even %}
            {{ name }}
        {% endif %}
    {% endfor %}
    
    {% for key, name in [1: 'Apple', 2: 'Banana', 3: 'Orange'] %}
        {% if key is odd %}
            {{ name }}
        {% endif %}
    {% endfor %}
    
    {% for key, name in [1: 'Apple', 2: 'Banana', 'third': 'Orange'] %}
        {% if key is numeric %}
            {{ name }}
        {% endif %}
    {% endfor %}
    
    {% set invoices = [1: 'Apple', 2: 'Banana'] %}
    {% if invoices is iterable %}
        {% for invoice in invoices %}
            ...
        {% endfor %}
    {% endif %}
    
    {% set world = 'hello' %}
    {% if world is sameas('hello') %}
        {{ 'it's hello' }}
    {% endif %}
    
    {% set external = false %}
    {% if external is type('boolean') %}
        {{ 'external is false or true' }}
    {% endif %}
    

    Macros

    Macros can be used to reuse logic in a template, they act as PHP functions, can receive parameters and return values:

    {# Macro 'display a list of links to related topics' #}
    {%- macro related_bar(related_links) %}
        <ul>
            {%- for link in related_links %}
            <li>
                <a href='{{ url(link.url) }}' 
                   title='{{ link.title|striptags }}'>
                    {{ link.text }}
                </a>
            </li>
            {%- endfor %}
        </ul>
    {%- endmacro %}
    
    {# Print related links #}
    {{ related_bar(links) }}
    
    <div>This is the content</div>
    
    {# Print related links again #}
    {{ related_bar(links) }}
    

    When calling macros, parameters can be passed by name:

    {%- macro error_messages(message, field, type) %}
        <div>
            <span class='error-type'>{{ type }}</span>
            <span class='error-field'>{{ field }}</span>
            <span class='error-message'>{{ message }}</span>
        </div>
    {%- endmacro %}
    
    {# Call the macro #}
    {{ 
        error_messages(
            'type': 'Invalid', 
            'message': 'The name not invalid', 
            'field': 'name'
        ) 
    }}
    

    Macros can return values:

    {%- macro my_input(name, class) %}
        {% return text_field(name, 'class': class) %}
    {%- endmacro %}
    
    {# Call the macro #}
    {{ '<p>' ~ my_input('name', 'input-text') ~ '</p>' }}
    

    And receive optional parameters:

    {%- macro my_input(name, class='input-text') %}
        {% return text_field(name, 'class': class) %}
    {%- endmacro %}
    
    {# Call the macro #}
    {{ '<p>' ~ my_input('name') ~ '</p>' }}
    {{ '<p>' ~ my_input('name', 'input-text') ~ '</p>' }}
    

    Tag Helpers

    Volt is highly integrated with Phalcon\Tag, so it’s easy to use the helpers provided by that component in a Volt template:

    <?php echo Phalcon\Tag::javascriptInclude('js/jquery.js') ?>
    
    <?php echo Phalcon\Tag::form(['products/save', 'method' => 'post']); ?>
    
        <label for='name'>Name</label>
        <?php echo Phalcon\Tag::textField(['name', 'size' => 32]); ?>
    
        <label for='type'>Type</label>
        <?php echo Phalcon\Tag::select(['type', $productTypes, 'using' => ['id', 'name']]); ?>
    
        <?php echo Phalcon\Tag::submitButton('Send'); ?>
    {{ end_form() }}
    

    To call a helper, you only need to call an uncamelized version of the method:

    MethodVolt function
    Phalcon\Tag::checkFieldcheck_field
    Phalcon\Tag::dateFielddate_field
    Phalcon\Tag::emailFieldemail_field
    Phalcon\Tag::endFormend_form
    Phalcon\Tag::fileFieldfile_field
    Phalcon\Tag::formform
    Phalcon\Tag::friendlyTitlefriendly_title
    Phalcon\Tag::getTitleget_title
    Phalcon\Tag::hiddenFieldhidden_field
    Phalcon\Tag::imageimage
    Phalcon\Tag::javascriptIncludejavascript_include
    Phalcon\Tag::linkTolink_to
    Phalcon\Tag::numericFieldnumeric_field
    Phalcon\Tag::passwordFieldpassword_field
    Phalcon\Tag::radioFieldradio_field
    Phalcon\Tag::selectselect
    Phalcon\Tag::selectStaticselect_static
    Phalcon\Tag::stylesheetLinkstylesheet_link
    Phalcon\Tag::submitButtonsubmit_button
    Phalcon\Tag::textAreatext_area
    Phalcon\Tag::textFieldtext_field

    Functions

    The following built-in functions are available in Volt:

    NameDescription
    constantReads a PHP constant
    contentIncludes the content produced in a previous rendering stage
    dateCalls the PHP function with the same name
    dumpCalls the PHP function var_dump()
    get_contentSame as content
    partialDynamically loads a partial view in the current template
    superRender the contents of the parent block
    timeCalls the PHP function with the same name
    urlGenerate a URL using the ‘url’ service
    versionReturns the current version of the framework

    Also, Volt is integrated with , you can play with the view hierarchy and include partials as well:

    {{ content() }}
    
    <div id='footer'>
        {{ partial('partials/footer') }}
        {{ partial('partials/footer', ['links': links]) }}
    </div>
    

    A partial is included in runtime, Volt also provides include, this compiles the content of a view and returns its contents as part of the view which was included:

    <div id='footer'>
        {% include 'partials/footer' %}
        {% include 'partials/footer' with ['links': links] %}
    </div>
    

    Include

    include has a special behavior that will help us improve performance a bit when using Volt, if you specify the extension when including the file and it exists when the template is compiled, Volt can inline the contents of the template in the parent template where it’s included. Templates aren’t inlined if the include have variables passed with with:

    <div id='footer'>
        {% include 'partials/footer.volt' %}
    </div>
    

    Partial vs Include

    Keep the following points in mind when choosing to use the partial function or include:

    Inheritance

    With template inheritance you can create base templates that can be extended by others templates allowing to reuse code. A base template define blocks than can be overridden by a child template. Let’s pretend that we have the following base template:

    {# templates/base.volt #}
    <!DOCTYPE html>
    <html>
        <head>
            {% block head %}
                <link rel='stylesheet' href='style.css' />
            {% endblock %}
    
            <title>{% block title %}{% endblock %} - My Webpage</title>
        </head>
    
        <body>
            <div id='content'>{% block content %}{% endblock %}</div>
    
            <div id='footer'>
                {% block footer %}
                    &copy; Copyright 2012-present. 
                    All rights reserved.
                {% endblock %}
            </div>
        </body>
    </html>
    

    From other template we could extend the base template replacing the blocks:

    {% extends 'templates/base.volt' %}
    
    {% block title %}Index{% endblock %}
    
    {% block head %}<style type='text/css'>.important { color: #336699; }</style>{% endblock %}
    
    {% block content %}
        <h1>Index</h1>
        <p class='important'>Welcome on my awesome homepage.</p>
    {% endblock %}
    

    Not all blocks must be replaced at a child template, only those that are needed. The final output produced will be the following:

    <!DOCTYPE html>
    <html>
        <head>
            <style type='text/css'>.important { color: #336699; }</style>
    
            <title>Index - My Webpage</title>
        </head>
    
        <body>
            <div id='content'>
                <h1>Index</h1>
                <p class='important'>Welcome on my awesome homepage.</p>
            </div>
    
            <div id='footer'>
                &copy; Copyright 2012-present. 
                All rights reserved.
            </div>
        </body>
    </html>
    

    Multiple Inheritance

    Extended templates can extend other templates. The following example illustrates this:

    {# main.volt #}
    <!DOCTYPE html>
    <html>
        <head>
            <title>Title</title>
        </head>
    
        <body>
            {% block content %}{% endblock %}
        </body>
    </html>
    

    Template layout.volt extends main.volt

    {# layout.volt #}
    {% extends 'main.volt' %}
    
    {% block content %}
    
        <h1>Table of contents</h1>
    
    {% endblock %}
    

    Finally a view that extends layout.volt:

    {# index.volt #}
    {% extends 'layout.volt' %}
    
    {% block content %}
    
        {{ super() }}
    
        <ul>
            <li>Some option</li>
            <li>Some other option</li>
        </ul>
    
    {% endblock %}
    

    Rendering index.volt produces:

    <!DOCTYPE html>
    <html>
        <head>
            <title>Title</title>
        </head>
    
        <body>
    
            <h1>Table of contents</h1>
    
            <ul>
                <li>Some option</li>
                <li>Some other option</li>
            </ul>
    
        </body>
    </html>
    

    Note the call to the function super(). With that function it is possible to render the contents of the parent block. As partials, the path set to extends is a relative path under the current views directory (i.e. app/views/).

    By default, and for performance reasons, Volt only checks for changes in the children templates to know when to re-compile to plain PHP again, so it is recommended initialize Volt with the option 'always' => true. Thus, the templates are compiled always taking into account changes in the parent templates.

    Autoescape mode

    You can enable auto-escaping of all variables printed in a block using the autoescape mode:

    Manually escaped: {{ invoice.inv_title|e }}
    
    {% autoescape true %}
        Autoescaped: {{ invoice.inv_title }}
        {% autoescape false %}
            No Autoescaped: {{ invoice.inv_title }}
        {% endautoescape %}
    {% endautoescape %}
    

    Extending Volt

    Unlike other template engines, Volt itself is not required to run the compiled templates. Once the templates are compiled there is no dependence on Volt. With performance independence in mind, Volt only acts as a compiler for PHP templates.

    The Volt compiler allow you to extend it adding more functions, tests or filters to the existing ones.

    Functions

    Functions act as normal PHP functions, a valid string name is required as function name. Functions can be added using two options, returning a simple string or using an anonymous function. Whichever option you use, you must return a valid PHP string expression.

    The following example binds the function name shuffle in Volt to the PHP function str_shuffle:

    <?php
    
    use Phalcon\Mvc\View\Engine\Volt;
    
    $volt = new Volt($view, $container);
    
    $compiler = $volt->getCompiler();
    
    $compiler->addFunction('shuffle', 'str_shuffle');
    

    and in Volt:

    {{ str_suffle('abcdefg') }}
    

    The example below registers the function with an anonymous function. Here we use $resolvedArgs to pass the arguments exactly when calling the method from the view:

    <?php
    
    $compiler->addFunction(
        'widget',
        function ($resolvedArgs, $exprArgs) {
            return 'MyLibrary\Widgets::get(' . $resolvedArgs . ')';
        }
    );
    

    and in Volt:

    {{ widget('param1', 'param2') }}
    

    You can also treat the arguments independently and also check for unresolved parameters. In the example below, we retrieve the first parameter and then check for the existence of a second parameter. If present, we store it, otherwise we use the default 10. Finally we call the str_repeat PHP method on the first and second parameter.

    <?php
    
    $compiler->addFunction(
        'repeat',
        function ($resolvedArgs, $exprArgs) use ($compiler) {
            $firstArgument = $compiler->expression($exprArgs[0]['expr']);
    
            if (isset($exprArgs[1])) {
                $secondArgument = $compiler->expression($exprArgs[1]['expr']);
            } else {
                $secondArgument = '10';
            }
    
            return 'str_repeat(' . $firstArgument . ', ' . $secondArgument . ')';
        }
    );
    

    and in Volt:

    {{ repeat('Apples', 'Oranges') }}
    

    You can also check the availability of functions in your system and call them if present. In the following example we will call mb_stripos if the mbstring extension is present. If present, then mb_stripos will be called, otherwise stripos:

    <?php
    
    $compiler->addFunction(
        'contains_text',
        function ($resolvedArgs, $exprArgs) {
            if (true === function_exists('mb_stripos')) {
                return 'mb_stripos(' . $resolvedArgs . ')';
            } else {
                return 'stripos(' . $resolvedArgs . ')';
            }
        }
    );
    

    You can also override built in functions by using the same name in the defined function. In the example below, we replace the built in Volt function dump() with PHP’s print_r.

    <?php
    
    $compiler->addFunction('dump', 'print_r');
    

    Filters

    A filter has the following form in a template: leftExpr|name(optional-args). Adding new filters is similar as with the functions.

    Add a new filter called hash using the sha1 method:

    <?php
    
    $compiler->addFilter('hash', 'sha1');
    

    Add a new filter called int:

    <?php
    
    $compiler->addFilter(
        'int',
        function ($resolvedArgs, $exprArgs) {
            return 'intval(' . $resolvedArgs . ')';
        }
    );
    

    Built-in filters can be overridden adding a function with the same name. The example below will replace the built in capitalize filter with PHP’s lcfirst function:

    <?php
    
    $compiler->addFilter('capitalize', 'lcfirst');
    

    With extensions the developer has more flexibility to extend the template engine, and override the compilation of instructions, change the behavior of an expression or operator, add functions/filters, and more.

    An extension is a class that implements the events triggered by Volt as a method of itself. For example, the class below allows to use any PHP function in Volt:

    <?php
    
    namespace MyApp\View\Extensions;
    
    class PhpFunctionExtension
    {
        public function compileFunction(string $name, string $arguments)
        {
            if (true === function_exists($name)) {
                return $name . '('. $arguments . ')';
            }
        }
    }
    

    The above class implements the method compileFunction which is invoked before any attempt to compile a function call in any template. The purpose of the extension is to verify if a function to be compiled is a PHP function allowing to call the PHP function from the template. Events in extensions must return valid PHP code, which will be used as result of the compilation instead of code generated by Volt. If an event does not return a string the compilation is done using the default behavior provided by the engine.

    Volt extensions must be in registered in the compiler making them available in compile time:

    <?php
    
    use MyApp\View\Extensions\PhpFunctionExtension;
    
    $compiler->addExtension(
        new PhpFunctionExtension()
    );
    

    Events

    The following compilation events are available to be implemented in extensions:

    Event/MethodDescription
    compileFunctionTriggered before trying to compile any function call in a template
    compileFilterTriggered before trying to compile any filter call in a template
    resolveExpressionTriggered before trying to compile any expression. This allows the developer to override operators
    compileStatementTriggered before trying to compile any expression. This allows the developer to override any statement

    Caching

    With Volt it’s easy cache view fragments. This mechanism improves performance, preventing the generated code from being executed by PHP, each time the view is displayed:

    {% cache 'sidebar' %}
        <!-- .... -->
    {% endcache %}
    

    Setting a specific number of seconds (1 hour):

    {% cache 'sidebar' 3600 %}
        <!-- ... -->
    {% endcache %}
    

    Any valid expression can be used as cache key:

    {% cache ('article-' ~ post.id) 3600 %}
    
        <h1>{{ post.title }}</h1>
    
        <p>{{ post.content }}</p>
    
    {% endcache %}
    

    The caching is done by the component via the view component.

    Services

    If a service container (DI) is available for Volt. Any registered service in the DI container is available in volt, with a variable having the same name as the one that the service is registered with. In the example below we use the flash service as well as the security one:

    <div id='messages'>{{ flash.output() }}</div>
    <input type='hidden' name='token' value='{{ security.getToken() }}'>
    

    Stand-alone

    You can use Volt as a stand along component in any application.

    Register the compiler and set some options:

    <?php
    
    use Phalcon\Mvc\View\Engine\Volt\Compiler as VoltCompiler;
    
    $compiler = new VoltCompiler();
    $compiler->setOptions(
        [
            // ...
        ]
    );
    

    Compilation of templates or strings:

    
    
    • A bundle for Sublime/Textmate is available here
    • , is another sample application that uses Volt, GitHub