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. After installing, run the engine's init command to generate the config file:

composer require quellabs/canvas-smarty
php vendor/bin/sculpt smarty:init

Replace smarty with the identifier of the engine you are installing. The identifier is also 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. Run the engine's init command to generate it:

php vendor/bin/sculpt smarty:init
php vendor/bin/sculpt blade:init
php vendor/bin/sculpt twig:init
php vendor/bin/sculpt latte:init
php vendor/bin/sculpt plates:init
php vendor/bin/sculpt handlebars:init

This creates the config file for the corresponding engine:

  • 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.

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: