PHP templating often gets a bad rap for facilitating subpar code — but that doesn’t have to be the case. Let’s look at how PHP projects can enforce a basic Model, View, Controller (MVC) structure without depending on a purpose-built templating engine.
But first, a very brief PHP history lesson
The history of PHP as a tool for HTML templating is full of twists and turns.
One of the first programming languages used for HTML templating was C, but it was quickly found to be tedious to use and generally ill-suited for the task.
Rasmus Lerdorf created PHP with this in mind. He wasn’t opposed to using C to handle back-end business logic but wanted a better way to generate dynamic HTML for the front end. PHP was originally designed as a templating language, but adopted more features over time and eventually became a full programming language in its own right.
PHP’s unique ability to switch between programming mode and HTML mode was found to be quite convenient, but also made it tempting for programmers to write unmaintainable code — code that mixed business logic and templating logic. A PHP file could begin with some HTML templating and then suddenly dive into an advanced SQL query without warning. This structure is confusing to read and makes reusing HTML templates difficult.
As time passed, the web development community found more and more value enforcing a strict MVC structure for PHP projects. Templating engines were created as a way to effectively separate views from their controllers.
To accomplish this task, templating engines typically have the following characteristics:
- The engine is purposely underpowered for business logic. If a developer wants to perform a database query, for example, they need to make that query in the controller and then pass the result to the template. Making a query in the middle of the template’s HTML is not an option.
- The engine takes care of common security risks behind the scenes. Even if a developer fails to validate user input and passes it directly into the template, the template will often escape any dangerous HTML automatically.
Templating engines are now a mainstay feature in many web technology stacks. They make for more maintainable, more secure codebases, so this comes as no surprise.
It is possible, however, to handle HTML templating with plain PHP as well. You may want to do this for a number of reasons. Maybe you are working on a legacy project and don’t want to bring in any additional dependencies, or maybe you are working on a very small project and prefer to keep things as lightweight as possible. Or maybe the decision isn’t up to you at all.
Use cases for PHP templating
One place that I often implement plain PHP templating is WordPress, which doesn’t enforce a rigid MVC structure by default. I feel that bringing in a full templating engine is a bit heavy-handed, but I still want to separate my business logic from my templates and want my views to be reusable.
Whatever your reason, using plain PHP to define your HTML templates is sometimes the preferred choice. This post explores how this can be done in a reasonably professional way. The approach represents a practical middle ground between the spaghetti-coded style that PHP templating has become notorious for and the no-logic-allowed approach available with formal templating engines.
Let’s dive into an example of how a basic templating system can be put into practice. Again, we’re using WordPress as an example, but this could be swapped to a plain PHP environment or many other environments. And you don’t need to be familiar with WordPress to follow along.
The goal is to break our views into components and create a distinct separation between the business logic and HTML templates. Specifically, we are going to create a view that displays a grid of cards. Each card is going to display the title, excerpt, and author of a recent post.
Step 1: Fetching data to render
The first step to take is fetching the data that we want to display in our view. This could involve executing a SQL query or using the ORM or helper functions of your framework/CMS to access your database indirectly. It could also involve making an HTTP request to an external API or collecting user input from a form or query string.
In this example, we’re going to use the WordPress get_posts
helper function to fetch some posts to display on our homepage.
<?php // index.php
$wp_posts = get_posts([
'numberposts' => 3
]);
We now have access to the data we want to display in the cards grid, but we need to do some additional work before we can pass it to our view.
Step 2: Preparing data for templating
The get_posts
function returns an array of WP_Post
objects. Each object contains the post title, excerpt, and author information that we need, but we don’t want to couple our view to the WP_Post
object type because we might want to show other kinds of data on our cards somewhere else in the project.
Instead, it makes sense to proactively convert each post object to a neutral data type, like an associative array:
<?php // index.php
$wp_posts = get_posts([
'numberposts' => 3
]);
$cards = array_map(function ($wp_post) {
return [
'heading' => $wp_post->post_title,
'body' => $wp_post->post_excerpt,
'footing' => get_author_name($wp_post->post_author)
];
}, $wp_posts);
In this case, each WP_Post
object is converted into an associative array by using the array_map
function. Notice that the keys for each value are not title
, excerpt
, and author
but are given more general names instead: heading
, body
, and footing
. We do this because the cards grid component is meant to support any kind of data, not just posts. It could just as easily be used to show a grid of testimonials that have a quote and a customer’s name, for example.
With the data properly prepared, it can now be passed into our render_view
function:
<?php // index.php
// Data fetching and formatting same as before
render_view('cards_grid', [
'cards' => $cards
]);
Of course, the render_view
function does not exist yet. Let’s define it.
Step 3: Creating a render function
// Defined in functions.php, or somewhere else that will make it globally available.
// If you are worried about possible collisions within the global namespace,
// you can define this function as a static method of a namespaced class
function render_view($view, $data)
{
extract($data);
require('views/' . $view . '.php');
}
This function accepts the name of the rendered view and an associative array representing any data to be displayed. The extract function takes each item in the associative array and creates a variable for it. In this example, we now have a variable named $cards
that contains the items we prepared in index.php
.
Since the view is executed in its own function, it gets its own scope. This is nice because it allows us to use simple variable names without fear of collisions.
The second line of our function prints the view matching the name passed. In this case, it looks for the view in views/cards_grid.php
. Let’s go ahead and create that file.
Step 4: Creating templates
<?php /* views/cards_grid.php */ ?>
<section>
<ul>
<?php foreach ($cards as $card) : ?>
<li>
<?php render_view('card', $card) ?>
</li>
<?php endforeach; ?>
</ul>
</section>
This template uses the $cards
variable that was just extracted and renders it as an unordered list. For each card in the array, the template renders a subview: the singular card view.
Having a template for a single card is useful because it gives us the flexibility to render a single card directly or use it in another view somewhere else in the project.
Let’s define the basic card view:
<?php /* views/card.php */ ?>
<div class="card">
<?php if (!empty($heading)) : ?>
<h4><?= htmlspecialchars($heading) ?></h4>
<?php endif;
if (!empty($body)) : ?>
<p><?= htmlspecialchars($body) ?></p>
<?php endif;
if (!empty($footing)) : ?>
<span><?= htmlspecialchars($footing) ?></span>
<?php endif; ?>
</div>
Since the $card
that was passed into the render function contained keys for a heading
, body
, and footing
, variables of those same names are now available in the template.
In this example, we can be reasonably sure that our data is free of XSS hazards, but it’s possible that this view could be used with user input at some later time, so passing each value through htmlspecialchars
is prudent. If a script tag exists in our data it will be safely escaped.
It’s also often helpful to check that each variable contains a non-empty value before rendering it. This allows for variables to be omitted without leaving empty HTML tags in our markup.
PHP templating engines are great but it is sometimes appropriate to use PHP for what it was originally designed for: generating dynamic HTML.
Templating in PHP doesn’t have to result in unmaintainable spaghetti code. With a little bit of foresight we can achieve a basic MVC system that keeps views and controllers separate from each other, and this can be done with surprisingly little code.
Old way to make a template, but work really well!, I prefer use of blade or twig, but if you have your own mini framework this way can work perfectly. :)
So, you have to remember to use
htmlspecialchars
every single time or you have a potential XSS vulnerability. In other words, it’s unsafe by default, which means it is unsafe. Surely we can’t recommend “You just have to remember to do X time after time after time, and by the way doing X makes your code less readable” as a way to build secure, reliable software?This is exactly why you can’t use PHP as a template language, which is really unfortunate, because it’s the one thing it was really designed for.
Could PHP fix this? Well, it seems that Rasmus believes the solution to XSS is something essentially the same as ‘magic quotes’ (remember them?) – described and analysed here – https://lukeplant.me.uk/blog/posts/why-escape-on-input-is-a-bad-idea/
My problem with your argument is that every template language allows for displaying html (as far as I know). Therefore, every templating system is just as vulnerable.
I use Mustache, even with PHP because I want designers to focus on design, not code. And they can’t do it well enough to make a template with PHP. And those that can, aren’t great designers.
A splitting of roles the best long term solution. Those of us that can do both (design and code) can handle XSS worries.
A better approach is usually to escape the data before it reaches the template. The template shouldn’t really be making the decision about what needs escaping and what doesn’t, it should assume all the data is appropriate for display.
In the same way I wouldn’t normally wrap the content in a paragraph tag, the calling code should be able to decide if the content is a single paragraph, or if you need something more in that spot. If I was implementing code like this I’d generally have the calling function wrap and escape the content before passing to the template.
I use Smarty template engine for PHP and it’s pretty good
Marvellous. That’s why I love PHP, simple and effective if you use it the right way.
Nice idea. As an alternative, Timber is a great solution for a robust pseudo-MVC way of doing WordPress. It uses twig for templating which is great to use compared to having to litter the template with open/close php tags and echo statements. It also has some excellent tools for handling image resizing, source sets and caching etc and allows you to seamlessly call WP hooks and functions etc.
If Timber is too much functionality or would be too much refactoring for an existing codebase you can still get the benefit of twig for templating by just using Twig as a dependency – you can then pass your context data into the render/compile function.
blading view in laravel is one of the best example of MVC pattern
Put
$cards
preparation into a callback.Example
function callModel() {
return function() { ...}
}
This function calls the data delivery engine, say model.
Then from the html document call
$cards= ($model->callModel())();
Notice, html part is not an extension of the model (data delivery engine) but an independant layer of the application.
HTML is the decopuled carrosserie of the car. Communicating with the engine is done by triggering the key.
No template engine with
extract()
etcetera needed. Just build html structure with php’sinclude
and callback the model from the html wherever you need data.I never look back to template engines.
Looks still like spaghetti to me, compared to current template engines.
<?php
still disturbs the readability. At least for me.I agree, I enable short codes for PHP because the very few times I work with XML, I can add special <?php tags there.
Is MUCH preferred over this: