如何在 symfony2 中从数据库渲染 Twig 模板

发布于 2024-12-16 13:40:46 字数 469 浏览 3 评论 0 原文

我正在开发用 symfony2 编写的应用程序,我想在某些操作/事件后发送电子邮件...问题是,用户可以定义类似“电子邮件模板”之类的内容,这些模板像简单的字符串一样存储在数据库中,例如: “这是来自 {{ user }} 的一些电子邮件”,我需要渲染该电子邮件的正文,该电子邮件应使用该模板...

在 symfony 文档中,此链接: https://symfony.com/doc/2.0/cookbook/email/email .html#sending-emails 渲染视图的方法是 $this->renderView 并且它需要文件的路径,例如“bundle:controller:file.html.twig”,但我的模板是来自数据库的简单字符串...

我该如何渲染它?

I'm working on application written in symfony2 and I want to send email after some action/event... the problem is, that the users can define something like "email templates" which are stores in db like simple string, for example: "This is some email from {{ user }}" and I need to render body for that email which should use that template...

In symfony documentation from this link: https://symfony.com/doc/2.0/cookbook/email/email.html#sending-emails the method for render view is $this->renderView and it expects the path to file such as "bundle:controller:file.html.twig", but my template is simple string from database...

How can I render it?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(11

丶情人眼里出诗心の 2024-12-23 13:40:46

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

Twig_Loader_String is deprecated and was always designed for internal use anyway. The usage of this loader is strongly discouraged.

From the API doc:

This loader should NEVER be used. It only exists for Twig internal
purposes. When using this loader with a cache mechanism, you should
know that a new cache key is generated each time a template content
"changes" (the cache key being the source code of the template). If
you don't want to see your cache grows out of control, you need to
take care of clearing the old cache file by yourself.

Also check out this issue: https://github.com/symfony/symfony/issues/10865


The best way I know to load a template from a String source are:

From a controller:

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

as described here: http://twig.sensiolabs.org/doc/recipes.html#loading-a-template-from-a-string

From a twig template:

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

as described here: http://twig.sensiolabs.org/doc/functions/template_from_string.html

Note, that the 'template_from_string' - function is not available by default and needs to be loaded. In symfony you would do this by adding a new service:

# services.yml
services:
    appbundle.twig.extension.string:
        class: Twig_Extension_StringLoader
        tags:
            - { name: 'twig.extension' }
淡紫姑娘! 2024-12-23 13:40:46

这应该有效。将“Hello {{ name }}”替换为模板文本,并使用您需要的任何变量填充传递到渲染函数的数组。

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

This should work. Replace "Hello {{ name }}" with your template text, and fill the array that is passed into the render function with any variables that you need.

$env = new \Twig_Environment(new \Twig_Loader_String());
echo $env->render(
  "Hello {{ name }}",
  array("name" => "World")
);
没有伤那来痛 2024-12-23 13:40:46

这是一个适用于 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;
}

最低限度的字段是 filenamesource,但包含 last_updated< 是一个非常好的主意/code> 否则你将失去缓存的好处。

创建DatabaseLoader类

src/Twig/Loader/DatabaseLoader.php

<?php
namespace App\Twig\Loader;

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');

包括来自另一个 Twig 模板(其中可以在数据库或文件系统中):

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

渲染为字符串(其中 $twig 是一个实例Twig\Environment)

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

在每种情况下,Twig 都会首先检查数据库。如果 DatabaseLoader 中的 getTemplate 返回 null,Twig 将检查文件系统。如果模板在数据库或文件系统中不可用,Twig 将抛出 Twig_Error_Loader

Here's a solution that works with Symfony 4 (and possibly older versions as well, although I haven't tested it) and allows you to work with templates stored in the database the same way you would work with templates in the filesystem.

This answer assumes you're using Doctrine, but is relatively easy to adapt if you're using another database library.

Create the Template entity

This is an example class that uses annotations, but you can use whatever configuration method you're already using.

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;
}

The bare minimum fields are filename and source, but it's a very good idea to include last_updated or you'll lose the benefits of caching.

Create a DatabaseLoader class

src/Twig/Loader/DatabaseLoader.php

<?php
namespace App\Twig\Loader;

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]);  
    }
}

The class is relatively simple. getTemplate looks up the template filename from the database, and the rest of the methods use getTemplate to implement the interface that Twig needs.

Add the DatabaseLoader to your service config

config/services.yaml

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

Now you can use your database templates in the same way as filesystem templates.

Rendering from a controller:

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

Including from another Twig template (which can be in the database or filesystem):

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

Rendering to a string (where $twig is an instance of Twig\Environment)

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

In each of these cases, Twig will check the database first. If getTemplate in your DatabaseLoader returns null, Twig will then check the filesystem. If the template isn't available in the database or the filesystem, Twig will throw a Twig_Error_Loader.

