Функция электронной почты с использованием шаблонов. Включает через ob_start и глобальные переменные

У меня есть простой класс Email(). Он используется для отправки писем с моего сайта.

<?
Email::send($to, $subj, $msg, $options);
?>

У меня также есть куча шаблонов электронной почты, написанных на простом HTML, пронизанном несколькими переменными PHP. Например. /inc/email/templates/account_created.php:

<p>Dear <?=$name?>,</p>
<p>Thank you for creating an account at <?=$SITE_NAME?>. To login use the link below:</p>
<p><a href="https://<?=$SITE_URL?>/account" target="_blank"><?=$SITE_NAME?>/account</a></p>

Чтобы отобразить переменные PHP, мне пришлось include интегрировать шаблон в свою функцию. Но так как include не возвращает содержимое, а просто отправляет его прямо на выход, мне пришлось обернуть его функциями буфера:

<?
abstract class Email {
    public static function send($to, $subj, $msg, $options = array()) {
        /* ... */
        ob_start();
        include '/inc/email/templates/account_created.php';
        $msg = ob_get_clean();
        /* ... */
    }
}

После этого я понял, что переменные PHP не отображаются, поскольку они находятся внутри области действия функции, поэтому мне пришлось глобализовать переменные внутри шаблона:

<?
global $SITE_NAME, $SITE_URL, $name;
?>
<p>Dear <?=$name?>,</p>
...

Итак, вопрос в том, есть ли более элегантное решение для этого? В основном меня беспокоят мои обходные пути с использованием ob_start() и global. Почему-то мне это кажется странным. Или это довольно распространенная практика?


person Geo    schedule 07.12.2012    source источник


Ответы (3)


Вы можете найти более элегантное решение своей проблемы в этом ответе.
Обратите внимание на использование PHP-функции extract для создания экземпляров переменных шаблона. .
Другими словами, вы должны перенести логику разбора шаблона за пределы функции отправки электронной почты.
Например:

<?php

class SimpleTemplate {
    private $_tpl  = "";
    private $_vars = array();

    function __construct($tpl_name) {
         $this->_tpl = $tpl_name;
    }

    public function __set($name, $value) {
        $this->_vars[$name] = $value;
    }

    public function setVars($values) {
        $this->_vars = $values;
    }

    public function parse() {
        ob_start();
        extract($this->_vars);
        include $this->_tpl;
        return ob_get_clean();
    }
}

abstract class Email {
    public static function send($to, $subj, $msg, $options = array()) {
        /* ... */
    }
}

$tpl = new SimpleTemplate('/inc/email/templates/account_created.php');
$tpl->name = 'Stack Overflow';
$tpl->SITE_NAME = 'site_name';
$tpl->SITE_URL = 'localhost';
Email::send("me@localhost", "Subject", $tpl->parse());

?>
person Community    schedule 26.12.2012

Один из способов сделать это — прочитать содержимое файла в переменную, а затем с помощью регулярных выражений заменить заполнители. Например, у вас есть файл new-users-template.phtml. Вы читаете это содержание с $content = file_get_content('new-users-templae.phtml');. В этих шаблонах у вас будут заполнители, такие как %%username%%, %%sitename%%, %%siteurl%%. Вам нужно обработать этот контент с помощью str_replace, заменив заполнители. Переместите этот код в какую-нибудь функцию "prepareEmailTemplate" и поместите результат этой функции в свою функцию "send".

person Dmitriy Sushko    schedule 07.12.2012
comment
Хм.. Это что-то вроде умных шаблонов, да? Однако переменные все равно должны быть глобализированы внутри функции. - person Geo; 07.12.2012
comment
Может быть, я не видел код Smarty внутри :) Насчет варов - вы можете указать их как дополнительные параметры функции отправки. Но лучше было бы переместить код разбора шаблона в другую функцию и поместить полученный контент в функцию отправки - person Dmitriy Sushko; 07.12.2012
comment
На самом деле вы могли бы использовать ваши существующие файлы именно таким образом, заменив весь раздел PHP, так что что-то вроде str_replace('<?=name;?>', 'Some name', $email_html); - person BenOfTheNorth; 22.12.2012

Одно из решений — глобализировать нужные переменные не внутри шаблона, а внутри функции send.

public static function send($to, $subj, $msg, $options = array()) {
    global $SITE_NAME, $SITE_URL, $name;

    /* ... */
    ob_start();
    include '/inc/email/templates/account_created.php';
    $msg = ob_get_clean();
    /* ... */
}

Другое решение — передать эти дополнительные переменные в качестве параметров. Это может быть некрасиво, так как количество параметров в функции set может сильно вырасти в зависимости от того, сколько из них вам нужно в вашем шаблоне. Чтобы решить эту проблему, другим способом реализации этого решения является передача этих дополнительных переменных в виде хэша и создание этих переменных на лету (с помощью функции eval). Вот пример:

public static function send($to, $extra_vars = array()) {
    foreach ($extra_vars as $key => $value) {
        eval("\$$key = '$value';");
    }

    /* ... */
    ob_start();
    include '/inc/email/templates/account_created.php';
    $msg = ob_get_clean();
    /* ... */
}

Затем, когда вы должны вызвать send следующим образом:

$SITE_NAME = "www.somewebsite.com";
Email::send("recipient", array('SITE_NAME' => $SITE_NAME));
person Diego Pino    schedule 22.12.2012
comment
Мне вообще не нравится идея глобализации переменных. Глобализация их в функции, как вы предлагаете, кажется немного чище, но потребует постоянной модификации класса, когда в шаблоны вводятся новые переменные. Это не весело. Мне нравится ваша вторая идея добавить их в функцию send. Мне придется немного поэкспериментировать с этим. Однако я определенно не рекомендовал бы использовать eval. Я лучше просто обновлю шаблоны до echo $extra_vars['SITE_NAME']. Спасибо! P.S. Поздравляю с оценкой репутации!! :) - person Geo; 22.12.2012
comment
Да, eval может быть злом ;-) Иметь их в шаблоне в виде массива тоже может быть неплохо. - person Diego Pino; 22.12.2012