Before I go any further - critique please!

Coding Critique is the place to post source code for peer review by other members of DevNetwork. Any kind of code can be posted. Code posted does not have to be limited to PHP. All members are invited to contribute constructive criticism with the goal of improving the code. Posted code should include some background information about it and what areas you specifically would like help with.

Popular code excerpts may be moved to "Code Snippets" by the moderators.

Moderator: General Moderators

User avatar
s.dot
Tranquility In Moderation
Posts: 5001
Joined: Sun Feb 06, 2005 7:18 pm
Location: Indiana

Before I go any further - critique please!

Post by s.dot »

I'm building a template engine and I have the very basics done. Template loading, variable assigning, variable replacement, and displaying. Before I go any further (into looping, if/else/elseif logic) I want this critiqued so I can slim it down and make it as lightweight as possible (without compiling, just yet).

smtemplate.class.php

Code: Select all

<?php
 
/**
 * SMTemplate is a small and lightweight template engine.
 *
 * Quick & Dirty: Template Syntax
 *
 * Variable names in the template are in the form of { $var }.
 * Arrays can be be assigned to variables and are accessed in the form of 
 *   { $var.key }.  It can even be nested, { $var.key1.key2.key3 }.
 *
 * Quick & Dirty: PHP Syntax
 *
 * Instantiation:
 * require_once '/path/to/smtemplate.class.php';
 * $tpl = new SMTemplate();
 *
 * Variables are assigned via the assign() method:
 * $tpl->assign('variable', 'value'); OR an associative array can be passed:
 * $tpl->assign(array('var1' => 'val1', 'var2' => 'val2', 'var3' => 'val3'));
 *
 * Once variables are assigned, templates are rendered through the display() 
 * method: $tpl->display('path/to/template.tpl');
 *
 */
class SMTemplate
{
    /**
     * This holds the variables and values that need to be replaced in the
     * template file.  Keys are variable names, values are values.
     */
    private $vars = array();
    
    /**
     * This holds the raw template code and is manipulated to produce the 
     * final output.
     */
    private $templateCode;
    
    /**
     * This class method allows assigning of variable values to variable names.
     *
     * @param mixed $varName - the name of the variable or an associative array
     *    with keys as variable names and values as their replacement values.
     *
     * @param string $varValue - if assigning a single variable, this is the 
     *    value of that variable
     *
     * @acess public
     */
    public function assign($varName, $varValue=null)
    {
        if (is_array($varName) && ($varValue === null))
        {
            foreach ($varName AS $k => $v)
            {
                $this->vars[$k] = $v;
            }
        } else
        {
            $this->vars[$varName] = $varValue;
        }
    }
    
    /**
     * This class method simply loads the entire template file into a class
     * member.
     *
     * @param string $templateFilename - The filename of the template to load.
     * @access private
     */
    private function getTemplate($templateFilename)
    {
        if (is_file($templateFilename))
        {
            $this->templateCode = file_get_contents($templateFilename);
            return;
        }
        
        //template file could not be found, so we can't do anything ;<
        trigger_error(
            '<strong>SMTemplate Error:</strong> Could not locate template file (' . $templateFilename . ').',
            E_USER_ERROR
        );
    }
    
    /**
     * This class method replaces variables found in the template with their values.
     *
     * @access private
     */
    private function replaceVars()
    {
        //gather all variables
        preg_match_all('/\{\s\$([a-z0-9_\.]+)\s\}/i', $this->templateCode, $matches, PREG_SET_ORDER);
            
        //do we have any variables to replace?
        if (!empty($matches))
        {
            //okay we do, loop through them and replace
            foreach ($matches AS $match)
            {
                //replace array variables
                if (strpos($match[0], '.'))
                {
                    $varsArray      = explode('.', $match[1]);
                    $varArrayString = '$this->vars[\'' . $varsArray[0] . '\']';
                    array_shift($varsArray);
                    
                    foreach ($varsArray AS $varsArrayElement)
                    {
                        $varArrayString .= '[\'' . $varsArrayElement . '\']';
                    }
                    
                    if ($varValue = @eval("return $varArrayString;"))
                    {
                        $this->templateCode = str_replace($match[0], $varValue, $this->templateCode);
                    }
                } else
                {
                    //replace regular variables
                    if (isset($this->vars[$match[1]]))
                    {
                        $this->templateCode = str_replace($match[0], $this->vars[$match[1]], $this->templateCode);
                    }
                }
            }
        }
        
        //replace all left over variables with nothing
        $this->templateCode = preg_replace('/\{\s\$([a-z0-9_\.]+)\s\}/i', '', $this->templateCode);
    }
    
    /**
     * This method loads the template specified, replaces variables with their
     * values, and displays the modified template.
     *
     * @param string $templateFilename - the template filename
     * @access public
     */
    public function display($templateFilename)
    {
        $this->getTemplate($templateFilename);
        $this->replaceVars();
        echo $this->templateCode;
        unset($this->templateCode);
    }
}
A test file I'm using:

Code: Select all

<?php
 
//instantiate
require_once 'smtemplate.class.php';
$tpl = new SMTemplate();
 
//assign a 'regular' variable
$tpl->assign('foo', 'bar');
 
