Views
Out of the box, Forme uses a templating library called Plates Plates is pretty much just standard PHP and WordPress, but with some rules and guidelines to keep things maintainable.
WARNING
We're using version 4.00-alpha, and the docs are incomplete over on the Plates website. All the features from version 3 are present, but the syntax is a little different. We'll go through the main bits below.
Forme also comes with support for popular php templating languages Twig and Blade as well as Plates 3. The rest of the examples here will assume you're using the default Plates 4.
What are views for?
Views are where your html lives and where you spit out the variables you prepare in your controller, using a limited amount of logic.
They usually get called/rendered from your controllers or from other views.
View file names and location
Your View files live in the 'views' directory, and should end with .plate.php
.
Within that directory, you can organise things how you like. Usually it's useful to have your main page content in the main directory, then layouts and partials/components in subdirectories. This is an example 'views' directory from an existing project:
// views/
.
├── layouts/
│ └── main.plate.php
├── partials/
│ ├── banner-image.plate.php
│ ├── banner-map.plate.php
│ ├── breadcrumbs-and-title-area.plate.php
│ ├── breadcrumbs.plate.php
│ ├── card.plate.php
│ ├── entry-result-image.plate.php
│ ├── entry-result.plate.php
│ ├── footer.plate.php
│ ├── global-nav.plate.php
│ ├── nav.plate.php
│ ├── search-form.plate.php
│ ├── side-box.plate.php
│ ├── side-nav.plate.php
│ ├── tabbed-logos.plate.php
│ ├── tabbed-tab-navs.plate.php
│ ├── tabbed-tab-panes.plate.php
│ ├── tabbed-text-card.plate.php
│ └── title-area.plate.php
├── archive.plate.php
├── default.plate.php
├── full-width.plate.php
├── index.plate.php
├── search-results.plate.php
└── tabbed.plate.php
The views in the main directory correspond to WordPress default or custom templates and generally contain the main content section (minus maybe the footer), the partials are various page elements/components that are then used by these sections (or by the layouts). Layouts are sort of like the outer shell of the page and contain things like the head, html and body tags, although you can structure this however you prefer.
If your project is particularly complex, you might want to split views up into more directories. It's up to you.
What can views do?
Apart from html elements, they should echo out your previously prepared variables or functions. They can also contain control structures in the form of if/else
and foreach
loops. Additionally, they can call other views, for example partials if you need to nest some components, or declaring which layout they use.
Take care
Views should not:
- assign any variables.
- make any database calls directly.
This kind of functionality belongs in your controllers, not in your views.
We are, however, still in WordPress, so you can make any function calls as you normally would.
Rendering Views
Controllers normally have an instance of the Plates class injected into them on which you call the rendering method.
So in your controller (or template controller) class, you might have something like:
// template-controllers/MyController.php
public function __invoke($args = [])
{
// template controller handle method magically has postId available
$posts = get_posts($args['postId']);
// template controller handle method magically has fields available
$context = $args['fields'];
$context['posts'] = $posts;
$context['foo']='Hello World'];
// you don't need to add the parent 'views/' folder nor the suffix '.plate.php'
return $this->view->render('foo', $context);
// 'Hello World' is now available in your foo view template as $foo
// $posts is available as $posts
// any acf fields are also available
}
Then in your view:
// views/foo.plate.php
<div>
<h1><?=e($foo)?></h1>
<?php foreach $posts as $post: ?>
<h2><?=e($post->title)?></h2>
<p><?=e($post->content)?></p>
<?php endforeach?>
</div>
Note the use of e()
to escape variable output.
Referencing templates
You should leave off the plates.php
ending when referring to a template in the render method. So for example, views/partials/foo.plates.php
would become just partials/foo
.
$this->view->render('partials/foo');
You can use either dot notation or slashes to reference view directories in the render
method, so for example partials/foo
or partials.foo
will both work the same.
$this->view->render('partials.foo');
Any template called index.plate.php
gets special treatment, you can leave it out as long as you include a trailing slash or dot. views/partials/foo/index.plate.php
becomes partials/foo/
or partials.foo.
$this->view->render('partials/foo/');
$this->view->render('partials.foo.');
TIP
All of the above magic only applies to Forme's default Legacy Plates implementation. Twig, Plates 3 and Blade all keep their standard behaviour, which may or may not be similar.
Using the render method within views
Within views, you can call the render method on the Plates class directly, which is available as $v
.
<?=$v->render('path/to/view', $context)?>
TIP
Notice we are echoing it out, this is important
The view() function
Themes also have a global helper function available called view
, which you can call from anywhere.
So, this also works in controllers if you don't have the Plates class injected:
// app/Controllers/MyController.php
...
public function __invoke($args = [])
{
$posts = get_posts();
$context['posts'] = $posts;
$context['foo']='Hello World'];
return view('foo', $context);
// 'Hello World' is now available in your foo view template as $foo
// $posts is available as $posts
}
And you can do this sort of thing in your views:
// views/foo.plate.php
<?=view('bar', ['someVar' => 'someValue'])?>
// views/bar.plate.php
<?=$someVar?>
However since standard controllers have a $this->view
instance anyway, and templates have $v
, view
is really most useful in ad hoc functions and classes where that isn't the case.
// some function
function fooBarBaz(array $args): string {
return view('bar.baz', $args);
}
TIP
As with other helpers, view()
is only available in themes, so you shouldn't rely on it existing within plugins, unless you explicitly copy over its definition.
Layouts
Layouts are declared within views using the layout()
method.
<?php $v->layout('layouts/main')?>
This tells the renderer to use the layout at views/layouts/main.plate.php
. You can put this anywhere in the view, it doesn't matter, but since it's like a declaration, it probably makes the most sense right at the top of your file.
You can pass variables up to the parent layout using the same syntax as with the render function, just pass an array as the second parameter.
<?php $v->layout('layouts/main', ['foo' => 'bar'])?>
Within layouts you can use section('content')
to show where you want the child content to render.
<body>
<div class="container">
<?=$v->section('content')?>
</div>
</body>
Layouts can inherit from other layouts, so you can use hierarchical patterns for more complex use cases.
// first define the main parent layout
// view/layout/main.plate.php
<body>
<div class="container">
<?=$v->section('content')?>
</div>
</body>
// we decide that we need to wrap the content in another div in certain views
// so let's make another layout for those but re-using the main parent
// views/layouts/wrapper.plate.php
<?php $v->layout('layouts/main')?>
<div class="content-wrapper">
<?=$v->section('content')?>
</div>
// this concrete view is default, i.e. without the .content-wrapper div
// views/default.plate.php
<?php $v->layout('layouts/main')?>
This content will be within .container div
// rendered result
<body>
<div class="container">
This content will be within .container div
</div>
</body>
// and this concrete view includes the wrapper around the content
// views/-with-wrapper.plate.php
<?php $v->layout('layouts/wrapper')?>
This content will be within .content-wrapper div
// rendered result
<body>
<div class="container">
<div class="content-wrapper">
This content will be within .content-wrapper div
</div>
</div>
</body>
Insert
You can also use insert()
instead of render
to include other views/partials. The syntax is the same.
<body>
<?=$v->insert('partial/header')?>
<div class="container">
<?=$v->section('content')?>
</div>
</body>
Plates Rules
Here are some rules for using Plates, together with some code examples. You can also check out some more general tips on writing views .
- Always use HTML with inline / single line PHP. Never use blocks of PHP.
- Always use the short echo syntax
<?=
when outputting variables. For all other inline PHP code, use the full<?php
tag. Do not use short tags. - Always use the alternative syntax for control structures, which is designed to make view templates more legible.
- Never use PHP curly brackets.
<?php
echo $foo;
if ($bar) {
echo $bar;
}
?>
<?=$foo?>
<?php if ($bar):?>
<?=$bar>
<?php endif>
- Always escape potentially dangerous variables prior to outputting using an escape function such as
e()
.
<?=$riskyVar?>
<?=e($riskyvar)>
e()
will use Laravel's helper which we usually prefer as it's the least verbose. You could however also use Plate's escape function using $v->e()
if you prefer, or WordPress' esc_html()
if you need to filter it for some reason.
// Plates
<?=$v->e($riskyVar)?>
// WordPress
<?=esc_html($riskyVar)?>
- Only ever have one statement in each PHP tag.
<?php echo $foo; if ($bar):?>
<div>Hello World</div>
<?php endif?>
<?=$foo?>
<?php if ($bar):?>
<div>Hello World</div>
<?php endif?>
- Avoid using semicolons to close statements. They are not needed when there is only one statement per PHP tag.
<?php if ($bar):?>
<div>Hello World</div>
<?php endif;?>
<?php if ($bar):?>
<div>Hello World</div>
<?php endif?>
- Never use the
use
operator and don't instantiate classes in views. View templates should not be interacting with classes in this way. Wrap their methods in a globally available helper function or pass them in as a variable instead
<?php use Foo\MyService;?>
<?php (new MyService)->doSomething()?>
// this function has been defined in helpers.php
<?php my_service_method_wrapper_function()?>
// this variable contains the service class, which has been instantiated in the controller and passed in $context
<?php $myService->doSomething()?>
- Never use the
for
,while
orswitch
control structures. Instead useif
andforeach
. - The Loop is EVIL. You should never use it nor any similarly odd and disturbing
while/have
implementations (e.g. ACF/Buddypress).
<?php while(has_posts(): the_post();?>
<div>
Difficult to reason about state here.
What does the_post even do?
Sick disturbing oddness.
</div>
<?php endwhile?>
<?php foreach ($posts as $post):?>
<div>
It's obvious what's happening here.
You're in control.
Normality ensues.
</div>
<?php endforeach?>
- Never put
if/else
statements on a single line - use the ternary operator instead.
<div class="default-class<?php if ($condition) { echo ' conditional-class'; }?>">
I might have a conditional class
</div>
<div class="default-class<?=$condition ? ' conditional-class' : ''?>">
I might have a conditional class
</div>
- Having said that, prefer
if/else
to hard to read or nested ternaries. Clarity is key. (Just don't put them on a single line!)
// this will parse fine and is easy enough to read
<div class="default-class
<?php if ($condition):?>
conditional-class
<?php endif?>
">
I might have a conditional class
</div>
- Avoid variable assignment.
<?php foreach ($posts as $post):?>
<?php $myField=get_field('foo', $post->id)?>
<?=$myField?>
<?php endforeach?>
// use the function directly and don't assign
<?php foreach ($posts as $post):?>
<?=get_field('foo', $post->id)?>
<?php endforeach?>
// or even better, pre-populate $myFields in your controller
<?php foreach ($myFields as $myField):?>
<?=$myField?>
<?php endforeach?>
- Of course, you still have access to the whole of PHP and wordpress in your view templates if you need it. So if you need to use, say,
the_content()
orget_field()
, go ahead and use them. We don't need to completely re-invent the wheel here, the main aim is to keep your views as lean and maintainable as possible.