Comment rendre le modèle de brindille à partir de la base de données en symfony2
je travaille sur la demande écrite en symfony2 et je veux envoyer un e-mail après une action/événement... le problème est que les utilisateurs peuvent définir quelque chose comme "modèles de courrier électronique" qui stocke dans la base de données comme une chaîne simple, par exemple: "Ceci est un courrier électronique de {{ user }}" et j'ai besoin de rendre le corps pour le courrier électronique à partir de ce modèle... Dans la documentation de symfony à partir de ce lien: http://symfony.com/doc/2.0/cookbook/email.html#sending-emails le methos pour rendre est $this->renderView et il attend de référence de fichier comme "bundle:contrôleur:fichier.HTML.twig", mais mon modèle est Base de données comme chaîne simple... Comment puis-je le rendre?
11 réponses
Voici une solution qui fonctionne avec Symfony 4 (et peut-être aussi avec des versions plus anciennes, bien que je ne l'ai pas testé) et qui vous permet de travailler avec des gabarits stockés dans la base de données de la même manière que vous travailleriez avec des gabarits dans le système de fichiers.
cette réponse suppose que vous utilisez la Doctrine, mais est relativement facile à adapter si vous utilisez une autre bibliothèque de base de données.
créer le modèle d'entité
C'est un exemple de classe qui utilise des annotations, mais vous pouvez utiliser n'importe quelle méthode de configuration que vous utilisez déjà.
src/Entité/Modèle.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;
}
les champs minimaux sont filename
et source
, mais c'est une très bonne idée d'inclure last_updated
ou vous perdrez les avantages de la mise en cache.
créer une classe 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)]);
}
}
la classe est relativement simple. getTemplate
recherche le nom du fichier de modèle à partir de la base de données, et le reste des méthodes utilisent getTemplate
pour mettre en œuvre l'interface dont Twig a besoin.
ajouter le DatabaseLoader à votre configuration de service
config/services.yaml
services:
App\Twig\Loader\DatabaseLoader:
tags:
- { name: twig.loader }
Maintenant vous pouvez utiliser votre modèles de base de données de la même manière que les modèles de système de fichiers.
Rendu d'un contrôleur:
return $this->render('home.html.twig');
y compris d'un autre modèle de brindille (qui peut être dans la base de données ou le système de fichiers):
{{ include('welcome.html.twig') }}
Rendu à une chaîne (où $twig
est une instance de Twig\Environment
)
$html = $twig->render('email.html.twig')
dans chacun de ces cas, Twig vérifiera d'abord la base de données. Si getTemplate
dans votre DatabaseLoader
retourne nul, Twig vérifiera alors le système de fichiers. Si le modèle n'est pas disponible dans la base de données ou le système de fichiers, Twig lancera un Twig_Error_Loader
.
Twig_Loader_String est déprécié et a toujours été conçu pour un usage interne de toute façon. L'utilisation de ce chargeur est fortement déconseillée.
de L'API doc:
ce chargeur ne doit jamais être utilisé. Il n'existe que pour les brindilles internes but. Lorsque vous utilisez ce chargeur avec un mécanisme de cache, vous devriez sachez qu'une nouvelle clé de cache est générée à chaque fois qu'un contenu de template "change" (la clé de cache étant le code source du modèle.) Si vous ne voulez pas voir votre cache se développe hors de contrôle, vous devez prenez soin de vider vous-même l'ancien fichier cache.
consultez aussi ce numéro: https://github.com/symfony/symfony/issues/10865
le meilleur moyen que je connaisse pour charger un modèle à partir d'une source de chaîne de caractères sont:
D'un contrôleur:
$template = $this->get('twig')->createTemplate('Hello {{ name }}');
$template->render(array('name'=>'World'));
comme décrit ici: http://twig.sensiolabs.org/doc/recipes.html#loading-a-template-from-a-string
D'un modèle de brindille:
{{ include(template_from_string("Hello {{ name }}", {'name' : 'Peter'})) }}
comme décrit ici: http://twig.sensiolabs.org/doc/functions/template_from_string.html
notez que la fonction' template_from_string ' n'est pas disponible par défaut et doit être chargée. En symfony vous feriez ceci en ajoutant un nouveau service:
# services.yml
services:
appbundle.twig.extension.string:
class: Twig_Extension_StringLoader
tags:
- { name: 'twig.extension' }
ça devrait marcher. Remplacez "Hello {{ name }}" par votre texte de modèle, et remplissez le tableau qui est passé dans la fonction de rendu avec toutes les variables dont vous avez besoin.
$env = new \Twig_Environment(new \Twig_Loader_String());
echo $env->render(
"Hello {{ name }}",
array("name" => "World")
);
clonez le service natif twig
et remplacez le chargeur du système de fichiers par le chargeur à ficelles natif:
<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>
exemple D'utilisation dans un contrôleur:
$this->get('my.twigstring')->render('Hello {{ name }}', array('name' => 'Fabien'));
le Twigengine ne supporte pas les chaînes de rendu. Mais il y a un paquet disponible qui ajoute ce comportement appelé TwigstringBundle .
il ajoute le service $this->get('twigstring')
que vous pouvez utiliser pour rendre vos chaînes.
La meilleure façon de le faire est d'utiliser template_from_string
brindille de fonction.
{{ include(template_from_string("Hello {{ name }}")) }}
{{ include(template_from_string(page.template)) }}
voir documentation de template_from_string
Voir pourquoi il est pas une bonne idée d'utiliser Twig_Loader_Chain
ou Twig_Loader_String
pour cette fin sur cette github question par stof .
avec Symfony 2.2 vous pouvez utiliser le Twig_Chain_Loader
Comment enregistrer un autre chargeur de brindilles (personnalisé) dans L'environnement Symfony2?
Vous pouvez trouver un bon exemple ici : http://twig.sensiolabs.org/doc/recipes.html#using-a-database-to-store-templates
j'ai récemment dû mettre en œuvre un SGC utilisé par plusieurs parties où chaque partie pourrait complètement personnaliser leurs modèles. Pour ce faire, j'ai mis en place un chargeur de brindilles personnalisé.
la partie la plus difficile a été de trouver une convention d'appellation pour les gabarits garantis ne pas chevaucher avec les gabarits existants, par exemple <organisation_slug>!AppBundle:template.html.twig
.
Dans le cas où le modèle n'était pas personnalisé, le modèle AppBundle:template.html.twig
devait être chargé comme modèle de rechange.
cependant, ce N'est pas possible avec le chargeur à chaîne (AFAIK) parce que là le nom du modèle ne peut pas être modifié. Par conséquent, j'ai dû injecter le chargeur par défaut (c'est-à-dire la chaîne du chargeur) dans mon chargeur et l'utiliser pour charger le modèle de rechange.
une autre solution serait de passer la pile de requêtes ou la session au chargeur de gabarits, ce qui permettrait de détecter automatiquement l'organisation, mais cela est difficile car le composant de sécurité dépend du sous-système templating, provoquant des problèmes de dépendance circulaire.
$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);