4
\$\begingroup\$

This is a very basic PHP class that I am working on which will allow me to provide an HTML template in the form of a string saved to a variable or into an actual template file. I can then pass into my PHP class an array where the array KEYS will be used to find and replace variables in the template HTML.

The template will define variables in this format {{first_name}}, so my PHP array would have a key of first_name and its value would be parsed into the final result HTML.

Example PHP array used to set and replace variables in the templates HTML...

$emailValues = array(
    'first_name' => 'Jason',
    'last_name' => 'Davis'
);

This PHP array would then find and replace all occurrences of {{first_name}} with Jason and {{last_name}} with Davis

I have coded this functionality into countless projects over the years but I generally have defined every single variable that get replaced in the template manually.

Purpose

I wanted a reusable method of doing this and even better, the flexibility to not have to define what variable are allowed in a template. With this class I can now use it in any project and use any variable I want in a template with {{varName}} instead of being confined to a list of variables. It can now have unlimited variables and I don't have to manually set each one up. Simply call the variable in my template and assign a matching KEY name in my PHP array of replacement values. I love the concept of it!


PHP Email Template Parsing Class

<?php

/**
 * Email Template Parser
 *
 * Create HTML email body while replacing variable placeholders with content using a PHP ARRAY.
 * @package
 * @author Jason Davis
 * @version 1.0
 * @copyright 2015
 */
class EmailParser {

    protected $_openingTag = '{{';
    protected $_closingTag = '}}';
    protected $_emailValues;
    protected $_emailTemplate;
    protected $_isTemplateFile = false;

    /**
     * Email Template Parser Class
     * @param array $emailValues ARRAY where the KEY is the Variable name in the template and the VALUE is the replacement value.
     * @param string $emailTemplate HTML template string OR File path to a Email Template file.
     * @param boolean $isTemplateFile Optional - (Default: FALSE) - If set to TRUE, $emailTemplate is expected to hold the HTML template string.  If set to FALSE, $emailTemplate is expected to be a File Path to external file containing email template string.
     * @return string HTML with any matching variables {{varName}} replaced with their values.
     */
    public function __construct($emailValues, $emailTemplate, $isTemplateFile = false) {
        $this->_emailValues = $emailValues;
        $this->_isTemplateFile = $isTemplateFile;

        if($this->_isTemplateFile){
            // Template HTML is stored in a File
            try
            {
                if(file_exists($emailTemplate)){
                    $this->_emailTemplate = file_get_contents($emailTemplate);
                }else{
                    throw new Exception('ERROR: Invalid Email Template Filepath');
                }
            }
            catch(Exception $e)
            {
                echo $e->getMessage(). ' | FILE: '.$e->getFile(). ' | LINE: '.$e->getLine();
            }
        }else{
            try
            {
                // Template HTML is stored in-line in the $emailTemplate property!
                if(is_string($emailTemplate)){
                    $this->_emailTemplate = $emailTemplate;
                }else{
                    throw new Exception('ERROR: Invalid Email Template.  $emailTemplate must be a String');
                }
            }
            catch(Exception $e)
            {
                echo $e->getMessage(). ' | FILE: '.$e->getFile(). ' | LINE: '.$e->getLine();
            }
        }
    }


    /**
     * Returns the Parsed Email Template
     * @return string HTML with any matching variables {{varName}} replaced with there values.
     */
    public function output() {
        $html = $this->_emailTemplate;
        foreach ($this->_emailValues as $key => $value) {
            if(isset($value) && $value != ''){
                $html = str_replace($this->_openingTag . $key . $this->_closingTag, $value, $html);
            }
        }
        return $html;
    }
}

Example Usage using a variable to hold the Email Template HTML

$emailTemplateInline = <<<HTML
<html>
<body>
<h1>Account Details Using Inline Template</h1>
<p>Thank you for registering on our site, your account details are as follows:<br>
Username: {{username}}<br>
Password: {{password}} </p>
</body>
</html>
HTML;

$emailValues = array(
    'username' => 'My username value here',
    'password' => ''
);


$emailHtml = new EmailParser($emailValues, $emailTemplateInline, false);
echo $emailHtml->output();

Example Usage using an external File to hold the Email Template HTML

$emailTemplate =  'email-template.tpl';

$emailValues = array(
    'username' => 'My username value here',
    'password' => ''
);

$emailHtml = new EmailParser($emailValues, $emailTemplate, true);
echo $emailHtml->output();

email-template.tpl

<html>
<body>
<h1>Account Details Using External Template File</h1>
<p>Thank you for registering on our site, your account details are as follows:<br>
Username: {{username}}<br>
Password: {{password}} </p>
</body>
</html>

Room for improvement

There is always room for improvement and 100 different ways to get your outcome when it comes to programming. With that said, I would like to hear some preferred ways of improving this very basic script.

This code works just fine so far in my testing but I always feel things can be improved and I do plan to use this across several large scale projects in the future so I want to identify and fix/improve any possible issues now if I can before it arises and issue once running on a live app!

To make it a little more flexible I decided to not require the Template HTML to be in an actual external template file. So now as an option, it can be a template file or else a string stored in a variable for more smaller 1 file type projects.

Another thing I tried to deal with is when a {{varName}} variable placeholder is in the template but does not have a matching PHP array value. For this I am simply doing if(isset($value) && $value != ''){} in the loop that is looking for placeholders in the template. I am sure there might be some better clever method.

It would be really cool if I could possibly have optional error mode which could build an array of any missing variables that are not being set and print that out to show that these variables are not being passed into the array.

I am mainly looking for you input for:

  • Improvements to any of the code sections
  • Ideas for additional settings and features
\$\endgroup\$
2
\$\begingroup\$

Yes, this is similar to what I do.

I haven't restricted my class to emails, I can use it on any string or template file. You could do that too. There's no good reason for the restriction you impose.

I have noticed that you provide all the arguments when you make the parser object. Is that wise? Think about it:

1: If you were to supply the $emailTemplate to output() you could reuse the class for different template strings.

2: Or if you supplied the $emailValues to output() you could change them up to the point where the output is needed.

In other words, don't supply all arguments when you make the class, supply them when they're really needed. It means you don't need to store them in the object, and you can reuse the object. In this case I would choose to supply the $emailValues to the __construct since they probably won't change, and use one object for all email templates.

Instead of using an array, outside of this class, to collect all the $emailValues, you could create the parser object first, and use a method to attach template variables to it. This means you don't have to store them in two places:

public function setVar($name,$value)

Now you're really starting to use the object and you're compartmentalizing methods and variables in a class.

I like the way you define the opening and closing tags, instead of using '{{' and '}}' every time. You could turn these into real constants, by using define(), or make them programmable. You have a sort of in between solution, they're not constant, nor programmable.

You store $isTemplateFile in the object, but you don't need to.

You could split you long __construct() into two methods: One for reading a file, and one for accepting a template string. Notice that one could be used by the other!

\$\endgroup\$
  • \$\begingroup\$ Thanks for the feedback, I have been thinking about adding other setter type methods instead of requiring it all set and injected through the constructor as you mentioned so your comments on that are the push I needed to go that route! With all my years of experience, it still feels "wrong" doing things in code sometimes as there are soo many routes that it can be done in! So it is nice to have feedback from other developers. \$\endgroup\$ – JasonDavis Mar 10 '15 at 3:21
  • \$\begingroup\$ In regards to a setVar($name,$value) method. I was sort of on the fence about doing that or not but I do like the idea so here is my first attempt in this Gist/codepen I have added the setVar() method. I am not a huge fan of requiring each variable to be set by a call to that function though. My reason is in the project I just used this code in, my array of key/values was already setup and being used in the project as it was passed to an API call....(continued...) \$\endgroup\$ – JasonDavis Mar 10 '15 at 3:22
  • \$\begingroup\$ ...(continued) So allowing an array to set the key/values in the template allowed me to simply pass my existing array and just make sure my template string used the same names! So in my Gist above you can see the new setVar() method allows both methods to be used...You can pass a single argument which is an Array or pass 2 arguments which are a key and a value. I like it to be flexible like that and auto detect it! Any comments on this new method and how it functions? \$\endgroup\$ – JasonDavis Mar 10 '15 at 3:22
  • \$\begingroup\$ As for the $isTemplateFile flag, I didn't "love" the solution but it was the best method I could think of at the moment when doing this. So I am open to ideas for this as well. I would love to somehow auto detect if the template string is stored in a variable or is stored in an external file somewhere, any ideas on how to best implement that functionality? \$\endgroup\$ – JasonDavis Mar 10 '15 at 3:23
  • \$\begingroup\$ If you have already an array you could add setVars() as a method. I would not combine this function with setVar(), as suggested. When discussing the $isTemplateFile flag I meant that you store it in the class, but you don't need that stored argument. \$\endgroup\$ – KIKO Software Mar 10 '15 at 8:14
1
\$\begingroup\$

Nice code. Looks great, but...

My answer is also a question: Why not use a template lib instead?

There're several template libs out there that you can use them through you project.

I'd recommend you to use Twig (http://twig.sensiolabs.org/doc/intro.html).

Using a lib reduce your work on things there were made by someone (parsing string template, loading it, trowing some exception and everything else).

\$\endgroup\$
  • \$\begingroup\$ Mainly all those libraries are much larger and more complex than I need. This single PHP Class can be dropped into lightweight projects as needed to do it;s 1 job. That is my main reason. Most libraries are trying to be flexible enough to handle everybody's needs which leads to them being bloated and larger. This simple small class handles the 1 need it is supposed to and that is all! BTW I have re-written the whole thing it is now here github.com/jasondavis/PHP-Email-Template-Parser-Class \$\endgroup\$ – JasonDavis Mar 11 '15 at 15:39

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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