Как отобразить шаблон Twig из базы данных в symfony2

Я работаю над приложением, написанным в symfony2, и я хочу отправить электронное письмо после некоторого действия/события... проблема в том, что пользователи могут определить что-то вроде "шаблонов электронной почты", которые хранятся в БД как простая строка, например: "это некоторая электронная почта от {{ user}}", и мне нужно отобразить тело для электронной почты из этого шаблона... В документации symfony по этой ссылке:http://symfony.com/doc/2.0/cookbook/email.html#sending-emails methos для рендеринга - $this - >renderView и он ожидает ссылку на файл как " bundle:controller: file.формат html.twig", но мой шаблон-это база данных как простая строка... Как я могу это передать?

11 ответов


вот решение, которое работает с Symfony 4 (и, возможно, более старыми версиями, хотя я его не тестировал) и позволяет работать с шаблонами, хранящимися в базе данных, так же, как вы работали бы с шаблонами в файловой системе.

этот ответ предполагает, что вы используете доктрину, но относительно легко адаптироваться, если вы используете другую библиотеку баз данных.

создайте сущность шаблона

это пример класса, который использует аннотации, но вы можете использовать любой метод конфигурации, который вы уже используете.

src / сущность / шаблон.в PHP

<?php
namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Table(name="templates")
 * @ORM\Entity
 */
class Template
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="IDENTITY")
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(type="string", nullable=false)
     */
    private $filename;

    /**
     * @var string
     *
     * @ORM\Column(type="text", nullable=false)
     */
    private $source;

    /**
     * @var \DateTime
     *
     * @ORM\Column(type="datetime", nullable=false)
     */
    private $last_updated;
}

минимальными полями являются filename и source, но это очень хорошая идея, чтобы включить last_updated или вы потеряете преимущества кэширования.

создайте класс DatabaseLoader

src / Twig / Loader / DatabaseLoader.в PHP

<?php
namespace App\Twig;

use App\Entity\Template;
use Doctrine\ORM\EntityManagerInterface;
use Twig_Error_Loader;
use Twig_LoaderInterface;
use Twig_Source;

class DatabaseLoader implements Twig_LoaderInterface
{
    protected $repo;

    public function __construct(EntityManagerInterface $em)
    {
        $this->repo = $em->getRepository(Template::class);
    }

    public function getSourceContext($name)
    {
        if (false === $template = $this->getTemplate($name)) {
            throw new Twig_Error_Loader(sprintf('Template "%s" does not exist.', $name));
        }

        return new Twig_Source($template->getSource(), $name);
    }

    public function exists($name)
    {
        return (bool)$this->getTemplate($name);
    }

    public function getCacheKey($name)
    {
        return $name;
    }

    public function isFresh($name, $time)
    {
        if (false === $template = $this->getTemplate($name)) {
            return false;
        }

        return $template->getLastUpdated()->getTimestamp() <= $time;
    }

    /**
     * @param $name
     * @return Template|null
     */
    protected function getTemplate($name)
    {
        return $this->repo->findOneBy(['filename' => $name)]);  
    }
}

класс относительно прост. getTemplate ищет имя файла шаблона из базы данных, а остальные методы используют getTemplate для реализации интерфейса, который нужен Twig.

добавьте DatabaseLoader в конфигурацию службы

config / services.и YAML

services:
    App\Twig\Loader\DatabaseLoader:
        tags:
        - { name: twig.loader }

теперь вы можете использовать шаблоны базы данных так же, как шаблоны файловой системы.

рендеринг от контроллера:

return $this->render('home.html.twig');

в том числе от еще один шаблон веточки (который может быть в базе данных или файловой системы):

{{ include('welcome.html.twig') }}

рендеринг в строку (где $twig пример Twig\Environment)

$html = $twig->render('email.html.twig')

в каждом из этих случаев Twig сначала проверит базу данных. Если getTemplate в своем DatabaseLoader возвращает null, затем Twig проверяет файловую систему. Если шаблон недоступен в базе данных или файловая система, Twig будет бросьте Twig_Error_Loader.


