Leafiny Documentation

Templates

This section describes Leafiny template design.

Theme

The theme is located in the Frontend module: /modules/Frontend/

The Frontend module is the core of your app, it should never be updated with new Leafiny releases. This module is always loaded after others, it contains optional overrides of templates, CSS and JS from all modules, and your own resources.

Leafiny use Twig as template engine : Twig.

Template overview

Template files are located in /modules/Frontend/template directory. We can find template files for:

  • Blocks : /modules/Frontend/template/block
  • Pages : /modules/Frontend/template/page
  • Mails : /modules/Frontend/template/mail

A page contains a skeleton template, a content template and optional block templates.

Open the Frontend module config file: /modules/Frontend/etc/config.php

The homepage template design is defined by:

/** modules/Frontend/etc/config.php **/

'page' => [
    'default' => [
        'template' => 'Frontend::page.twig',
    ],
    '/' => [
        /* ... */
        'content' => 'Frontend::page/index.twig',
        /* ... */
    ],
],

The skeleton template is defined for all pages with default identifier : Frontend::page.twig. It is the template file path in template directory of the specified module (/modules/Frontend/template/page.twig).

Specific page skeleton can be overrided (see How config variables work):

/** modules/Frontend/etc/config.php **/

'page' => [
    '/' => [
        /* ... */
        'template' => 'Frontend::homepage.twig',
        'content'  => 'Frontend::page/index.twig',
        /* ... */
    ],
],

The homepage content key is fill with Frontend::page/index.twig, which means /modules/Frontend/template/page/index.twig. The content is a block in the skeleton called with:

{{ child('content') }}

Override a template file

Third-party modules declare template files for pages, blocks and mails.

Open the config file of the third-party module (in etc directory) and find the page object declaration, for example in Leafiny_Category module:

/** modules/Leafiny_Category/etc/config.php **/

'page' => [
    '/category/*.html' => [
        /* ... */
        'content' => 'Leafiny_Category::page/category/view.twig',
        /* ... */
    ],
    /* ... */
],

The category content template file is named view.twig in /modules/Leafiny_Category/template/page/category directory. Copy this file in the Frontend module: /modules/Frontend/template/page/category/view.twig.

Then, open the Frontend module config file: /modules/Frontend/etc/config.php, and declare the new template:

/** modules/Frontend/etc/config.php **/

'page' => [
    '/category/*.html' => [
        'content' => 'Frontend::page/category/view.twig',
    ],
    /* ... */
],

The content template file for the /category/*.html identifier is now Frontend::page/category/view.twig.

Add a new page

Open the Frontend module config file: /modules/Frontend/etc/config.php, and declare a new route:

/** modules/Frontend/etc/config.php **/

'page' => [
    /* ... */
    '/example.html' => [
        'title'            => 'Example Page',
        'content'          => 'Frontend::page/example.twig',
        'meta_title'       => 'Example',
        'meta_description' => 'This is an example page',
        'my_var'           => 'Hello World!',
    ],
    /* ... */
],
/* ... */

Then, create the example.twig template in /modules/Frontend/template/page/ directory:

<div class="example">
    {{ page.getCustom('my_var') }}
</div>

The new page is now available with /example.html URL path. The page print the custom var declared in config (Hello World!).

For a simple static website, all modules can be deleted and the pages added in frontend module.

Add a new block

Open the Frontend module config file: /modules/Frontend/etc/config.php, and declare a new block:

/** modules/Frontend/etc/config.php **/

'block' => [
    /* ... */
    'my.block' => [
        'template' => 'Frontend::block/my_block.twig',
        'name'     => 'John Doe',
    ],
    /* ... */
]

Then, create the my_block.twig template in /modules/Frontend/template/block/ directory:

<!-- modules/Frontend/template/block/my_block.twig -->

<div class="my-block">
    Hello {{ block.getCustom('name') }}!
</div>

The block print the custom var name declared in config (John Doe).

Call the new block in any template with function child:

{{ child('my.block') }}

This block will be only available in frontend. The context of the block by default is named frontend. A block in backend template must declare the backend context:

/** modules/Backend/etc/config.php **/

'block' => [
    /* ... */
    'admin.my.block' => [
        'template' => 'Frontend::block/backend/my_block.twig',
        'name'     => 'John Doe',
        'context'  => Backend_Page_Admin_Page_Abstract::CONTEXT_BACKEND,
    ],
    /* ... */
]

With backend context, admin.my.block can not be display in frontend area. This will throw the following error:

Block "admin.my.block" is not allowed in "frontend" context

All custom admin blocks and pages must be declared in backend module (modules/Backend/etc/config.php).

Prefix backend block identifier with admin is a good pratice.

Custom vars can be added to the block object:

{% set posts = page.getPosts %}
{% for post in posts %}
    {{ child('my.post.block', {'post':post}) }}
{% endof %}
<!-- my.post.block -->

{% set post = block.getCustom('post') %}
{{ post.getTitle }}

This is usefull for creating a reusable template.

Disable a block