//assign an associative array
$tpl->assign(
    array(
        'testTitle' => 'title here yo!',
        'test' => 'single variable test',
        'testArray' => array('variable' => array('nested' => array('deep' => 'test array variable')))
    )
);
 
//display the template
$tpl->display('test.tpl');
Test template file:

Code: Select all

<html>
<head>
<title>{ $testTitle }</title>
</head>
<body>
<p><strong>Foo:</strong> { $foo }<br />
<strong>Test Variable:</strong> { $test }<br />
<strong>Test Array Variable:</strong> { $testArray.variable.nested.deep }</p>
<p>{ $testArray.not.valid.variable }</p>
</body>
</html>
Critiques welcomed and encouraged. Be harsh.

EDIT| The code on here stripped out my escaping \ before the 's in these lines:

Code: Select all

$varsArray      = explode('.', $match[1]);
                     $varArrayString = '$this->vars['' . $varsArray[0] . '']';
                     array_shift($varsArray);
                    
                     foreach ($varsArray AS $varsArrayElement)
                     {
                         $varArrayString .= '['' . $varsArrayElement . '']';
                     }
So be sure to add that in if you play with it.
Set Search Time - A google chrome extension. When you search only results from the past year (or set time period) are displayed. Helps tremendously when using new technologies to avoid outdated results.
User avatar
s.dot
Tranquility In Moderation
Posts: 5001
Joined: Sun Feb 06, 2005 7:18 pm
Location: Indiana

Re: Before I go any further - critique please!

Post by s.dot »

OK, it must be perfect. ;)
Set Search Time - A google chrome extension. When you search only results from the past year (or set time period) are displayed. Helps tremendously when using new technologies to avoid outdated results.
User avatar
pickle
Briney Mod
Posts: 6445
Joined: Mon Jan 19, 2004 6:11 pm
Location: 53.01N x 112.48W
Contact:

Re: Before I go any further - critique please!

Post by pickle »

  • You've spelled "access" wrong in your comments about the 'assign' function.
  • Not sure how much weight savings this will create, but if you only have one command after an if/elseif/else condition, you don't need curly braces.

    This:

    Code: Select all

    if(TRUE)
    {
       echo 'yay';
    }
    Can become:

    Code: Select all

    if(TRUE)
        echo 'yay';
  • What do you want to happen if someone passes assign() an array as the first argument, and NULL as the second. Currently you'll set $this->vars['array'] to NULL. I'd suggest that if the first argument is an array, disregard the 2nd argument entirely - don't care what it's value is (long story short, remove the $varValue===null condition)
  • You should return TRUE from getTemplate() if the template is loaded fine
  • Using regular expressions is very heavy. I imagine it would be more lightweight to just iterate through the variables you have set via assign() and do any replacements possible. Don't bother with the regex checking - just do the replacement with str_replace(). If a variable is found & replaced, great, if not, that's fine too.
  • Similarly, rather than examining the template string of { $var1.subvar2 }, build that string while iterating through the variables you've set, and just do a simple replacement. To be honest, that snippet you've got there looks a bit like a kludge.
Real programmers don't comment their code. If it was hard to write, it should be hard to understand.
User avatar
s.dot
Tranquility In Moderation
Posts: 5001
Joined: Sun Feb 06, 2005 7:18 pm
Location: Indiana

Re: Before I go any further - critique please!

Post by s.dot »

Wow, now that's the kind of critique I'm looking for!
# You've spelled "access" wrong in your comments about the 'assign' function.
Fixed. :) Good eye you have.
# Not sure how much weight savings this will create, but if you only have one command after an if/elseif/else condition, you don't need curly braces.
That's just a personal preference of mine. I like seeing the curly braces, doubt I'll be changing that.
# What do you want to happen if someone passes assign() an array as the first argument, and NULL as the second. Currently you'll set $this->vars['array'] to NULL. I'd suggest that if the first argument is an array, disregard the 2nd argument entirely - don't care what it's value is (long story short, remove the $varValue===null condition)
I thought about that. I don't know why someone would assign an array and add null as a second parameter, but hey! It could happen. I have removed that part.
# You should return TRUE from getTemplate() if the template is loaded fine
Absolutely. Good call.
# Using regular expressions is very heavy. I imagine it would be more lightweight to just iterate through the variables you have set via assign() and do any replacements possible. Don't bother with the regex checking - just do the replacement with str_replace(). If a variable is found & replaced, great, if not, that's fine too.
# Similarly, rather than examining the template string of { $var1.subvar2 }, build that string while iterating through the variables you've set, and just do a simple replacement. To be honest, that snippet you've got there looks a bit like a kludge.
Hmm. I'm not sure about this.

In my usual setup for my scripts I'd have a header.php, content.php, and footer.php file. In header.php I assign variables for the header template, then in the content I assign variables for the footer content template, and the same for the footer. If I simply looped through the variables I would be looping through a lot of unnecessary variables. But then again, I'd have to use preg_match_all() for all three of those files the current way I'm doing it.

So which is better?
Looping through all of the variables feels ugly, preg_match_all() on each template file feels heavy.
Set Search Time - A google chrome extension. When you search only results from the past year (or set time period) are displayed. Helps tremendously when using new technologies to avoid outdated results.
User avatar
s.dot
Tranquility In Moderation
Posts: 5001
Joined: Sun Feb 06, 2005 7:18 pm
Location: Indiana

