web-dev-qa-db-ja.com

symfony2のデータベースからTwigテンプレートをレンダリングする方法

Symfony2で記述されたアプリケーションで作業していて、アクション/イベントの後にメールを送信したい...問題は、ユーザーが「メールテンプレート」のようなものを定義できることです。 「これは{{user}}からの一部のメールです」とそのテンプレートを使用するメールの本文をレンダリングする必要があります...

このリンクからのsymfonyのドキュメント: https://symfony.com/doc/2.0/cookbook/email/email.html#sending-emails レンダービューのメソッドは$ this-> renderViewであり、 「bundle:controller:file.html.twig」などのファイルへのパスが必要ですが、テンプレートはデータベースからの単純な文字列です...

どうすればレンダリングできますか?

30
Karol F

以下は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;

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は最初にデータベースをチェックします。getTemplateDatabaseLoaderがnullを返す場合、Twig次に、ファイルシステムをチェックします。テンプレートがデータベースまたはで使用できない場合、Twigがスローされます_Twig_Error_Loader_。

7
Clamburger

Twig_Loader_Stringは非推奨であり、常に内部使用のために設計されています。このローダーの使用は強くお勧めしません。

APIドキュメントから:

このローダーは絶対に使用しないでください。これは、Twig内部目的でのみ存在します。このローダーをキャッシュメカニズムで使用する場合、テンプレートコンテンツが「変更」されるたびに新しいキャッシュキーが生成されることを知っておく必要があります(キャッシュキーはテンプレートのソースコード)キャッシュが制御不能になりたくない場合は、古いキャッシュファイルを自分でクリアする必要があります。

この問題も確認してください: 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

twigテンプレートから:

{{ 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' }
29
Atan

これはうまくいくはずです。 「Hello {{name}}」をテンプレートテキストで置き換え、render関数に渡される配列に必要な変数を入力します。

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

ネイティブ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'));
10
Philipp Rieber

Twig 1.10以降、Twigエンジンは文字列のレンダリングをサポートしていません。ただし、この動作を追加するバンドルが利用可能です TwigstringBundle

文字列のレンダリングに使用できる$this->get('twigstring')サービスを追加します。

(19年9月現在、Twigは2.Xであり、バージョン3が間近です。そのため、これは非常に古いバージョンのTwigにのみ適用されます)。

8

これを行う最良の方法は、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を使用することをお勧めしますstof によるこのgithubの問題。

6
Quentin
1
Juburin

参考までに、この機能は 推奨 でした 追加 Twig現在 1.11. 以降、ただし、開発者がアクティブにする必要があります。

0
yvoyer

私は最近、複数の当事者が使用するCMSを実装する必要があり、各当事者はテンプレートを完全にカスタマイズできました。これを実現するために、カスタムTwigローダーを実装しました。

最も難しい部分は、既存のテンプレートと重複しないことが保証されているテンプレートの命名規則を考え出すことでした。たとえば、<organisation_slug>!AppBundle:template.html.twig。テンプレートがカスタマイズされていない場合、テンプレートAppBundle:template.html.twigをフォールバックテンプレートとしてロードする必要があります。

ただし、チェーンローダー(AFAIK)ではテンプレート名を変更できないため、これは不可能です。したがって、デフォルトのローダー(ローダーチェーン)をローダーに挿入し、それを使用してフォールバックテンプレートをロードする必要がありました。

別の解決策は、リクエストスタックまたはセッションをテンプレートローダーに渡し、組織を自動的に検出できるようにすることですが、セキュリティコンポーネントがテンプレートサブシステムに依存し、循環依存の問題を引き起こすため、これは困難です。

0
MauganRa
0
Benoit Mariaux