Disable a block with disable key in block configuration. For example, Leafiny_Cms module add a block in category pages who list all pages linked to the category. In modules/Leafiny_Cms/etc/config.phpwe find:

/** modules/Leafiny_Cms/etc/config.php **/

'block' => [
    'category.cms.page.list' => [
        'template' => 'Leafiny_Cms::block/static/page/list.twig',
        'class'    => Cms_Block_Category_Page::class
    ],
]

For disabled this block, open frontend module config file (module/Frontend/etc/config.php), and add disabled key for category.cms.page.list identifier:

/** modules/Frontend/etc/config.php **/

'block' => [
    /* ... */
    'category.cms.page.list' => [
        'disabled' => 1,
    ],
    /* ... */
]

Custom methods

Separate code business logic by creating custom methods. An object is always assigned to the template files. By default:

  • Instance of Core_Page object is assigned to page template and is accessible with page variable
  • Instance of Core_Block object is assigned to block template and is accessible with block variable
{{ page.getUrl('example.html') }}
{{ block.getUrl('example.html') }}

Set a custom class by adding class key in block or page configuration:

/** modules/Frontend/etc/config.php **/

'block' => [
    /* ... */
    'my.block' => [
        'template' => 'Frontend::block/my_block.twig',
        'name'     => 'John Doe',
        'class'    => Frontend_Block_Custom::class
    ],
    /* ... */
]

Create Frontend_Block_Custom class in modules/Frontend/app/Frontend/Block directory:

<?php
/** modules/Frontend/app/Frontend/Block/Custom.php **/

declare(strict_types=1);

/**
 * Class Frontend_Block_Custom
 */
class Frontend_Block_Custom extends Core_Block
{
    /**
     * Random number
     *
     * @return int
    */
    public function getRandomNumber(): int
    {
        return rand(0, 1000);
    }
}

The name of the class must respect the name of the folder in module app directory: Frontend_Block_Custom = Frontend/Block/Custom.php

Use custom method in block template:

<div class="my-block">
    Hello {{ block.getCustom('name') }}! Your number is {{ block.getRandomNumber }}.
</div>

The name of the class must respect the name of the folder in module app directory: Frontend_Block_Custom = Frontend/Block/Custom.php

Page and block classes are related to ViewModel in Leafiny MVVM pattern. They are used for get and show data from models.

Usefull methods

Custom var:

{{ page.getCustom('my_var') }}

Config data variable:

{{ page.getConfig('page.default.my_var') }}

Media file URL:

<!-- https://www.example.com/modules/Frontend/media/logo.png -->
<img src="{{ page.getMediaUrl('Frontend::logo.png') }}" alt="" />

<!-- https://www.example.com/media/logo.png -->
<img src="{{ page.getMediaUrl }}logo.png" alt="" />

Skin file URL:

<!-- https://www.example.com/modules/Frontend/skin/css/style.css -->
{{ page.getSkinUrl('Frontend::css/style.css') }}

<!-- https://www.example.com/modules/Frontend/skin/js/page.js -->
{{ page.getSkinUrl('Frontend::js/page.js') }}

Main domain:

<!-- https://www.example.com/ -->
{{ page.getDomain }}

Url:

<!-- https://www.example.com/hello.html -->
{{ page.getUrl('hello.html') }}

<!-- https://www.example.com/ -->
{{ page.getUrl }}

Helper:

/** modules/Frontend/etc/config.php **/

'helper' => [
    'my_helper' => [
        'class' => Frontend_Helper_Custom::class,
    ],
],
<!-- Core_Helper -->
{% set helper = page.getHelper %}
{{ helper.formatKey('Hello World!') }}

<!-- Frontend_Helper_Custom -->
{% set helper = page.getHelper('my_helper') %}
{{ helper.getFullname }}

Add a twig filter

Twig filter is frequently used for value transformation.

Open the Frontend module config file: /modules/Frontend/etc/config.php. In app key, add a twig_filters configuration with custom class:

/** modules/Frontend/etc/config.php **/

'app' => [
    'twig_filters' => [
        'frontend' => Frontend_Twig_Filters::class,
    ],
],

The class key (here frontend) must be unique. It may be used for override filter class declared by a third party module.

Add the class in /modules/Frontend/app/Frontend/Twig directory:

<?php
/** modules/Frontend/app/Frontend/Twig/Filters.php **/

declare(strict_types=1);

/**
 * Class Frontend_Twig_Filters
 */
class Frontend_Twig_Filters
{
    /**
     * Add twig filters
     *
     * @param Twig\Environment $twig
     */
    public function __construct(Twig\Environment $twig)
    {
        $twig->addFilter(new Twig\TwigFilter('currency', [$this, 'currency']));
    }

    /**
     * Format currency
     * 
     * @param mixed $value
     * 
     * @return string
     */
    public function currency($value): string
    {
        return number_format((float)$value, 2, ',', '') . ' $';
    }
}

currency filter can now be used in any template:

{{ 23.89|currency }} <!-- 23,89 $ -->

