Templates
A template is a PHP file that makes HTML. The framework has no special template language.
Template files have the extension .phtml. Some old templates have the extension .php.
This page tells you how to:
- give data to a template
- render a template from a plugin
- find which file the framework uses when two templates have the same name
- change a template of a plugin or of the system without a change to its package
Related pages:
- Frontend overview shows how a full page is made.
- Theme package describes the theme layout templates.
- DGS templates describes the templates of the Data Grid Store.
The Template class
The Template class is the base of all template objects. It does three things:
- It keeps the variables that you assign.
- It finds the template file in its lookup paths.
- It includes the file and returns the output as a string.
These classes extend Template or implement the same interface (core\ITemplate):
| Class | Package | Where it is used |
|---|---|---|
Festi\Theme\Plugin\DisplayPlugin |
Theme package | Each plugin that renders HTML. |
Festi\Theme\DefaultTheme |
Theme package | The theme layout (main.phtml, head.phtml). |
AbstractField |
Core | Each DGS field. |
StoreView |
Core | The DGS list and forms. |
DisplayPlugin and DefaultTheme are not in the Core package. They are available only if the project has the Theme package (festi-team/festi-framework-theme) in its composer.json:
"require": {
"festi-team/festi-framework-core": "dev-develop",
"festi-team/festi-framework-theme": "dev-develop"
}
Assign data
Use assign() to give a variable to the template. The method returns the object, thus you can chain the calls.
$template = new Template('/path/to/templates/');
$template->assign('title', 'My Page Title');
$template->assign([
'content' => 'This is the content of the page.',
'user' => 'John Doe',
]);
A property assignment does the same as assign():
$template->title = 'My Page Title';
Render a template
Use fetch() to render a template. The method returns the output. It does not print it.
$content = $template->fetch('example.phtml');
echo $content;
In example.phtml each assigned variable is a PHP variable:
<h1><?php echo $title; ?></h1>
<p><?php echo $content; ?></p>
fetch() throws a SystemException when it does not find the file.
Local variables
The second argument of fetch() is an array of local variables.
A local variable exists only for that one fetch() call.
If a local variable and an assigned variable have the same name, the template gets the local variable.
$localVars = [
'description' => 'This description is local.',
];
$content = $template->fetch('example.phtml', $localVars);
Read an assigned variable
Use retrieve() to read a variable that you assigned. The method returns null when the variable does not exist.
$template->assign('user', 'John Doe');
$user = $template->retrieve('user'); // John Doe
Check that a template exists
if ($template->isTemplateExists('example.phtml')) {
echo $template->fetch('example.phtml');
}
isTemplateExists() looks only in the lookup paths of the object.
Lookup paths
A Template object can have one lookup path or a list of lookup paths.
fetch() uses the first file that it finds.
$template = new Template([
'/path/to/custom/templates/',
'/path/to/default/templates/',
]);
The order of the search is:
- The file name as it is, if a file with this path exists.
- Each lookup path, in the order of the list.
For the Template class, the third argument of fetch() is a directory. If you give it, fetch() uses only directory + file name.
Templates in a plugin
A plugin that extends Festi\Theme\Plugin\DisplayPlugin is a template object. This class comes from the Theme package (see above). Put the templates of the plugin in its templates directory:
plugins/
`-- My/
|-- MyPlugin.php
|-- templates/
| |-- example.phtml
| `-- parts/
| `-- row.phtml
`-- static/
Render a plugin template
<?php
use Festi\Theme\Plugin\DisplayPlugin;
class MyPlugin extends DisplayPlugin
{
public function onDisplayMain(Response &$response): bool
{
// One variable
$this->assign('userToken', '12345678');
// A property assignment does the same as assign()
$this->userName = 'test user';
// Many variables
$this->assign([
'idUser' => 5,
]);
$response->content = $this->fetch('example.phtml');
return true;
}
}
templates/example.phtml:
<b>User Name:</b> <?php echo htmlspecialchars($userName); ?><br>
<b>User Token:</b> <?php echo htmlspecialchars($userToken); ?><br>
<b>User ID:</b> <?php echo (int) $idUser; ?><br>
The template only makes the content of the page. The theme puts this content into the page layout. See Frontend overview.
Note: A template prints its variables without a change. Use
htmlspecialchars()for each value that comes from a user or from the database.
What $this is in a template
In a template, $this is the object that called fetch().
| The template is rendered by | $this is |
|---|---|
a plugin ($this->fetch() in a plugin method) |
the plugin instance |
the theme (main.phtml, head.phtml, menu.phtml) |
the theme instance (ITheme) |
| a DGS field | the field instance |
| a HUI component | a copy of the plugin or of the theme that made the component |
In a plugin template you can call each public and protected method of the plugin:
<a href="<?php echo $this->getUrl('/users/'); ?>"><?php echo __('Users'); ?></a>
<?php echo $this->ui->button()->setCaption(__('Save'))->fetch(); ?>
Scope and lifetime of variables
| Type of variable | How you set it | How long it exists | Which templates get it |
|---|---|---|---|
| Assigned | $this->assign('name', $value) or $this->name = $value |
Until the request ends | All templates that this plugin renders after the assignment |
| Local | Second argument of fetch() |
One fetch() call |
Only that template |
Important rules:
- A plugin has one instance for each request. Thus an assigned variable stays in the plugin after
fetch(). - Each plugin has its own variables. A plugin does not get the variables of a different plugin.
- The theme does not get the variables of a plugin. See Give data to the theme.
- A template that you render from a template does not get the PHP variables of the first template. It gets the assigned variables of the plugin and the local variables that you give it.
Use local variables for data that only one template uses. This prevents a conflict between two methods that assign the same name.
Names that you must not use for variables
fetch() makes PHP variables from the assigned variables and the local variables.
The Template class of the framework renders the templates of a DGS (store views, fields, filters) and every template that you render with your own new Template(...). Its fetch() includes the file in a method that has no variable of its own that a template variable can replace:
- A variable named
templatePath,localVars,vars,file,pathorresultis an ordinary variable. It does not change the file thatfetch()includes. - A template does not get variables that it was not given.
$templatePath,$localVars,$varsand$filedo not exist in a template unless you assign them. Use__FILE__and__DIR__to get the path of the template, andget_defined_vars()to get its variables. - A variable named
thisis ignored.$thisis always the template object. - Names that start with
__festiTemplateare the names offetch(). Do not use them: the template does not get a variable named__festiTemplatePathor__festiTemplateVars.
DisplayPlugin of the theme package renders the templates of a plugin with its own fetchFrom(). Until that package has the same change, do not assign templatePath or localVars in a plugin: the assigned value replaces an internal variable, and fetch() then fails or includes the wrong file. Also do not use file, path, vars and result there.
The framework also sets these names. Do not use them for other data:
| Name | Who sets it | Value |
|---|---|---|
content |
DisplayPlugin::fetchMain() and fetch() with a wrapper template |
The HTML of the page content |
info |
DisplayPlugin::fetchMain() |
Page properties for the theme (URLs, CSS files, JS files, SEO data) |
seo |
DisplayPlugin::fetchMain() |
SEO data of the page |
Render a template in a template
Call $this->fetch() in the template. Give the data as local variables.
templates/list.phtml:
<ul class="b-users">
<?php foreach ($users as $user) : ?>
<?php echo $this->fetch('parts/row.phtml', ['user' => $user]); ?>
<?php endforeach; ?>
</ul>
templates/parts/row.phtml:
<li class="e-user"><?php echo htmlspecialchars($user['name']); ?></li>
The second template gets:
- all assigned variables of the plugin
- the local variables from the call (
$user)
The second template does not get $users, if $users was a local variable of the first template.
Render a template of a different plugin
Call fetch() on the instance of the other plugin. The template then uses the variables and the templates directory of the other plugin.
$html = $this->plugin->users->fetch('profile_card.phtml', ['idUser' => $idUser]);
Prefer a public method of the other plugin that returns the HTML. Then the other plugin controls its template and its data.
Wrapper template
For a plugin, the third argument of fetch() is the name of a wrapper template. It is not a directory.
$response->content = $this->fetch('report.phtml', null, 'layout.phtml');
The steps are:
- The plugin renders
report.phtml. - The plugin assigns the result to the variable
content. - The plugin renders
layout.phtml, which prints$content.
templates/layout.phtml:
<div class="b-report">
<h2><?php echo __('Report'); ?></h2>
<?php echo $content; ?>
</div>
Render a template from a specified directory
Use fetchFrom() when the template is not in the lookup paths. The third argument is the directory.
$path = __DIR__.'/templates/mail/';
$html = $this->fetchFrom('welcome.phtml', ['user' => $user], $path);
fetchFrom() does no lookup. It uses only directory + file name.
Which file the plugin uses
When a plugin calls fetch('name.phtml'), the framework looks for the file in this order. It uses the first file that exists.
| Step | Directory | Core option | Default |
|---|---|---|---|
| 1 | Theme templates for this plugin | Core::OPTION_THEME_TEMPLATE_PATH + plugins/<Name>/ |
<project root>/themes/<theme name>/templates/plugins/<Name>/ |
| 2 | Plugin templates | plugin_path + templates/ |
plugins/<Name>/templates/ |
| 3 | The name as a path | - | - |
| 4 | Project templates | Core::OPTION_TEMPLATE_PATH |
<project root>/templates/ |
| 5 | Theme templates | Core::OPTION_THEME_TEMPLATE_PATH |
<project root>/themes/<theme name>/templates/ |
Important results of this order:
- A theme replaces one template of one plugin with a file in its
templates/plugins/<Name>/directory (step 1).<Name>is the name of the plugin. See Templates that a plugin has. - Without such a file, the template of the plugin has priority. A file with the same name in the project
templatesdirectory or in the root of the themetemplatesdirectory does not replace it. - If the plugin does not have the file, the project
templatesdirectory has priority over the theme. - A plugin template does not use the templates of the Theme package (
ENGINE_TEMPLATE_PATH). Only HUI components and the DGS use them.
Note: The DGS and the HUI components use a different lookup order. See DGS templates and the Theme package.
Two plugins with the same template name
Each plugin looks in the theme directory with its own name and then in its own templates directory. Thus two plugins can have a template with the same name. There is no conflict.
Change a template without a change to its package
Do not edit a template in a plugin that comes from a package or from a submodule. The next update removes your change. Use one of these procedures.
Templates that a plugin does not have (system templates)
Some plugins render templates that are not in their own templates directory. The Jimbo system plugin does this for the sign-in pages:
| Template | Page |
|---|---|
signin.php |
Sign-in form |
signup.phtml |
Registration form |
forgot_password.phtml |
Password recovery form |
The theme supplies these files (step 5 of the lookup). To replace one of them, put a file with the same name in the project templates directory (step 4):
<project root>/
|-- templates/
| `-- signin.php <-- your file, the framework uses it
`-- themes/
`-- default/
`-- templates/
`-- signin.php <-- the file of the theme, not used
You do not change the theme.
Templates that a plugin has
A file with the same name in the project templates directory or in the root of the theme templates directory does not replace a template that the plugin has. Use one of these methods:
-
Theme directory for the plugin. Put a file with the same name in
templates/plugins/<Name>/of the theme, where<Name>is the name of the plugin. The plugin uses this file and takes its other templates from its own directory. This needs a version of the Theme package that has this lookup step.<project root>/ |-- plugins/ | `-- Orders/ | `-- templates/ | |-- list.phtml <-- the file of the plugin, not used | `-- form.phtml <-- used, the theme has no replacement `-- themes/ `-- default/ `-- templates/ `-- plugins/ `-- Orders/ `-- list.phtml <-- your file, the framework uses it -
Template directory with a postfix. Set the Core option
plugin_template_postfix(Core::OPTION_PLUGIN_TEMPLATE_POSTFIX), for example to_site. If a plugin has the directorytemplates_site, the plugin uses it as its template directory. The plugin then does not use itstemplatesdirectory. Thustemplates_sitemust contain all the templates of that plugin. - Event listener. Add a listener for
ITheme::EVENT_THEME_BEFORE_FETCH_MAIN. The listener gets the page content by reference and can change it. See the Theme package. - Subclass. Make a plugin that extends the original plugin, and render your own template in the method that you override.
- Extension point in the plugin. If you are the owner of the plugin, add an extension point. For example, render the template with
fetchFrom()and a directory that a project can set, or dispatch an event that lets a listener replace the HTML.
Give data to the theme
The theme layout (main.phtml) does not get the variables of a plugin. A plugin gives data to the theme only in these ways:
| Data | How the plugin gives it | How the theme reads it |
|---|---|---|
| Page content | $response->content |
$content in main.phtml |
| Page title | $this->core->seo = ['title' => 'Users'] |
$info['seo']['title'] in head.phtml |
| JS and CSS files | $this->includeStatic('js/users.js') |
$info['js'], $info['css'] in head.phtml |
| Other SEO data | $this->core->seo = [...] or onMeta() |
$info['seo'] |
| HTML for a specified location of the layout | A listener for a theme event | The theme prints the result of the event |
See Static files for JS and CSS files.
IDE type hints in plugin templates
A plugin template runs in the scope of the plugin instance. An IDE cannot find this automatically.
Add a PHPDoc block at the top of each template. Declare $this and all the variables of the template:
<?php
/**
* @var ContentsPlugin $this
* @var array $contents
* @var int $currentPage
* @var string $resourceTitle
*/
?>
Rules:
- Use the class name of the plugin for
$this(for exampleContentsPlugin). Do not useAbstractPlugin. - Declare each variable that the plugin method assigns.
- Change the block when you add or remove a variable in the plugin.
Rules for templates
- Use the full tag
<?php echo ...; ?>. Do not use the short tag<?=. - Use
__()for each text that a user sees. - Use
$this->getUrl()for each URL. - Use HUI components for forms, buttons, and dialogs. Do not write this HTML by hand.
- Do not query the database in a template. Get the data in the plugin method and assign it.
- Do not call
includeStatic()in a template. Call it in the plugin method. See Static files.