錯遇了你 2024-12-23 13:40:46

克隆本机 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'));

Clone the native twig service and replace the filesystem loader with the native twig string loader:

<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>

Usage example from within a controller:

$this->get('my.twigstring')->render('Hello {{ name }}', array('name' => 'Fabien'));
披肩女神 2024-12-23 13:40:46

最好的方法是使用 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 来实现此目的href="https://github.com/symfony/symfony/issues/10865">stof 的这个 github 问题

The best way to do it is to use template_from_string twig function.

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

See documentation of template_from_string

See why it is not a good idea to use Twig_Loader_Chain or Twig_Loader_String for that purpose on this github issue by stof.

司马昭之心 2024-12-23 13:40:46

从 Twig 1.10 开始,Twig 引擎不支持渲染字符串。但是有一个可用的捆绑包添加了此行为,称为 TwigstringBundle

它添加了 $this->get('twigstring') 服务,您可以使用它来渲染字符串。

(截至 2019 年 9 月,Twig 的当前版本为 2.X,版本 3 即将推出;因此这仅适用于非常旧的 Twig 版本)。

As of Twig 1.10, the Twig Engine doesn't support rendering strings. But there is a bundle available which adds this behavior called TwigstringBundle.

It adds the $this->get('twigstring') service wich you can use to render your strings.

(As September '19, the current version of Twig is 2.X, and version 3 is around the corner; so this is only applies to very old versions of Twig).

回忆凄美了谁 2024-12-23 13:40:46

这对我有用:

$loader = new \Twig\Loader\ArrayLoader([
    'Temp_File.html' => 'Hello {{ name }}!',
]);
$twig = new \Twig\Environment($loader);

echo $twig->render('Temp_File.html', ['name' => 'Fabien']);

https://twig.symfony.com/doc/2 .x/api.html

This work for me:

$loader = new \Twig\Loader\ArrayLoader([
    'Temp_File.html' => 'Hello {{ name }}!',
]);
$twig = new \Twig\Environment($loader);

echo $twig->render('Temp_File.html', ['name' => 'Fabien']);

https://twig.symfony.com/doc/2.x/api.html

小清晰的声音 2024-12-23 13:40:46

仅供参考,此功能建议建议。 com/fabpot/Twig/commit/bf3802bc6e5199b4d065b383caf3fe81203b713d" rel="nofollow">添加从 1.11.0 开始,Twig 的核心,但需要激活由开发商。

FYI, This feature was suggested to be added in the core of Twig as of 1.11.0, but will be needed to be activated by the developper.

怎会甘心 2024-12-23 13:40:46

我最近必须实现一个由多方使用的 CMS,其中每一方都可以完全自定义他们的模板。为了实现这一点,我实现了一个自定义的 Twig Loader。

最困难的部分是为模板制定一个命名约定,保证不与任何现有模板重叠,例如 !AppBundle:template.html.twig
如果模板未自定义,则必须加载模板 AppBundle:template.html.twig 作为后备模板。

然而,这对于 Chain Loader (AFAIK) 来说是不可能的,因为模板名称无法修改。因此,我必须将默认加载器(即加载器链)注入到我的加载器中,并使用它来加载后备模板。

另一种解决方案是将请求堆栈或会话传递给模板加载器,从而可以自动检测组织,但这很困难,因为安全组件依赖于模板子系统,从而导致循环依赖问题。

I recently had to implement a CMS used by multiple parties where each party could completely customize their templates. To achieve this I implemented a custom Twig Loader.

The most difficult part was coming up with a naming convention for the templates guaranteed not to overlap with any existing templates, for example <organisation_slug>!AppBundle:template.html.twig.
In case the template was not customised, the template AppBundle:template.html.twig would have to be loaded as fallback template.

However, this is not possible with the Chain Loader (AFAIK) because there the template name cannot be modified. Therefore I had to inject the default loader (i.e. the loader chain) into my loader and use it to load the fallback template.

Another solution would be to pass the request stack or the session to the template loader, making it possible to automatically detect the organisation, but this is difficult because the security component depends on the templating subsystem, causing circular dependency issues.

束缚m 2024-12-23 13:40:46
  $message = \Swift_Message::newInstance()
        ->setSubject('Hello Email')
        ->setFrom('[email protected]')
        ->setTo('[email protected]')
        ->setBody('hai its a sample mail')
    ;
    $this->get('mailer')->send($message);
  $message = \Swift_Message::newInstance()
        ->setSubject('Hello Email')
        ->setFrom('[email protected]')
        ->setTo('[email protected]')
        ->setBody('hai its a sample mail')
    ;
    $this->get('mailer')->send($message);
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文