【问题标题】:How to render Twig template from database in symfony2如何在 symfony2 中从数据库中渲染 Twig 模板
【发布时间】:2021-12-11 00:21:23
【问题描述】:

我正在开发用 symfony2 编写的应用程序,我想在一些操作/事件之后发送电子邮件......问题是,用户可以定义像“电子邮件模板”这样的东西,它们像简单的字符串一样存储在 db 中,例如:“这是来自 {{ user }} 的一些电子邮件”,我需要为应该使用该模板的电子邮件呈现正文...

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

如何渲染它?

【问题讨论】:

  • $this->renderView() 应该返回一个简单的字符串。你试过return "some_string",而不是return $this->renderView(),只是想看看会发生什么?
  • 你应该看看这个包github.com/Remixjobs/RjEmailBundle,它可以处理你想要的。
  • 自 2011 年以来,情况发生了变化。对于现在遇到这个问题的任何人 - 请阅读 Twig 文档中的相关食谱:twig.sensiolabs.org/doc/…twig.sensiolabs.org/doc/…

标签: email symfony render twig


【解决方案1】:

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

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

(截至 19 年 9 月,Twig 的当前版本是 2.X,版本 3 即将发布;所以这只适用于非常旧的 Twig 版本)。

【讨论】:

    【解决方案2】:

    克隆原生 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'));
    

    【讨论】:

    • 从 Symfony2.2 开始,您可以简单地添加一个新的加载程序来定义服务并使用 twig.loader 标记对其进行标记。这样,twig 将使用 Twig_Loader_Chain 尝试使用每个定义的加载器加载模板。看看symfony.com/doc/current/reference/dic_tags.html#twig-loader 如果你需要定义树枝加载器的优先级,看看DifaneTwigDatabaseBundle,我在github.com/webgriffe/DifaneTwigDatabaseBundle 的fork 中做了一些更改(没有推回原始仓库,因为我仍然需要查看代码)
    【解决方案3】:
      $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);
    

    【讨论】:

    • 这与问题无关。请包含一个使用 Twig 的示例。
    【解决方案4】:

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

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

    【讨论】:

    • 我同意,一个简洁的解决方案,不使用侧包。
    • 我认为这是最好的解决方案和最好的答案:)
    • 这行得通。但是,我注意到默认情况下未启用自定义树枝扩展。所以这里的问题:stackoverflow.com/questions/16383522/…
    • 这个解决方案并不适合所有人:它不加载 twig 扩展,不缓存已编译的模板(较慢),并且不允许任何类型的模板引用:{% extends %}或 {% include %}。
    • 我在 cmets 中读到 Twig_Loader_String(已弃用)它会填满缓存...
    【解决方案5】:

    仅供参考,从 1.11.0 开始,此功能在 Twig 核心中的 suggested 将变为 added,但需要由开发人员激活。

    【讨论】:

      【解决方案6】:

      【讨论】:

      • 请将所有信息添加到您的答案中,而不是仅链接到外部网站
      【解决方案7】:

      最好的方法是使用template_from_string twig 函数。

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

      见documentation of template_from_string

      了解为什么在this github issue by stof 上使用Twig_Loader_Chain 或Twig_Loader_String 是不是一个好主意。

      【讨论】:

      • 甜蜜而简单。省时间。救生员。 +1
      【解决方案8】:

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

      【讨论】:

      • 在我的情况下,我无法在 template_from_string 中发送变量(可能它具有相同的范围?),所以我在 inlude 之前使用了 {% set name = 'Peter' %}
      【解决方案9】:

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

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

      但是,对于 Chain Loader (AFAIK),这是不可能的,因为无法修改模板名称。因此我不得不将默认加载器(即加载器链)注入我的加载器并使用它来加载回退模板。

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

      【讨论】:

        【解决方案10】:

        这是一个适用于 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/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-&gt;render('home.html.twig');

        从另一个 Twig 模板中包含(可以在数据库或文件系统中):

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

        呈现为字符串(其中$twig 是Twig\Environment 的一个实例)

        $html = $twig-&gt;render('email.html.twig')

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

        【讨论】:

        • 对于相同但使用 PDO,您可以看到 twig.symfony.com/doc/2.x/…
        • 对于那些想要实现这一点的人,请注意,这将使 cache:clear 和 cache:warmup 依赖于数据库...如果您通过 docker 等进行部署,这可能会破坏您的映像构建过程。 ..
        • 您可以随时将安装后脚本更改为cache:clear --no-warmup,但需要注意这一点...
        • 你现在必须控制缓存
        【解决方案11】:

        这对我有用:

        $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

        【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-05-20
        • 2015-06-04
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多