Jump to content

Template design suggestions, advice


RecoilUK

Recommended Posts

Hi guys.

 

Just finished my template system, well everything apart from exceptions and commenting, but just wanted to get some opinions, suggestions and advice as its my first piece of code that i,m happy with.

 

The goal was to totally omit any php from the html templates, I know some people disagree with this as a definition of a templating system, but this is how I wanted it, as hopefully it will be part of a cms in the future and I didnt want the template designers having to know anything about php, all the have to know is what the place holders look like, and so far there,s only 3, although there could be a fourth when I get around to designing a form handler as I may need to change css based on form errors ...

 

<FLAGS:name /> for individual variables like the page title etc.

 

<BOX:name /> to identify areas of the web page, header, footer etc.

 

<DATA:name /> this is designed for database query results.

 

As you can its pretty simple, and as soon as you view a template, you can instantly tell what its purpose is.

 

Anyway here it is ...

 

<?php

Class TEMPLATE {

  private static $instance = null;  // declare static variable for Singleton pattern.
  private $layout; // container to hold page layout.
  private $page; // container to hold page layout, used during parsing.
  private $moduledir; // current module directory.
  private $rootdir; // root directory.
  private $boxkey; // container to define box names.
  private $boxvalue; // container to hold box data.
  private $flagkey; // container to define flag values.
  private $flagvalue; // container to hold flag values.

  protected function __construct() {

    $this->db = DATABASE::Instance();
    
    $pos = strrpos($_SERVER['SCRIPT_FILENAME'], '/') + 1;
    $this->moduledir = substr($_SERVER['SCRIPT_FILENAME'], 0,$pos);
    unset($pos);
    $this->rootdir = $_SERVER['DOCUMENT_ROOT'] . '/';
  }

  public static function Instance() {

    if(!self::$instance ) {
      self::$instance = new self(); 
    }
    return self::$instance;
  }

  public function SetLayout($file) {

    if (file_exists($this->moduledir.'templates/'.$file)) {
      $this->layout = file_get_contents($this->moduledir.'templates/'.$file);
    }
    elseif (file_exists($this->rootdir.'templates/'.$file)) {
      $this->layout = file_get_contents($this->rootdir.'templates/'.$file);
    }
    else {
      throw new Exception("Fatal Exception : Layout Template - \"$file\" - Not Found");
    }
    unset($file);
  }

  public function CreateBox($box) {

    $argcount = func_num_args();
    switch($argcount) {
      case 1:
        $box = "<BOX:$box />";
        if (!$this->boxkey[$box]) {
          $this->boxkey[$box] = $box;
          $this->boxvalue[$box] = '';
        }
        break;
      default:
        $argvalues = func_get_args();
        foreach($argvalues as $key => $value) {
          $value = "<BOX:$value />";
          if (!isset($this->boxkey[$key])) {
            $this->boxkey[$value] = $value;
            $this->boxvalue[$value] = '';
          }
        }
    }
    unset ($name);
  }

  public function SetBox($box, $data) {

    $boxvalue = "<BOX:$box />";
    if (!isset($this->boxkey[$box])) {
      $this->boxkey[$box] = $boxvalue;
      $this->boxvalue[$box] = "";
    }
    $this->boxvalue[$box] .= $data;
    unset($box, $boxvalue, $data);
  }

  public function SetFlag($var, $value) {

    $this->flagkey[$var] = "<FLAG:$var />";
    $this->flagvalue[$value] = $value;
    unset($var, $value);
  }

  public function ResetBox($box) {

    $this->boxvalue[$box] = '';
    unset ($box);
  }

  public function GetTemplate($file) {

    if (file_exists($this->moduledir . 'templates/' . $file)) {
      return file_get_contents($this->moduledir . 'templates/' . $file);
    } else {
      return file_get_contents($this->rootdir . 'templates/' . $file);
    }
    unset ($file);
  }

  public function ParseResult($box, $file) {

    $html = $this->GetTemplate($file);
    while ($row = $this->db->fetch_assoc()) {
      foreach ($row as $key => $value) {
        $datakey[$key] = "<DATA:$key />";
        $datavalue[$key] = $value;
      }
      $this->SetBox($box, str_replace($datakey, $datavalue, $html));
      unset ($datakey, $datavalue);
    }
    unset ($html, $file, $row);
  }

  private function ParsePage() {

      foreach($this->boxkey as $key => $value) {
        if ($this->boxvalue[$key] == '') {
          $this->boxvalue[$key] = $this->boxkey[$value];
        }
      }
    $this->page = str_replace($this->flagkey, $this->flagvalue, 
      str_replace($this->boxkey, $this->boxvalue, $this->layout));
  }

  public function DisplayPage() {

    $this->ParsePage();
    echo $this->page;
  }

}

?>

 

Looking forward to your comments.

 

Cheers

Link to comment
Share on other sites

If I may ask, why is it a singleton? As I see, it severly limits the class flexibility... for example, what happens if I have to parse two templates before outputting anything which hold similar placeholder names? The values will get overwritten and the first template will be lost. Displaying two consecutive users list divided by group for example. Same template and placeholders, different values, same instance -> one is lost.

 

