Я работаю над приложением, написанным на symfony2, и я хочу отправить электронное сообщение после какого-либо действия/события... проблема в том, что пользователи могут определить что-то вроде "шаблонов электронной почты", которые хранятся в db, например, как простая строка, например: "Это письмо из {{user}}", и мне нужно отобразить тело для электронной почты из этого шаблона... В документации по symfony по этой ссылке: http://symfony.com/doc/2.0/cookbook/email.html#sending-emails метод metos для рендеринга - это $this- > renderView, и он ожидает ссылки на файл как "bundle: controller: file.html.twig", но мой шаблон - это база данных как простая строка... Как это сделать?
Как визуализировать шаблон Twig из базы данных в symfony2
Ответ 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.
Ответ 7
С Symfony 2.2 вы можете использовать Twig_Chain_Loader
Как зарегистрировать другой (пользовательский) загрузчик Twig в среде Symfony2?
Ответ 8
FYI, эта функция предложила быть добавлена в ядре Twig от 1.11.0, но будет необходимо активировать разработчик.
Ответ 9
Здесь вы можете найти хороший пример: http://twig.sensiolabs.org/doc/recipes.html#using-a-database-to-store-templates
Ответ 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);