Re: Before I go any further - critique please!

Post by s.dot »

OK, I think I'm going to go with the regexp matching for a few reasons:

* So I don't loop over variables that aren't necessary in each template. Things like configuration variables and blindly str_replace()'ing variables that aren't in the template is too ugly for me. :P

* I added a (basic) check to see if there's any variable-like syntax even in the template, so I can skip the regexp if I don't need to replace anything.

Code: Select all

private function gatherVars()
{
    if (strpos($this->templateCode, '{ $') !== false)
    {
        return preg_match_all('/\{\s\$([a-z0-9_\.]+)\s\}/i', $this->templateCode, $this->varsFound, PREG_SET_ORDER);
    }
}
* I made the unused variables, by default, just be shown in the template to avoid a call to preg_replace(). However, one can pass true as a second argument to $tpl->display(), and those will be removed from the template.

Here's the new and (hopefully) improved code:

Code: Select all

<?php
 
class SMTemplate
{
    private $vars = array();
    private $templateCode;
    private $varsFound;
    
    public function assign($varName, $varValue=null)
    {
        if (is_array($varName))
        {
            foreach ($varName AS $k => $v)
            {
                $this->vars[$k] = $v;
            }
        } else
        {
            $this->vars[$varName] = $varValue;
        }
    }
    
    private function getTemplate($templateFilename)
    {
        return (is_file($templateFilename) && ($this->templateCode = file_get_contents($templateFilename)));
    }
    
    private function gatherVars()
    {
        if (strpos($this->templateCode, '{ $') !== false)
        {
            return preg_match_all('/\{\s\$([a-z0-9_\.]+)\s\}/i', $this->templateCode, $this->varsFound, PREG_SET_ORDER);
        }
    }
 
    private function replaceVars()
    {
        foreach ($this->varsFound AS $var)
        {
            if (strpos($var[0], '.'))
            {
                $this->replaceNestedVar($var);
            } else
            {
                $this->replaceVar($var);
            }
        }
    }
 
    private function replaceNestedVar(&$var)
    {
        $varsArray      = explode('.', $var[1]);
        $varString  = '$this->vars[\'' . array_shift($varsArray) . '\']';
                
        foreach ($varsArray AS $varsElement)
        {
            $varString .= '[\'' . $varsElement . '\']';
        }
                
        if ($varValue = @eval("return $varString;"))
        {
            $this->templateCode = str_replace($var[0], $varValue, $this->templateCode);
        }
    }
    
    private function replaceVar(&$var)
    {
        if (isset($this->vars[$var[1]]))
        {
            $this->templateCode = str_replace($var[0], $this->vars[$var[1]], $this->templateCode);
        }
    }
    
    private function replaceUnusedVars()
    {
        $this->templateCode = preg_replace('/\{\s\$([a-z0-9_\.]+)\s\}/i', '', $this->templateCode);
    }
    
    public function clear()
    {
        $args = func_get_args();
        
        foreach ($args AS $arg)
        {
            unset($this->vars[$arg]);
        }
    }
    
    public function display($templateFilename, $replaceUnusedVars=false)
    {
        if (!$this->getTemplate($templateFilename))
        {
            trigger_error(
                '<strong>SMTemplate Error:</strong> Could not locate template file (' . $templateFilename . ').',
                E_USER_ERROR
            );
        }
        
        if ((bool) $this->gatherVars())
        {
            $this->replaceVars();
            
            if ($replaceUnusedVars)
            {
                $this->replaceUnusedVars();
            }
        }
        
        echo $this->templateCode;
        unset($this->templateCode); 
    }
}
I also added a clear() method to unset variables that will no longer be in use.

I think I'm ready to move on to my next step, which would be looping. It has given me fits in the past so I am looking forward to it!
Set Search Time - A google chrome extension. When you search only results from the past year (or set time period) are displayed. Helps tremendously when using new technologies to avoid outdated results.
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Re: Before I go any further - critique please!

Post by Christopher »

Can you show us what the templates look like?
(#10850)
User avatar
s.dot
Tranquility In Moderation
Posts: 5001
Joined: Sun Feb 06, 2005 7:18 pm
Location: Indiana

Re: Before I go any further - critique please!

Post by s.dot »

The test template I have been using is in the first post. :)
Set Search Time - A google chrome extension. When you search only results from the past year (or set time period) are displayed. Helps tremendously when using new technologies to avoid outdated results.
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Re: Before I go any further - critique please!

Post by Christopher »

I reworked your code to give some features that are important to me:
- to be able to use a different delimiter (I prefer just {var} to { $var })
- return template as a string rather than echoing it,
- allow you to render template multiple times without reloading,
- be able to pass the filename to the constructor.

I also collapsed all the code into the rendering method.

Code: Select all

<?php
 
class SMTemplate
{
    private $vars = array();
    private $templateStart = '{ $';
    private $templateEnd = ' }';
    private $templateFilename;
    private $templateCode;
    private $varsFound;
   
    public function __construct($templateFilename='')
    {
        $this->templateFilename = $templateFilename;
    }
   
