16

I was reading this question over on stackoverflow:

https://stackoverflow.com/questions/104516/calling-php-functions-within-heredoc-strings

and the accepted answer says to do plain PHP templates like this:

template.php:

<html>
    <head>
        <title><?=$title?></title>
    </head>
    <body>
        <?=getContent()?>
    </body>
</html>

index.php:

<?php

$title = 'Demo Title';

function getContent() {
    return '<p>Hello World!</p>';
}

include('template.php');

?>

To me the above isn't well structured in the sense that template.php depends on variables that are defined in other scripts. And you're using an include() to execute code when you do include('template.php')(as opposed to using include() to include a class or a function which isn't immediately executed).

I feel like a better approach is to wrap your template inside a function:

template.php:

<?php

function template($title, $content) {
    ob_start(); ?>

<html>
    <head>
        <title><?=$title?></title>
    </head>
    <body>
        <?=$content?>
    </body>
</html>

    <?php return ob_get_clean();
}

?>

index.php:

<?php

require_once('template.php');

print template('Demo Title', '<p>Hello World!</p>');

?>

Is the second approach better? Is there an even better way to do it?

5 Answers 5

13

I wouldn't do the second approach as the parameters aren't named.

A well-written essay on describing how a template system should work is coming from Parr, it's often quoted by people writing template systems and/or web-mvc frameworks.

Personally, what I usually prefer is to implement an ArrayObject class with a properties array, and the template would refer to $this->propertyName, which in fact would be the template object's $this->property['name']. This could be also achieved simply by using __set and __get, so:

class Template {
  private $_scriptPath=TEMPLATE_PATH;//comes from config.php
  public $properties;
  public function setScriptPath($scriptPath){
    $this->_scriptPath=$scriptPath;
  }
  public function __construct(){
      $this->properties = array();
  }
  public function render($filename){

   ob_start();
   if(file_exists($this->_scriptPath.$filename)){
     include($this->_scriptPath.$filename);
    } else throw new TemplateNotFoundException();
    return ob_get_clean();
  }
  public function __set($k, $v){
      $this->properties[$k] = $v;
  }
  public function __get($k){
      return $this->properties[$k];
  }
}

and a template would look like:

<html>
      <head>
         <title><?=$this->title?></title>
      </head>
      <body>Hey <?=$this->name?></body>
</html>

and invoking it would look like:

$view = new Template();
$view->title="Hello World app";
$view->properties['name'] = "Jude";
echo $view->render('hello.inc');

As far as I remember, this is how the old Symfony 1.x and the Zend_View template engines look like, and for me it's fine.

4
  • TinyButStrong does something similar, as well
    – Izkata
    Aug 5, 2012 at 1:33
  • how do you handle looping over some scores say? do you use a separate "sub" template?
    – Ryan
    Aug 5, 2012 at 3:25
  • i wonder if there is any way to get rid of using $this in the template, for example just $title or $name.
    – Ryan
    Aug 5, 2012 at 3:52
  • 1
    @Ryan You can use extract($this->properties); to extract the properties into variables, just before the include statement.
    – Joyce Babu
    Aug 13, 2014 at 3:31
2

Because Codeigniter is my favorite framework, I propose you a Codeigniter-ish solution:

class Template {
    protected $path, $data;

    public function __construct($path, $data = array()) {
        $this->path = $path;
        $this->data = $data;
    }

    public function render() {
        if(file_exists($this->path)){
            //Extracts vars to current view scope
            extract($this->data);

            //Starts output buffering
            ob_start();

            //Includes contents
            include $this->path;
            $buffer = ob_get_contents();
            @ob_end_clean();

            //Returns output buffer
            return $buffer;
        } else {
            //Throws exception
        }
    }       
}

Your template would look like:

<html>
    <head>
        <title><?=$title?></title>
    </head>
    <body>
        <?=$content?>
    </body>
</html>

And you would render it by instantiating the class, passing data in an array as a constructor parameter and calling the 'render' method:

$data = array('title' => 'My title', 'content' => 'My content');
$tmpl = new Template('template.php', $data);
echo $tmpl->render();

In this way you are not directly executing the included code, and you are also keeping your template readable, so designers will be happy.

0
1

A template will always depend on outside variables, and with PHP being a dynamic language, there is no way to enforce at compile time that they will be there. So the first approach is probably fine; there are still a few problems though:

  • The example does not take care of HTML-encoding the output, which means you have potential XSS vulnerabilities.
  • If one of those variables isn't set, the code will raise a warning; you won't see whether the warning makes sense or not (maybe the variable is optional).

A very straightforward approach is to write a function, preferably with a very short name, that takes care of both these; calling it _() seems to be a common convention. A simple version might look like this:

function _($raw) {
    if (isset($raw)) {
        return htmlspecialchars($raw);
    }
}

And then in your template, you'd do:

<title><?= _($title) ?></title>

More things to do with the _() function:

  • Give it a second argument to allow overriding the htmlspecialchars call, for cases where you want to pass HTML into it rather than raw strings.
  • Add type checking so that arrays and objects won't yield Array and Object, but rather something meaningful (or empty string).
  • Add internationalization support (yet another argument).
  • When in debug mode, print the name of the variable with some special formatting if it isn't defined. This way, you can see at a glimpse if there's anything wrong with the template.
1

The first template.php is easier to load as-is into dreamweaver/bluegriffon/whatever and edited by a server-side non-savvy web designer, the second... well it still kinda could, but it's much more likely to break during the editing process.

I'd go with the first to keep it looking like HTML with benefits, the way 3rd party designers usually prefer.

2
  • in what sense? do you mean it can't be altered using drag and drop? textually the second one is the same as the first but with a couple lines of PHP wrapped around. So assuming it's always going to have that structure, it isn't any harder for them to edit by hand.
    – Ryan
    Aug 5, 2012 at 2:05
  • 1
    A good template makes sense to developers, a great template makes sense to designers.
    – Citricguy
    Aug 7, 2012 at 9:07
-3

Php is not a programming language. So writing strongly-typed is not possible if your language doesn't have a real type system. But you can help your IDE like PHP-Storm to do the trick you want...

Your view definition:

interface mySimpleView { 
  public function getTitle(); 
} 

Your template file:

<?php
/**
* @var $this mySimpleView  <== Your IDE will try to use this type.
*/
?>

<h1><?= $this->getTitle() ?></h1>
<p><?= $this->getContent() ?></p> <!-- <= IDE will warn you !  -->
0

Not the answer you're looking for? Browse other questions tagged or ask your own question.