Symfony2で記述されたアプリケーションで作業していて、アクション/イベントの後にメールを送信したい...問題は、ユーザーが「メールテンプレート」のようなものを定義できることです。 「これは{{user}}からの一部のメールです」とそのテンプレートを使用するメールの本文をレンダリングする必要があります...
このリンクからのsymfonyのドキュメント: https://symfony.com/doc/2.0/cookbook/email/email.html#sending-emails レンダービューのメソッドは$ this-> renderViewであり、 「bundle:controller:file.html.twig」などのファイルへのパスが必要ですが、テンプレートはデータベースからの単純な文字列です...
どうすればレンダリングできますか?
以下は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
_を含めることは非常に良い方法です。そうしないと、キャッシュの利点が失われます。
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に必要なインターフェイスを実装します。
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は最初にデータベースをチェックします。getTemplate
のDatabaseLoader
がnullを返す場合、Twig次に、ファイルシステムをチェックします。テンプレートがデータベースまたはで使用できない場合、Twigがスローされます_Twig_Error_Loader
_。
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
{{ 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' }
これはうまくいくはずです。 「Hello {{name}}」をテンプレートテキストで置き換え、render関数に渡される配列に必要な変数を入力します。
$env = new \Twig_Environment(new \Twig_Loader_String());
echo $env->render(
"Hello {{ name }}",
array("name" => "World")
);
ネイティブ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'));
Twig 1.10以降、Twigエンジンは文字列のレンダリングをサポートしていません。ただし、この動作を追加するバンドルが利用可能です TwigstringBundle 。
文字列のレンダリングに使用できる$this->get('twigstring')
サービスを追加します。
(19年9月現在、Twigは2.Xであり、バージョン3が間近です。そのため、これは非常に古いバージョンのTwigにのみ適用されます)。
これを行う最良の方法は、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の問題。
Symfony 2.2では Twig_Chain_Loader を使用できます
別の(カスタム)を登録する方法Twig Symfony2環境のローダー?
私は最近、複数の当事者が使用するCMSを実装する必要があり、各当事者はテンプレートを完全にカスタマイズできました。これを実現するために、カスタムTwigローダーを実装しました。
最も難しい部分は、既存のテンプレートと重複しないことが保証されているテンプレートの命名規則を考え出すことでした。たとえば、<organisation_slug>!AppBundle:template.html.twig
。テンプレートがカスタマイズされていない場合、テンプレートAppBundle:template.html.twig
をフォールバックテンプレートとしてロードする必要があります。
ただし、チェーンローダー(AFAIK)ではテンプレート名を変更できないため、これは不可能です。したがって、デフォルトのローダー(ローダーチェーン)をローダーに挿入し、それを使用してフォールバックテンプレートをロードする必要がありました。
別の解決策は、リクエストスタックまたはセッションをテンプレートローダーに渡し、組織を自動的に検出できるようにすることですが、セキュリティコンポーネントがテンプレートサブシステムに依存し、循環依存の問題を引き起こすため、これは困難です。