    public function assign($varName, $varValue=null)
    {
        if (is_array($varName))
        {
            foreach ($varName AS $k => $v)
            {
                $this->vars[$k] = $v;
            }
        } else
        {
            $this->vars[$varName] = $varValue;
        }
    }
   
    private function setFilename($templateFilename)
    {
        if ($templateFilename)
        {
            $this->templateFilename = $templateFilename;
        }
    }
   
    private function load()
    {
        if ($this->templateFilename) {
            if (is_file($this->templateFilename)) {
                $this->templateCode = file_get_contents($this->templateFilename);
                // clear filename once loaded so we don't reload
                $this->templateFilename = '';
            }
            else
            {
                trigger_error(
                    '<strong>SMTemplate Error:</strong> Could not locate template file (' . $this->templateFilename . ').',
                    E_USER_ERROR
                );
            }
            }
    }
   
    public function clear()
    {
        $args = func_get_args();
       
        foreach ($args AS $arg)
        {
            unset($this->vars[$arg]);
        }
    }
   
    public function render($templateFilename='', $replaceUnusedVars=false)
    {
        $this->setFilename($templateFilename);
        $this->load();
       
        $varsFound = array();
        $out = $this->templateCode;
        if (strpos($out, $this->templateStart) !== false)
        {
            // escape regex symbols (should add ^, [, etc.)
            $regex = '/' . str_replace('$', '\$', $this->templateStart) . '([a-z0-9_\.]+)' . $this->templateEnd . '/i';
            preg_match_all($regex, $out, $varsFound, PREG_SET_ORDER);
        }
        if ($varsFound)
        {
            foreach ($varsFound AS $var)
            {
                if (strpos($var[0], '.'))
                {
                    $varsArray      = explode('.', $var[1]);
                    $varString  = '$this->vars[' . array_shift($varsArray) . ']';
                           
                    foreach ($varsArray AS $varsElement)
                    {
                        $varString .= '[' . $varsElement . ']';
                    }
                           
                    if ($varValue = @eval("return $varString;"))
                    {
                        $out = str_replace($var[0], $varValue, $out);
                    }
                }
                else
                {
                    $out = str_replace($var[0], $this->vars[$var[1]], $out);
                }
            }
                       
            if ($replaceUnusedVars)
            {
                $out = preg_replace($regex, '', $out);
            }
        }
       
        return $out;
    }
   
    public function display($templateFilename='', $replaceUnusedVars=false)
    {
        echo $this->render($templateFilename, $replaceUnusedVars);
    }
}
 
 
//instantiate
#require_once 'smtemplate.class.php';
$tpl = new SMTemplate('test.html');
 
//assign a 'regular' variable
$tpl->assign('foo', 'bar');
 
//assign an associative array
$tpl->assign(
    array(
        'testTitle' => 'title here yo!',
        'test' => 'single variable test',
        'testArray' => array('variable' => array('nested' => array('deep' => 'test array variable')))
    )
);
 