Which brings me to the second question: What happens when there is a template with a dynamic sized list? The registered users for example, the way your class is set, I would have to enter hundreds of <DATA:> lables in the template instead of having a way to define dynamic loops. (Or else I misunderstood your methods, I'm sleepy...)

 

What if I need a template inside a template inside a template?

 

And last but not least; why is the template class accesing directly de database object for results? This couples your template with unrelated parts of your application. Objects should have a sinlge responsability and clear dependencies. Right now it has at least two responsabilities: Presenting data and fetching the data. The dependency on the DATABASE class is also not transparent in the class API as it depends on a specific named class and implementation which can't be seen or isn't implied from the outside. If it must use the database class, consider using a setter, passing it as an argument in the constructor, dependency injection or a registry. (On the record, I despise singletons... but others love them so this argument is somewhat biased).

 

Anyway, I would advocate in favor of a data setter somewhere which recieves the database object output or whichever data is needed and stores it inside the class for later use.

 

Cheers.

Link to comment
Share on other sites

Hi

 

Thanks for the response.

 

I,ll answer question 4 first.

 

Its accessing the database object directly because of the way the database object works, its also a singleton and the query, instead of the usual method of spitting out the result to whatever method or function called the query, it assigns the result to $this->result, so it never leaves the database object, to get access to the data you have to use additional methods such as $db->result->fetch_assoc();.

 

So in efect, it doesnt actually control the query, it just has access to the query data when another part of the control process decides to give it, turns the data into something suitable for parsing and uses it. I thought it may limit the amount of querys between different scripts that are working on the same data.

 

Question 1 and 3 as they are related.

 

Its a sinlgeton because I cant see it working any other way, there is no point having losts of different template objects as the template class isnt aware that they exist.

 

If you want to have one template inside another then say the first 'layout.html' as ,<BOX:header />, <BOX:footer /> for example, and it needed a space to load a second template, then you could call this 'template2' just for example and create a seperate box for it <BOX:template2 />, do whatever you needed and load data into that box. Its an accumulative template engine, it never overwrites any data, just adds data to previously entered data.

 

Question 2.

 

The ParseResult method is designed for dynamic data, first it gets the template file, which will contain the placeholders for the dynamic data you wish to display, say <DATA:username /> <DATA:age /> etc, with maybe some html table tags, and instead of you having to enter hundreds of these tags, it will loop over the result given to it one row at a time, alter the data for display and parse the template, loading the data into whatever box you told it to.

 

And it already has 3 data setters, SetFlag(), SetBox(), and ParseResult() you can use whatever one is most suitable.

 

Hope that answers your questions.

 

What i,m really interested in though, is what, if any, could be problems in the future as the application grows.

 

Thanks again.

Link to comment
Share on other sites

A yes, I see now how the dynamic list is done, I hadn't noticed the .= in the box method.

 

My inquiry about the database call still stands though. What if you need to manipulate database results before displaying them? Like changing from timestamp to date format, or adding different columns into one?

Link to comment
Share on other sites

Hi

 

Originally, I just had the setbox and setflag methods, and these could still be used it you wanted to manipulate query data, for instance ..

 

Some method gets query results from database.

Manipulates data.

Then directly puts data into box with a combination of GetTemplate(); Parse Template internally(Could need another template method for this) and SetBox();.

 

But then I thought, it would be good to have a method acting directly on a result set for when the need arises.

 

L8rs

Link to comment
Share on other sites

Hi

 

Ok here,s the update ...

 

public function ParseArray($box, $file, $array) {

    if (is_array($array)) {
      $html = $this->GetTemplate($file);
      foreach ($array as $key => $value) {
        $datakey[$key] = "<DATA:$key />";
        $datavalue[$key] = $value;
      }
      $this->SetBox($box, str_replace($datakey, $datavalue, $html));
      unset ($datakey, $datavalue);
    } else {
      throw new Exception("Fatal Exception : ParseArray() requires 3rd Argument to be an array");
    }
    unset ($box, $html, $file, $row, $array);
  }

  public function ParseResult($box, $file) {

    $html = $this->GetTemplate($file);
    while ($row = $this->db->fetch_assoc()) {
      foreach ($row as $key => $value) {
        $datakey[$key] = "<DATA:$key />";
        $datavalue[$key] = $value;
      }
      $this->SetBox($box, str_replace($datakey, $datavalue, $html));
      unset ($datakey, $datavalue);
    }
    unset ($box, $html, $file, $row);
  }

 

Now, the question is, do I keep both functions or just the array function, as both these accomplish the same result...

 

while ($array = $db->result->fetch_assoc()) {
  $template->ParseArray('content','test.html', $array);
}

$array = array('User' => 'RecoilUK', 'Password' => 'inyourdreams');
$template->ParseArray('content','test.html', $array);

 

What do you think? actually, I guess I already know your answer.

 

Thanks

Link to comment
Share on other sites

This thread is more than a year old. Please don't revive it unless you have something important to add.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.