Simple PHP Template Class

If you are unfamiliar with some of the basic concepts around templating, take a moment to review my previous post: How to Create a Simple PHP Templating Function, where I explain how to look for files, buffer output, and extract variables into scope.

For those unfamiliar with classes and OOP, know that this post doesn’t attempt to explain the concept. It does however provide examples for how a class can be used.

Before we get started, review this simple OOP vocabulary list.

  • Encapsulation – the concept of grouping like-functionality together.
  • Class – group of functions and variables that work together as a single concept. When you write a class, you have “Encapsulated” those functions and variables.
  • Method – a function that is part of a class.
  • Property – a variable that is part of a class.
  • Instantiate – the creation of a new object from a class.
  • Instance / Object – a run-time value of some class.

PHP Template Class

Let’s take a look at this new template class:

<?php
/**
 * Class Template - a very simple PHP class for rendering PHP templates
 */
class Template {
	/**
	 * Location of expected template
	 *
	 * @var string
	 */
	public $folder;
	/**
	 * Template constructor.
	 *
	 * @param $folder
	 */
	function __construct( $folder = null ){
		if ( $folder ) {
			$this->set_folder( $folder );
		}
	}
	/**
	 * Simple method for updating the base folder where templates are located.
	 *
	 * @param $folder
	 */
	function set_folder( $folder ){
		// normalize the internal folder value by removing any final slashes
		$this->folder = rtrim( $folder, '/' );
	}
	/**
	 * Find and attempt to render a template with variables
	 *
	 * @param $suggestions
	 * @param $variables
	 *
	 * @return string
	 */
	function render( $suggestions, $variables = array() ){
		$template = $this->find_template( $suggestions );
		$output = '';
		if ( $template ){
			$output = $this->render_template( $template, $variables );
		}
		return $output;
	}
	/**
	 * Look for the first template suggestion
	 *
	 * @param $suggestions
	 *
	 * @return bool|string
	 */
	function find_template( $suggestions ){
		if ( !is_array( $suggestions ) ) {
			$suggestions = array( $suggestions );
		}
		$suggestions = array_reverse( $suggestions );
		$found = false;
		foreach( $suggestions as $suggestion ){
			$file = "{$this->folder}/{$suggestion}.php";
			if ( file_exists( $file ) ){
				$found = $file;
				break;
			}
		}
		return $found;
	}
	/**
	 * Execute the template by extracting the variables into scope, and including
	 * the template file.
	 *
	 * @internal param $template
	 * @internal param $variables
	 *
	 * @return string
	 */
	function render_template( /*$template, $variables*/ ){
		ob_start();
		foreach ( func_get_args()[1] as $key => $value) {
			${$key} = $value;
		}
		include func_get_args()[0];
		return ob_get_clean();
	}
}
simple-template-class.php

The main take away here is that we have broken our previous single function into multiple smaller functions, and encapsulated them together within a class.

Here is a contrived usage of this class:

<?php
$tpl = new Template( 'path/to/templates/folder' );

print $tpl->render( 'my_template_name', array(
  'some_var' => 'that has this value',
  'another_var' => 'with another value',
) );

Let’s walk through what is going on above:

  1. First, we instantiate the class as a new object named $tpl. During the creation of the object, we pass in the folder location where we expect to find template files.
  2. Next, we run the render() method, providing our template name and the variables we would like to inject into the scope of the template.
  3. The render() method then executes another method within the class named find_template(), which loops through the suggestions and looks for the first file it can find.
  4. If a template file was found, the render() method then calls the render_template() method.
  5. The render_template() method first starts an output buffer so that our output is captured as opposed to being sent to the screen.Then it dynamically defines the variables into the scope and includes the found template file.When done, it returns the buffered output to the caller (in this case the “caller” is the render() method).
  6. Finally, the render() method returns the output to the caller (in this case the “caller” is the above example usage of this class).

How this class improves upon the previous function:

  1. No hard-coded template path. Now each instance of this class can define its own template path, and an instance’s folder can but updated using the set_folder() method.
  2. No potential for extract variable collisions, as no variables are defined within the scope of the render_template() method.By using the func_get_args() function to access the values for parameters passed into the render_template() method, we avoid naming the variables within the scope. This mean that when we later extract() the variables, it is not possible for an extracted variable to conflict with another variable within the method.
  3. Template suggestions are run through array_reverse() before searched, to provided a better developer experience when building the $suggestions array.This means that when you are creating a list of template suggestions, you can easily add more-specific suggestions to the end of the array, as opposed to needing to unshift() them to the beginning.Example:

    Example Template

    <div class="user-profile">
    	<h3 class="title"><?php echo $user_name; ?></h3>
    	<div class="content"><?php echo $user_bio; ?></div>
    </div>
    user-profile-example-template.php

    Using Template Suggestions

    <?php
    $suggestions = array( 'user-profile-example-template' );
    // provide more-specific template suggestions for logged in users
    if ( user_is_logged_in() ) {
      
      // provide a generic template suggestion for all users of a given type
      if ( user_is_of_special_type() ) {
        $suggestions[] = 'user-profile-' . $user->type;
      }
      // provide the most-specific template suggestion possible for this user.
      $suggestions[] = 'user-profile-' . $user->id;
    }
    $tpl = new Template( 'path/to/templates/folder' );
    $tpl->render( $suggestions, array(
      'user_name' => $user->name,
      'user_bio' => $user->bio
    ) );
    user-profile-example-template-suggestions-usage.php

    While this example is contrived, it should be clear how we add more specific suggestions to the existing list of suggestions.

There we have it. Using the concepts presented in the previous post we have broken that function into smaller pieces and encapsulated those pieces within a class. In doing so, we have provided significantly better (more flexible, less error-prone) functionality and developer experience.

Let me know what you think!

References

4 Thoughts

Discussion

Manh Hung
November 12, 2016

Thanks Mr. Daggerhart. Your Template class work perfectly.

Oleksandr
March 22, 2017

Hi, Jonathan! Thanks for your text and php class
Noticed that there is no template example. I think it would be great for understanding for beginners.
Going to use it in my work!

Jonathan Daggerhart
March 22, 2017

Thanks Oleksandr!

I’ve added the template example, and moved from the extract() to the foreach loop. Both great suggestions!

Oleksandr
March 22, 2017

btw,
foreach(func_get_args()[1] as $key=>$value) $$key = $value;
2-3 times faster then extract()

Alex Lord
February 23, 2019

Great template class!
I was wondering if it’s possible to extend the functionality to take an array of arrays?

I’d like to be able to render a template, and have that template loop through an array of arrays, which loads a template within each loop. So a template that renders templates. If that makes sense?

Thanks in advance!

Jonathan Daggerhart
September 16, 2019

Hi Alex,

Apologies for the slow reply. I think the simplest way to perform additional templating within a template is to pass the $tpl class into the template as a variable.

For example:

$tpl->render( $suggestions, array(
  'user_name' => $user->name,
  'user_bio' => $user->bio,
  'tpl' => $tpl,
) );

This would allow you to use the class within a template to render additional templates.

Leave a Reply

Your email address will not be published. Required fields are marked *