//display the template
echo $tpl->render();
(#10850)
User avatar
pickle
Briney Mod
Posts: 6445
Joined: Mon Jan 19, 2004 6:11 pm
Location: 53.01N x 112.48W
Contact:

Re: Before I go any further - critique please!

Post by pickle »

I'd modify clear() to allow it to receive an array as well.

I hadn't thought about the problem of iterating through variables you're not using - to be honest that never comes up for me. When I have multiple templates, I use my template object to call, for example, page.tpl. page.tpl then includes head.tpl and foot.tpl. I'd imagine the parser then parses through the result & replaces any variables it finds.

So... I did my own version of SMTemplate without using regex & it's much faster. ~arborint's code renders 1000 of my templates in about 0.27 seconds. My code renders 1000 of the same template in about 0.1 seconds.

Rather than pasting the whole class, here's just the relevant functions. These two functions replace render()

Code: Select all

   public function render($templateFilename='', $replaceUnusedVars=false)
    {
        $this->setFilename($templateFilename);
        $this->load();
       
        $out = $this->templateCode;
        
        if(strpos($out,$this->templateStart))
        {
            foreach($this->vars as $name=>$value)
            {
                $out = $this->replace($name,$value,$out);
            }
        }
        
        return $out;
    }
    
    private function replace($running_replace,$value,$out)
    {
        if(is_string($value))
        {
            $name = $this->templateStart.$running_replace.$this->templateEnd;
            
            if(strpos($out,$name)!== FALSE)
            {
                $out = str_replace($name,$value,$out);
            }
        }
        else
        {
            foreach($value as $sub_replace=>$sub_value)
            {
                $out = $this->replace($running_replace.'.'.$sub_replace,$sub_value,$out);
            }           
        }
 
        return $out;
 
    }
Here's the file I used test both classes

Code: Select all

<?PHP
require('SMTemplate.php');
require('SMTemplate2.php');
 
$tpl = new SMTemplate('test.html');
$tpl2 = new SMTemplate2('test.html');
 
//assign a 'regular' variable
$tpl->assign('foo', 'bar');
$tpl2->assign('foo', 'bar');
 
//assign an associative array
$tpl->assign(
    array(
        'testTitle' => 'title here yo!',
        'test' => 'single variable test',
        'testArray' => array('variable' => array('nested' => array('deep' => 'test array variable')))
    )
);
 
$tpl2->assign(
    array(
        'testTitle' => 'title here yo!',
        'test' => 'single variable test',
        'testArray' => array('variable' => array('nested' => array('deep' => 'test array variable')))
    )
);
 
//display the template
$iterations = 1000;
 
$start1 = microtime(TRUE);
for($i = 0;$i<$iterations;++$i)
{
    $result1 = $tpl->render();
}
$elapsed1 = microtime(TRUE) - $start1;
 
$start2 = microtime(TRUE);
for($i = 0;$i<$iterations;++$i)
{
    $result2 = $tpl2->render();
}
$elapsed2 = microtime(TRUE) - $start2;
 
 
echo <<<DUMP
1: $elapsed1<br />
2: $elapsed2
DUMP;
 
?>
And finally, here's my template. I added some lorem ipsum & included the variables a few more times - to hopefully get closer to a real-word situation.

Code: Select all

<html>
    <head>
        <title>
            { $testTitle }
        </title>
    </head>
    <body>
        Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Praesent gravida sodales mi. Phasellus tristique, neque id gravida blandit, mi urna lobortis est, sit amet vulputate velit lectus ut dolor. Curabitur accumsan metus in lectus. Praesent sit amet felis. Vestibulum ullamcorper. Nam ultricies diam eget pede. Maecenas ac dui scelerisque nulla tempor consectetuer. Pellentesque ligula. Nunc erat tellus, euismod volutpat, sodales non, consectetuer ut, est. Duis posuere, massa in faucibus faucibus, justo nisi tristique diam, ut ultricies lorem purus nec lectus. Nunc sodales fringilla purus. Aliquam sit amet justo. Etiam mollis velit quis tortor. Aliquam vestibulum, diam eget pellentesque vulputate, odio arcu fringilla dui, sit amet malesuada metus neque quis eros. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Proin vel elit sit amet purus elementum viverra. Donec egestas faucibus felis. Integer hendrerit. Donec vel ipsum. In venenatis volutpat erat.
        
        Nulla pharetra dapibus orci. Praesent felis libero, dignissim vitae, varius sed, varius vel, felis. Pellentesque sapien. Cras hendrerit pharetra velit. Vestibulum nisl odio, congue at, scelerisque nec, vehicula sit amet, turpis. Ut arcu quam, congue nec, dapibus sed, egestas quis, lacus. Nunc in diam vel nulla feugiat tempus. Donec non lectus. Nunc ac turpis. Morbi commodo ante a massa.
        { $bar }
        In nec velit. Ut vitae erat. Ut pulvinar accumsan nulla. Suspendisse eget purus sit amet massa sodales vehicula. Integer adipiscing mi sed risus. Aliquam justo eros, convallis pulvinar, dictum nec, consectetuer a, urna. Fusce mollis. Phasellus purus mi, placerat sed, tempor a, rutrum sit amet, nulla. Sed lobortis orci sit amet orci. Sed in diam. Nam rhoncus mollis sapien. Curabitur mauris. Duis in lectus quis massa volutpat sodales. Nam pretium vulputate mi. Donec quam nisl, accumsan a, commodo eu, mollis pulvinar, purus. Donec tincidunt neque vel justo. Cras at eros sed orci luctus laoreet.
        
        In vitae quam. Aliquam dictum pulvinar elit. Vestibulum eget nulla. Nulla congue pulvinar nibh. Cras quis erat eu ligula molestie egestas. Vestibulum eget mi. Suspendisse tempor laoreet metus. Suspendisse convallis lacus mollis libero. Sed rhoncus lacinia turpis. Morbi est nisi, sagittis at, sollicitudin nec, eleifend non, nibh. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus luctus sollicitudin metus. Duis nisi. Sed vestibulum odio. Donec volutpat bibendum ligula.
        
        Aliquam erat volutpat. Vivamus sed pede. In pede. Nullam nec lorem non nisi aliquet auctor. In hac habitasse platea dictumst. Cras condimentum tincidunt justo. Quisque posuere varius nibh. Donec sit amet risus. Nam elementum. Morbi iaculis eros ut risus. Integer et lacus. Praesent id urna id odio eleifend egestas. In fermentum lectus ut elit. Proin vehicula, sapien sed tempus dictum, massa tortor fringilla massa, sed facilisis lectus orci sit amet dolor. 
        <p><strong>Foo:</strong> { $foo }<br />
        Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Praesent gravida sodales mi. Phasellus tristique, neque id gravida blandit, mi urna lobortis est, sit amet vulputate velit lectus ut dolor. Curabitur accumsan metus in lectus. Praesent sit amet felis. Vestibulum ullamcorper. Nam ultricies diam eget pede. Maecenas ac dui scelerisque nulla tempor consectetuer. Pellentesque ligula. Nunc erat tellus, euismod volutpat, sodales non, consectetuer ut, est. Duis posuere, massa in faucibus faucibus, justo nisi tristique diam, ut ultricies lorem purus nec lectus. Nunc sodales fringilla purus. Aliquam sit amet justo. Etiam mollis velit quis tortor. Aliquam vestibulum, diam eget pellentesque vulputate, odio arcu fringilla dui, sit amet malesuada metus neque quis eros. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Proin vel elit sit amet purus elementum viverra. Donec egestas faucibus felis. Integer hendrerit. Donec vel ipsum. In venenatis volutpat erat.
        
        Nulla pharetra dapibus orci. Praesent felis libero, dignissim vitae, varius sed, varius vel, felis. Pellentesque sapien. Cras hendrerit pharetra velit. Vestibulum nisl odio, congue at, scelerisque nec, vehicula sit amet, turpis. Ut arcu quam, congue nec, dapibus sed, egestas quis, lacus. Nunc in diam vel nulla feugiat tempus. Donec non lectus. Nunc ac turpis. Morbi commodo ante a massa.
        
        In nec velit. Ut vitae erat. Ut pulvinar accumsan nulla. Suspendisse eget purus sit amet massa sodales vehicula. Integer adipiscing mi sed risus. Aliquam justo eros, convallis pulvinar, dictum nec, consectetuer a, urna. Fusce mollis. Phasellus purus mi, placerat sed, tempor a, rutrum sit amet, nulla. Sed lobortis orci sit amet orci. Sed in diam. Nam rhoncus mollis sapien. Curabitur mauris. Duis in lectus quis massa volutpat sodales. Nam pretium vulputate mi. Donec quam nisl, accumsan a, commodo eu, mollis pulvinar, purus. Donec tincidunt neque vel justo. Cras at eros sed orci luctus laoreet.
        
        In vitae quam. Aliquam dictum pulvinar elit. Vestibulum eget nulla. Nulla congue pulvinar nibh. Cras quis erat eu ligula molestie egestas. Vestibulum eget mi. Suspendisse tempor laoreet metus. Suspendisse convallis lacus mollis libero. Sed rhoncus lacinia turpis. Morbi est nisi, sagittis at, sollicitudin nec, eleifend non, nibh. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus luctus sollicitudin metus. Duis nisi. Sed vestibulum odio. Donec volutpat bibendum ligula.
        
        Aliquam erat volutpat. Vivamus sed pede. In pede. Nullam nec lorem non nisi aliquet auctor. In hac habitasse platea dictumst. Cras condimentum tincidunt justo. Quisque posuere varius nibh. Donec sit amet risus. Nam elementum. Morbi iaculis eros ut risus. Integer et lacus. Praesent id urna id odio eleifend egestas. In fermentum lectus ut elit. Proin vehicula, sapien sed tempus dictum, massa tortor fringilla massa, sed facilisis lectus orci sit amet dolor. 
        <strong>Test Variable:</strong> { $test }<br />
        Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Praesent gravida sodales mi. Phasellus tristique, neque id gravida blandit, mi urna lobortis est, sit amet vulputate velit lectus ut dolor. Curabitur accumsan metus in lectus. Praesent sit amet felis. Vestibulum ullamcorper. Nam ultricies diam eget pede. Maecenas ac dui scelerisque nulla tempor consectetuer. Pellentesque ligula. Nunc erat tellus, euismod volutpat, sodales non, consectetuer ut, est. Duis posuere, massa in faucibus faucibus, justo nisi tristique diam, ut ultricies lorem purus nec lectus. Nunc sodales fringilla purus. Aliquam sit amet justo. Etiam mollis velit quis tortor. Aliquam vestibulum, diam eget pellentesque vulputate, odio arcu fringilla dui, sit amet malesuada metus neque quis eros. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Proin vel elit sit amet purus elementum viverra. Donec egestas faucibus felis. Integer hendrerit. Donec vel ipsum. In venenatis volutpat erat.
        
        Nulla pharetra dapibus orci. Praesent felis libero, dignissim vitae, varius sed, varius vel, felis. Pellentesque sapien. Cras hendrerit pharetra velit. Vestibulum nisl odio, congue at, scelerisque nec, vehicula sit amet, turpis. Ut arcu quam, congue nec, dapibus sed, egestas quis, lacus. Nunc in diam vel nulla feugiat tempus. Donec non lectus. Nunc ac turpis. Morbi commodo ante a massa.
        
        In nec velit. Ut vitae erat. Ut pulvinar accumsan nulla. Suspendisse eget purus sit amet massa sodales vehicula. Integer adipiscing mi sed risus. Aliquam justo eros, convallis pulvinar, dictum nec, consectetuer a, urna. Fusce mollis. Phasellus purus mi, placerat sed, tempor a, rutrum sit amet, nulla. Sed lobortis orci sit amet orci. Sed in diam. Nam rhoncus mollis sapien. Curabitur mauris. Duis in lectus quis massa volutpat sodales. Nam pretium vulputate mi. Donec quam nisl, accumsan a, commodo eu, mollis pulvinar, purus. Donec tincidunt neque vel justo. Cras at eros sed orci luctus laoreet.
        { $foo }
        In vitae quam. Aliquam dictum pulvinar elit. Vestibulum eget nulla. Nulla congue pulvinar nibh. Cras quis erat eu ligula molestie egestas. Vestibulum eget mi. Suspendisse tempor laoreet metus. Suspendisse convallis lacus mollis libero. Sed rhoncus lacinia turpis. Morbi est nisi, sagittis at, sollicitudin nec, eleifend non, nibh. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus luctus sollicitudin metus. Duis nisi. Sed vestibulum odio. Donec volutpat bibendum ligula.
        
        Aliquam erat volutpat. Vivamus sed pede. In pede. Nullam nec lorem non nisi aliquet auctor. In hac habitasse platea dictumst. Cras condimentum tincidunt justo. Quisque posuere varius nibh. Donec sit amet risus. Nam elementum. Morbi iaculis eros ut risus. Integer et lacus. Praesent id urna id odio eleifend egestas. In fermentum lectus ut elit. Proin vehicula, sapien sed tempus dictum, massa tortor fringilla massa, sed facilisis lectus orci sit amet dolor. 
        <strong>Test Array Variable:</strong> { $testArray.variable.nested.deep }</p>
        Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Praesent gravida sodales mi. Phasellus tristique, neque id gravida blandit, mi urna lobortis est, sit amet vulputate velit lectus ut dolor. Curabitur accumsan metus in lectus. Praesent sit amet felis. Vestibulum ullamcorper. Nam ultricies diam eget pede. Maecenas ac dui scelerisque nulla tempor consectetuer. Pellentesque ligula. Nunc erat tellus, euismod volutpat, sodales non, consectetuer ut, est. Duis posuere, massa in faucibus faucibus, justo nisi tristique diam, ut ultricies lorem purus nec lectus. Nunc sodales fringilla purus. Aliquam sit amet justo. Etiam mollis velit quis tortor. Aliquam vestibulum, diam eget pellentesque vulputate, odio arcu fringilla dui, sit amet malesuada metus neque quis eros. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Proin vel elit sit amet purus elementum viverra. Donec egestas faucibus felis. Integer hendrerit. Donec vel ipsum. In venenatis volutpat erat.
        
        Nulla pharetra dapibus orci. Praesent felis libero, dignissim vitae, varius sed, varius vel, felis. Pellentesque sapien. Cras hendrerit pharetra velit. Vestibulum nisl odio, congue at, scelerisque nec, vehicula sit amet, turpis. Ut arcu quam, congue nec, dapibus sed, egestas quis, lacus. Nunc in diam vel nulla feugiat tempus. Donec non lectus. Nunc ac turpis. Morbi commodo ante a massa.
        { $bar } { $bar }
        In nec velit. Ut vitae erat. Ut pulvinar accumsan nulla. Suspendisse eget purus sit amet massa sodales vehicula. Integer adipiscing mi sed risus. Aliquam justo eros, convallis pulvinar, dictum nec, consectetuer a, urna. Fusce mollis. Phasellus purus mi, placerat sed, tempor a, rutrum sit amet, nulla. Sed lobortis orci sit amet orci. Sed in diam. Nam rhoncus mollis sapien. Curabitur mauris. Duis in lectus quis massa volutpat sodales. Nam pretium vulputate mi. Donec quam nisl, accumsan a, commodo eu, mollis pulvinar, purus. Donec tincidunt neque vel justo. Cras at eros sed orci luctus laoreet.
        
        In vitae quam. Aliquam dictum pulvinar elit. Vestibulum eget nulla. Nulla congue pulvinar nibh. Cras quis erat eu ligula molestie egestas. Vestibulum eget mi. Suspendisse tempor laoreet metus. Suspendisse convallis lacus mollis libero. Sed rhoncus lacinia turpis. Morbi est nisi, sagittis at, sollicitudin nec, eleifend non, nibh. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus luctus sollicitudin metus. Duis nisi. Sed vestibulum odio. Donec volutpat bibendum ligula.
        { $foo }
        Aliquam erat volutpat. Vivamus sed pede. In pede. Nullam nec lorem non nisi aliquet auctor. In hac habitasse platea dictumst. Cras condimentum tincidunt justo. Quisque posuere varius nibh. Donec sit amet risus. Nam elementum. Morbi iaculis eros ut risus. Integer et lacus. Praesent id urna id odio eleifend egestas. In fermentum lectus ut elit. Proin vehicula, sapien sed tempus dictum, massa tortor fringilla massa, sed facilisis lectus orci sit amet dolor. 
        
        <p>{ $testArray.not.valid.variable }</p>
    </body>
</html>
Real programmers don't comment their code. If it was hard to write, it should be hard to understand.
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Re: Before I go any further - critique please!

Post by Christopher »

Yes, I use the same str_replace() scheme that you do. However, I think scottayy has built some additional functionality to deal with arrays that the str_replace() scheme does not handle. I don't use that functionality, and it sounds like you don't either, but that's why the implementations are different.
(#10850)
User avatar
pickle
Briney Mod
Posts: 6445
Joined: Mon Jan 19, 2004 6:11 pm
Location: 53.01N x 112.48W
Contact:

Re: Before I go any further - critique please!

Post by pickle »

The recursive call to replace() takes care of arrays - or were you thinking of something else?
Real programmers don't comment their code. If it was hard to write, it should be hard to understand.
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Re: Before I go any further - critique please!

Post by Christopher »

pickle wrote:The recursive call to replace() takes care of arrays - or were you thinking of something else?
He allows {$foo.bar.baz} to reference $foo['bar']['baz'] using eval().
(#10850)
User avatar
pickle
Briney Mod
Posts: 6445
Joined: Mon Jan 19, 2004 6:11 pm
Location: 53.01N x 112.48W
Contact:

Re: Before I go any further - critique please!

Post by pickle »

Ah - right. Different technique - same result.
Real programmers don't comment their code. If it was hard to write, it should be hard to understand.
User avatar
s.dot
Tranquility In Moderation
Posts: 5001
Joined: Sun Feb 06, 2005 7:18 pm
Location: Indiana

Re: Before I go any further - critique please!

Post by s.dot »

This is interesting! You guys use templates in a much different way than I do.

I like to stack the variables in the template BEFORE calling display(). My usually header,content, footer each call display() but the template engine is only instantiated in header(). (well not really in header, but in a bootstrap include)

Therefore, lots of variables are in the template class member that aren't needed... which is why I added the clear() method and didn't want to iterate over lots of unused variables. But I like the if (strpos()) thingy to check if they're used. Nice!

I also like the being able to change the delimeters. Freaking sweeettt!

I use being able to pass arrays as as a value because I can do something like this

Code: Select all

$tpl->assign('memberData', $memberRow);

Code: Select all

<p>Member Details: Username: { $memberData.username }, Location: { $memberData.location }, Name { $memberData.name.last }, { $memberData.name.first }</p>
I think I'm going to rework this with your guys methods in mind!
Set Search Time - A google chrome extension. When you search only results from the past year (or set time period) are displayed. Helps tremendously when using new technologies to avoid outdated results.
User avatar
s.dot
Tranquility In Moderation
Posts: 5001
Joined: Sun Feb 06, 2005 7:18 pm
Location: Indiana

Re: Before I go any further - critique please!

Post by s.dot »

OK! here we go..

* Added the ability to pass the template filename to the constructor. If you don't set it in the constructor (my preferred method), you can set it as the first argument of display().

Soo

Code: Select all

$tpl = new SMTemplate('test.tpl');
//assigning
$tpl->display();

Code: Select all

$tpl = new SMTemplate();
//assigning
$tpl->display('test.tpl');
will both work.

* Added the ability to change the variable syntax. setStartDelimeter() and setEndDelimiter() methods achieve this.
* Got rid of the regex by using pickle's recursive solution. (however I used if (!is_array()) instead of if (is_string()) because integers and floats are not strings but still need replaced.
* Added the ability to pass true as a second parameter to display to return the modified template as a string rather than echo it.
* Copied the template into the $out variable in render(), to allow multiple modifications of the raw template code held in class member templateCode
* Checks for strpos() instead of blindly str_replace()'ing.

Here's the new much sexier code:

Code: Select all

<?php
 
class SMTemplate
{
    private $delimStart = '{ $';
    private $delimEnd = ' }';
    private $vars = array();
    private $templateFilename = '';
    private $templateCode;
    
    public function __construct($templateFilename='')
    {
        if (!empty($templateFilename))
        {
            $this->templateFilename = $templateFilename;
        }
    }
    
    public function assign($var, $value=null)
    {
        if (is_array($var))
        {
            foreach ($var AS $k => $v)
            {
                $this->vars[$k] = $v;
            }
        } else
        {
            $this->vars[$var] = $value;
        }
    }
    
    public function setStartDelimeter($startDelim)
    {
        $this->delimStart = $startDelim;
    }
    
    public function setEndDelimeter($endDelim)
    {
        $this->delimEnd = $endDelim;
    }
    
    public function clear()
    {
        $args = func_get_args();
        
        foreach ($args AS $arg)
        {
            unset($arg);
        }
    }
    
    private function getTemplate(&$templateFilename)
    {
        return (is_file($templateFilename) && ($this->templateCode = file_get_contents($templateFilename)));
    }
    
    private function render()
    {
        $out = $this->templateCode;
        
        if (strpos($out, $this->delimStart) !== false)
        {
            foreach ($this->vars AS $k => $v)
            {
                $out = $this->replace($k, $v, $out);
            }
        }
        
        return $out;
    }
    
    private function replace($varName, $varValue, &$out)
    {
        if (!is_array($varValue))
        {
            $varToReplace = $this->delimStart . $varName . $this->delimEnd;
            
            if (strpos($out, $varToReplace) !== false)
            {
                $out = str_replace($varToReplace, $varValue, $out);
            }
        } else
        {
            foreach ($varValue AS $k => $v)
            {
                $out = $this->replace($varName . '.' . $k, $v, $out);
            }
        }
        
        return $out;
    }
    
    public function display($templateFilename='', $return=false)
    {
        if (empty($this->templateFilename))
        {
            $this->templateFilename = $templateFilename;
        }
        
        if (!$this->getTemplate($this->templateFilename))
        {
            trigger_error(
                '<strong>SMTemplate:</strong> Could not load template file (' . $templateFilename . ').',
                E_USER_ERROR
            );
        }
        
        if ($return)
        {
            return $this->render();
        }
        
        echo $this->render();
    }
}
Set Search Time - A google chrome extension. When you search only results from the past year (or set time period) are displayed. Helps tremendously when using new technologies to avoid outdated results.
Post Reply