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

Posted

技术标签:

【中文标题】如何在 symfony2 中从数据库中渲染 Twig 模板【英文标题】:How to render Twig template from database in symfony2 【发布时间】:2012-01-01 14:04:05 【问题描述】:

我正在开发用 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/… 【参考方案1】:

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' %【参考方案2】:

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

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

【讨论】:

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

这是一个适用于 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,否则您将失去缓存的好处。

创建一个 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')

呈现为字符串(其中$twigTwig\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:clearcache:warmup 依赖于数据库...如果您通过 docker 等进行部署,这可能会破坏您的映像构建过程。 .. 您可以随时将安装后脚本更改为cache:clear --no-warmup,但需要注意这一点... 你现在必须控制缓存【参考方案4】:

克隆原生 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 中做了一些更改(没有推回原始仓库,因为我仍然需要查看代码)【参考方案5】:

最好的方法是使用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_ChainTwig_Loader_String不是一个好主意。

【讨论】:

甜蜜而简单。省时间。救生员。 +1【参考方案6】:

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

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

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

【讨论】:

【参考方案7】:

这对我有用:

$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

【讨论】:

还在 3.x 文档中 https://twig.symfony.com/doc/3.x/api.html.【参考方案8】:

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

【讨论】:

【参考方案9】:

在 Symfony 2.2 中,您可以使用 Twig_Chain_Loader How to register another (custom) Twig loader in Symfony2 environment?

【讨论】:

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

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

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

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

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

【讨论】:

【参考方案11】:
  $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 的示例。

以上是关于如何在 symfony2 中从数据库中渲染 Twig 模板的主要内容,如果未能解决你的问题,请参考以下文章

如何在Symfony2中将json渲染成树枝

在symfony2中渲染数据时删除第一行

如何在反应中从渲染函数返回一个空的jsx元素?

Symfony2:使用侦听器更改渲染视图

如何在three.js对象渲染中从中心向左更改轴(x,y,z)位置?

如何在vue js中从组件渲染组件