Twig_Loader_String устарел и всегда был разработан для внутреннего использования в любом случае. Использование этого загрузчика не рекомендуется.

из документа API:

этот загрузчик никогда не должен использоваться. Он существует только для Twig внутреннего цели. При использовании этого загрузчика с механизмом кэша необходимо знаю, что новый ключ кэша каждый раз, когда содержимое шаблона "изменения" (ключ кэша является исходным кодом шаблона). Если вы не хотите чтобы увидеть, что ваш кэш выходит из-под контроля, вам нужно позаботьтесь об очистке старого файла кэша самостоятельно.

также проверьте эту проблему:https://github.com/symfony/symfony/issues/10865


лучший способ, который я знаю, чтобы загрузить шаблон из источника строки:

от контроллера:

$template = $this->get('twig')->createTemplate('Hello {{ name }}');
$template->render(array('name'=>'World'));

как описано здесь: http://twig.sensiolabs.org/doc/recipes.html#loading-a-template-from-a-string

из шаблона веточки:

{{ include(template_from_string("Hello {{ name }}", {'name' : 'Peter'})) }}

как описано здесь: http://twig.sensiolabs.org/doc/functions/template_from_string.html

обратите внимание, что функция' template_from_string ' недоступна по умолчанию и должна быть загружена. В symfony вы бы сделали это, добавив новую услугу:

# services.yml
services:
    appbundle.twig.extension.string:
        class: Twig_Extension_StringLoader
        tags:
            - { name: 'twig.extension' }

Это должно работать. Замените" Hello {{ name}} " текстом шаблона и заполните массив, переданный в функцию рендеринга, любыми переменными, которые вам нужны.

$env = new \Twig_Environment(new \Twig_Loader_String());
echo $env->render(
  "Hello {{ name }}",
  array("name" => "World")
);

клонировать уроженца twig сервис и замена загрузчика файловой системы на собственный загрузчик строк twig:

<service id="my.twigstring" class="%twig.class%">
    <argument type="service" id="my.twigstring.loader" />
    <argument>%twig.options%</argument>
</service>        
<service id="my.twigstring.loader" class="Twig_Loader_String"></service>

пример использования из контроллера:

$this->get('my.twigstring')->render('Hello {{ name }}', array('name' => 'Fabien'));

Twigengine не поддерживает строки рендеринга. Но есть доступный пакет, который добавляет Это поведение под названием TwigstringBundle.

добавляет $this->get('twigstring') сервис, который вы можете использовать для рендеринга строк.


лучший способ сделать это-использовать template_from_string функции прутик.

{{ include(template_from_string("Hello {{ name }}")) }}
{{ include(template_from_string(page.template)) }}

посмотреть документация template_from_string

понимаю, почему это не хорошая идея, чтобы использовать Twig_Loader_Chain или Twig_Loader_String для этой цели этот выпуск github от stof.



FYI, эта функция была предложил на добавил в ядре веточки с 1.11.0, но будет необходимо быть активированным developper.


вы можете найти хороший пример здесь : http://twig.sensiolabs.org/doc/recipes.html#using-a-database-to-store-templates


недавно мне пришлось реализовать CMS, используемую несколькими сторонами, где каждая сторона могла полностью настроить свои шаблоны. Для этого я реализовал пользовательский загрузчик Twig.

самой сложной частью было придумать соглашение об именах для шаблонов, гарантированных не перекрываться с любыми существующими шаблонами, например <organisation_slug>!AppBundle:template.html.twig. В случае, если шаблон не был настроен, шаблон AppBundle:template.html.twig должен быть загружен как резервный шаблон.

однако, это невозможно с цепным загрузчиком (AFAIK), потому что там имя шаблона не может быть изменено. Поэтому мне пришлось ввести загрузчик по умолчанию (т. е. цепочку загрузчиков) в мой загрузчик и использовать его для загрузки резервного шаблона.

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


  $message = \Swift_Message::newInstance()
        ->setSubject('Hello Email')
        ->setFrom('send@example.com')
        ->setTo('recipient@example.com')
        ->setBody('hai its a sample mail')
    ;
    $this->get('mailer')->send($message);