3

I found a question on stack overflow about loading a template file in php, which was fine, but I wish to loop a template file of one line, many times instead. The article I have read is here [PHP Content Separation. The best answer on that page was from prodigitalson with his function GetTemplate.

Now I wanted to use something like this, but if I put his function in a loop then it wouldn't be very efficient, as it would keep loading the same file many times.

So I tried this. I would get the html included. Then store it in a variable, before putting it in the loop. However it didn't work.

Here is my code. The data is already in an array called $result.

$salesTemp=$this->tempLine('salesTEM.php');
while($row = array_shift($result)):
    echo $salesTemp;
endwhile;

private function tempLine($file){
    ob_start();
    require $file;
    return ob_get_clean();
}

The problem is that my variable is not being updated in the template. Here is my template

<li class="list-group-item"><?php echo $Customer;?><span class="label label-primary pull-right">SALES</span></li>

So is there a way of re-writing this so my $Customer variable is updated.

I am doing this to try to keep php and html separate.

2
  • Perhaps its easier to use an already existing template engine such as Twig. It is able to use a template syntax {% for item in array %} <p>{{item.customer}}</p> {% end %} Others are available, for example comper. If you really want to build one your own with a script syntax you can take a look at lexing Commented Nov 21, 2016 at 14:02
  • 1
    I didn't want to use a template engine. I know there are a lot out there, but I prefer to solve things without other peoples engines. I am still working on this, and might come up with a solution soon, but it is good to know if a solution already exists. Commented Nov 21, 2016 at 14:04

3 Answers 3

1

The primary problem I see with the code as it stands is that when $salesTemp is declared and ran through the tempLine() method, a string is returned (due to ob_get_clean()). The variable within the template has been resolved, and when the string is echoed in the loop the variables in the template are not updated because they have already been resolved and processed into a string. To fix the situation I would:

while ($row = array_shift($result)) {

    echo $this->tempLine('salesTEM.php', $Customer);
}

/**
 * @return string
 */
private function tempLine($file, $Customer) {

    ob_start();
    require $file;
    return ob_get_clean();
}

This would be the shortest path to getting what you want. If you wish to not include the template on each iteration, try:

$salesTEM = include 'salesTEM.php';

while ($row = array_shift($result)) {

    echo sprintf($salesTEM, $Customer);
}

/**
 * salesTEM.php
 */
<li class="list-group-item">
    %s
    <span class="label label-primary pull-right">SALES</span>
</li>

There are many frameworks available that provide this functionality out of the box and may be perused for additional information on templating techniques. Essentially, it is always good form to pass the information in via the function (file name and data) and expect the string back out. It will make it easy to unit test as well. Allowing it to pick up the information passively tends to make the code error prone, for example:

while ($row = array_shift($result)) {

    echo include 'salesTEM.php';
}

/**
 * salesTEM.php
 */
<li class="list-group-item">
    <?php echo $Customer;?>
    <span class="label label-primary pull-right">SALES</span>
</li>

You could accidentally include the file and have not declared the $Customer variable resulting in a difficult to find bug. Define everything going in and coming out and it will make it much more manageable down the road.

Sign up to request clarification or add additional context in comments.

2 Comments

Problem with this solution is that you are including the file in every loop. However I think I have found a solution, just fleshing it out now.
If you do not want to include the partial on each iteration, you may just want to use sprintf() and use a %s placeholder for the customer since it is such a simple partial. Then you can define the string outside the loop and use if for each iteration.
0

You should really move the loop to inside the template file, seeing as you've already used PHP as the templating language inside of it. This will make things a whole lot easier for you, and give you the best possible performance (without rewriting the entire templating system).

As for the statement about keeping the HTML and PHP separate: It is actually not quite accurate, as it's a subtle rewrite from the original goal. Namely keeping the business code and the presentation code separate.
Normally, the presentation code is pure HTML, but in the case of dynamic sites the presentation code also requires dynamic elements. Most of the time this is solved by using a template engine that provides its own template language, but PHP can also be used as one. Matter of fact, PHP started as a template langauge. :)

So, using PHP in your views is perfectly fine. Provided said PHP code only controls output, and not business operations.

3 Comments

Strangely enough up to this point I was always putting the actual loop in html, but I have been reading that it is best to try and separate the code, which is what I have been attempting. I will have to go back to my old method if I can't find a better one. The other method is to put html in my php class which is something else I was trying to avoid.
@ThomasWilliams: I would go so far as to disagree with that statement. By having a loop inside the template, you are giving a clear indicator to the designer that there can (and will) be multiple users listed. If you hide that loop inside the business code, you are forcing the designer to read through it to be able to understand how the views are built. In short, instead of separating business code from presentation code, you're now moving the presentation code into the business code. Being pragmatic is much better than slavishly following an arbitrary principle after all. ;)
You are most probably correct. Still if there is an answer it would be nice to know.
0

I have an answer which works. However as ChristianF says it may not be the best answer, but this does solve my question.

Right instead of having an echo in the html I put the tag in %% like this

<li class="list-group-item">%CUSTOMER%<span class="label label-primary pull-right">SALES</span></li>

Then my loop in my class. I can build the object in the previous line for better readability or do it inline like I have.

$salesTemp=$this->tempLine('salesTEM.php');
while($row = array_shift($result)):
    echo $this->replaceTemp($salesTemp, $obj=(object)array ( 'CUSTOMER' => $row['CustomerName'] ));
endwhile;

and a small function

private function replaceTemp($file, $obj){
    return preg_replace('~%(\w+)%~e', '$obj->$1', $file);
}
private function tempLine($file){
    ob_start();
    require $file;
    return ob_get_clean();
}

The function basically replaces everything in the html where there are %% symbols with whatever is in the object.

UPDATE After posting this I have improved my answer, but I will leave the above there as it was my original answer. Below is a class which improves on the above answer. In the rTemplate function I can have one, or many tags that will be replaced in the template. For the example I only have one tag to replace, but you could add many. Running testLoad will list the array, grab the template and fill in the tags with what is in rTemplate.

class testlist{
    private $myfile;

    public function __construct(){
        $this->myfile=$this->loadTemplate('salesTEM.php');
    }

    public function testload(){
        // At this point the array is populated from a database, but I haven't shown this.
        // You can populate the array anyway you like.
        while($row = array_shift($result)):
            echo $this->rTemplate($row['CustomerName']);
        endwhile;

    }

    // This loads in a file into a string and then returns it.
    private function loadTemplate($file){
        ob_start();
        require $file;
        return ob_get_clean();
    }

    // This makes the object to replace keywords, then replaces them
    private function rTemplate($customer)
    {
        $obj = (object) array(
            'CUSTOMER' => $id           
        );
        return preg_replace('~%(\w+)%~e', '$obj->$1', $this->myfile);
    }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.