Add a twig function

Twig function is frequently used for content generation.

Open the Frontend module config file: /modules/Frontend/etc/config.php. In app key, add a twig_functions configuration with custom class:

/** modules/Frontend/etc/config.php **/

'app' => [
    'twig_functions' => [
        'frontend' => Frontend_Twig_Functions::class,
    ],
],

The class key (here frontend) must be unique. It may be used for override function class declared by a third party module.

Add the class in /modules/Frontend/app/Frontend/Twig directory:

<?php
/** modules/Frontend/app/Frontend/Twig/Functions.php **/

declare(strict_types=1);

/**
 * Class Frontend_Twig_Functions
 */
class Frontend_Twig_Functions
{
    /**
     * Add twig functions
     *
     * @param Twig\Environment $twig
     */
    public function __construct(Twig\Environment $twig)
    {
        $twig->addFunction(new Twig\TwigFunction('imgHtml', [$this, 'imgHtml']));
    }

    /**
     * Generate HTML img element
     *
     * @param string $src
     * @param string $alt
     *
     * @return Twig\Markup
     */
    public function imgHtml(string $src, string $alt = ''): Twig\Markup
    {
        return new Twig\Markup('<img src="' . $src . '" alt="' . $alt . '" />', 'UTF-8');
    }
}

imgHtml function can now be used in any template:

{{ imgHtml('logo.png', 'Logo') }} <!-- <img src="logo.png" alt="Logo"> -->

Add a twig extension

You are free to add Twig extensions. This section show 2 examples.

Debug Extension

In this example we add the Twig Debug Extension.

Open the Frontend module config file: /modules/Frontend/etc/config.php. In app key, add a twig_extensions configuration with custom class:

/** modules/Frontend/etc/config.php **/

'app' => [
    'twig_extensions' => [
        'frontend' => Frontend_Twig_Extensions::class,
    ],
],

Add the class in /modules/Frontend/app/Frontend/Twig directory:

<?php
/** modules/Frontend/app/Frontend/Twig/Extensions.php **/

declare(strict_types=1);

/**
 * Class Frontend_Twig_Extensions
 */
class Frontend_Twig_Extensions
{
    /**
     * Add Debug extension
     *
     * @param Twig\Environment $twig
     */
    public function __construct(Twig\Environment $twig)
    {
        $twig->enableDebug();
        $twig->addExtension(new Twig\Extension\DebugExtension());
    }
}

dump function can now be used in any template:

<pre>
    {{ dump(page.getData) }}
</pre>

Cache Tag Extension

In this example we add the Twig Cache Tag added in Twig 3.2. The cache tag is part of the CacheExtension which is not installed by default.

We need composer to add twig/cache-extra library. Add composer.json file with following content:

{
  "require": {
    "twig/cache-extra": "^3.2"
  }
}

Then run composer install.

Open the Frontend module config file: /modules/Frontend/etc/config.php. In app key, add a twig_extensions configuration with custom class:

/** modules/Frontend/etc/config.php **/

'app' => [
    'twig_extensions' => [
        'frontend' => Frontend_Twig_Extensions::class,
    ],
],

Add the class in /modules/Frontend/app/Frontend/Twig directory:

<?php
/** modules/Frontend/app/Frontend/Twig/Extensions.php **/

declare(strict_types=1);

/**
 * Class Frontend_Twig_Extensions
 */
class Frontend_Twig_Extensions
{
    /**
     * Load twig cache extension if available
     * Need composer to add the necessary libraries
     * @see https://twig.symfony.com/doc/3.x/tags/cache.html
     *
     * @param Twig\Environment $twig
     */
    public function __construct(Twig\Environment $twig)
    {
        $required = [
            'Twig\Extra\Cache\CacheExtension',
            'Twig\Extra\Cache\CacheRuntime',
            'Symfony\Component\Cache\Adapter\TagAwareAdapter',
            'Symfony\Component\Cache\Adapter\FilesystemAdapter'
        ];

        foreach ($required as $class) {
            if (!class_exists($class)) {
                return;
            }
        }

        $twig->addRuntimeLoader(new class extends Core_Helper implements Twig\RuntimeLoader\RuntimeLoaderInterface {
            public function load($class) {
                if (Twig\Extra\Cache\CacheRuntime::class === $class) {
                    $namespace = App::getEnvironment() . '_' . App::getLanguage();
                    $cacheDir  = $this->getCacheDir() . Core_Template_Abstract::CACHE_TWIG_DIRECTORY;
                    return new Twig\Extra\Cache\CacheRuntime(
                        new Symfony\Component\Cache\Adapter\TagAwareAdapter(
                            new Symfony\Component\Cache\Adapter\FilesystemAdapter($namespace, 0, $cacheDir)
                        )
                    );
                }
            }
        });

        $twig->addExtension(new Twig\Extra\Cache\CacheExtension());
    }
}

cache tag can now be used in any template:

{% cache "random_number" %}
    {{ random(0, 1000) }}
{% endcache %}