Подтвердить что ты не робот

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

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

4b9b3361

Ответ 1

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

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

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

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

SRC/Entity/template.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/Грузчик /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 в конфигурацию вашего сервиса

конфиг /services.yaml

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

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

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

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

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

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

Рендеринг в строку (где $twig - это экземпляр Twig\Environment)

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

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

Ответ 2

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

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

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

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


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

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

$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' }

Ответ 3

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

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

Ответ 4

Клонировать собственную службу 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'));

Ответ 5

Начиная с Twig 1.10, Twig Engine не поддерживает рендеринг строк. Но есть пакет, который добавляет это поведение, который называется TwigstringBundle.

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

(По состоянию на сентябрь 19 года текущая версия Twig - 2.X, а версия 3 не за горами; так что это применимо только к очень старым версиям Twig).

Ответ 6

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

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

Смотрите документацию template_from_string

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

Ответ 10

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

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

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

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

Ответ 11

  $message = \Swift_Message::newInstance()
        ->setSubject('Hello Email')
        ->setFrom('[email protected]')
        ->setTo('[email protected]')
        ->setBody('hai its a sample mail')
    ;
    $this->get('mailer')->send($message);