Templating

Canvas supports multiple templating engines through optional packages. Smarty is installed by default, but you can switch to Blade, Twig, Latte, or Plates depending on your preference — or use multiple engines side by side within the same application.

explanation

Installation

Each templating engine is available as a separate Composer package.

composer require quellabs/canvas-smarty

Install the package for the engine you want to use. The identifier is the string that activates it as the default in config/app.php:

Identifier Package Engine
smartyquellabs/canvas-smartySmarty (default, installed with Canvas)
bladequellabs/canvas-bladeLaravel Blade syntax
twigquellabs/canvas-twigTwig by Symfony
lattequellabs/canvas-latteLatte by Nette
platesquellabs/canvas-platesPlates (native PHP templates)
handlebarsquellabs/canvas-handlebarsHandlebars (compiled native PHP templates)

Basic Usage

Call $this->render() from any controller that extends BaseController, passing the template path and an array of variables:

class UserController extends BaseController {

    /**
     * @Route("/users")
     */
    public function index(): Response {
        $users = $this->em()->findBy(UserEntity::class, ['active' => true]);
        return $this->render('users/index.tpl', ['users' => $users]);
    }
}

Template paths are relative to the template_dir defined in the engine's config file (see Engine-Specific Configuration below). The default is the templates/ directory in your project root.

{* templates/users/index.tpl *}
{foreach $users as $user}
    <p>{$user->getName()}</p>
{/foreach}

Template inheritance, layouts, and partials are handled entirely by the engine itself — refer to the engine's own documentation for those features.

Configuration

Set the active templating engine in config/app.php using the template_engine key. The value must match the package name of the installed engine:

// Template engine
'template_engine' => 'smarty',  // default

Engine-Specific Configuration

Each templating engine can be configured individually through its own config file in the config/ directory. These files are created automatically when you install the corresponding package:

  • config/smarty.php
  • config/blade.php
  • config/twig.php
  • config/latte.php
  • config/plates.php
  • config/handlebars.php

Use these files to configure engine-specific options such as the template directory, caching, debug mode, and extensions — without touching the global config/app.php. For example, Smarty's default configuration:

// config/smarty.php
return [
    'template_dir'   => dirname(__FILE__) . '/../templates/',
    'compile_dir'    => dirname(__FILE__) . '/../storage/smarty/compile/',
    'cache_dir'      => dirname(__FILE__) . '/../storage/smarty/cache/',
    'debugging'      => false,
    'caching'        => false,
    'clear_compiled' => true,
];

Rendering via the Container

$this->render() is a convenience wrapper around the DI-resolved engine. Resolving the engine directly from the container is equivalent and produces the same result:

// Via BaseController shorthand
return $this->render('users/index.tpl', ['users' => $users]);

// Via container directly — identical result
$engine = $this->container->get(TemplateEngineInterface::class);
return new Response($engine->render('users/index.tpl', ['users' => $users]));

The container approach is useful in service classes or other contexts where BaseController is not available.

Injecting an Engine into a Route Method

A specific engine can be injected directly into a route method using @WithContext. This is useful when a single route needs a different engine than the application default:

/**
 * @Route("/users")
 * @WithContext(parameter="engine", context="blade")
 */
public function index(TemplateEngineInterface $engine): Response {
    $users = $this->em()->findBy(UserEntity::class, ['active' => true]);
    return new Response($engine->render('users/index.blade.php', ['users' => $users]));
}

Without @WithContext, the default engine is injected automatically. Use @WithContext only when you need a specific engine other than the default — the context value must match one of the engine identifiers listed above. See Contextual Containers for full details on @WithContext.

Mixing Engines via Dependency Injection

Canvas allows you to use multiple templating engines within the same application. Resolve a specific engine from the container using the for() method with the engine identifier:

$template = $container->for('smarty')->get(TemplateEngineInterface::class);
$template = $container->for('plates')->get(TemplateEngineInterface::class);
$template = $container->for('blade')->get(TemplateEngineInterface::class);

This is useful when migrating between engines incrementally, or when different parts of your application have different templating needs. Each resolved instance is fully configured and ready to render.

Engine Documentation

For template syntax, layouts, inheritance, and engine-specific features, refer to each engine's own documentation: