How to use renderView and twig templating in a Symfony 4 Service - php

I’m creating a Emailer Service in my new Symfony 4 application.
I have tried a million things but no luck. I could only find a few resources on this topic for S4 at the moment. Any help is appreciated.
This what I’m trying to achieve. I understand I have to use different services inside of my Emailer service but no luck.
<?php
namespace App\Mailer;
class Emailer
{
public function sendWelcome($email): \Swift_Mailer
{
$message = (new \Swift_Message('P****** - Welcome In!'))
->setFrom('no-reply#p****n.com')
->setTo($email)
->setBody(
$this->renderView(
// templates/emails/registration.html.twig
'emails/registration.html.twig',
array('name' => $user->getUsername())
),
'text/html'
)
->setCharset('utf-8');
$mailer->send($message);
return true;
}
}

First you need to get your templating service injected into your class (constructor injection) and then you can use it to render template.
In the code you can see it that we type-hint it in constructor so Symfony Dependency injection know what we need. Then we just use it. Same will be with your $mailer service.
<?php
namespace App\Mailer;
use Symfony\Component\Templating\EngineInterface;
class Emailer
{
/**
* #var EngineInterface
*/
private $templating;
/**
* TestTwig constructor.
*/
public function __construct(EngineInterface $templating)
{
$this->templating = $templating;
}
public function sendWelcome($email): \Swift_Mailer
{
$message = (new \Swift_Message('P****** - Welcome In!'))
->setFrom('no-reply#p****n.com')
->setTo($email)
->setBody(
$this->templating->render(
// templates/emails/registration.html.twig
'emails/registration.html.twig',
array('name' => $user->getUsername())
),
'text/html'
)
->setCharset('utf-8');
$mailer->send($message);
return true;
}
}

#miles-m a use statement is not the same as injection. A use statement just makes the class accessible with the class name as an alias. Dependency Injection is a pattern that decouples your classes from each other which facilitates better testing and debugging (you can swap out your injected objects for mock objects etc).
One way to inject the Swift_Mailer would be as a constructor parameter, i.e.
class Emailer
{
/** #var \Swift_Mailer $mailer */
private $mailer;
public function __construct(
EngineInterface $templating,
\Swift_Mailer $mailer <== mailer will be injected here
) : \Swift_Mailer
{
//...
$this->mailer->send($message);
}
}
class CallingClass
{
//...
$emailer = new Emailer(
//EngineInterface instance
//\Swift_Mailer instance <== injecting
);
$emailer->sendWelcome('email#example.com');
}
Other questions
$mailer->send($message)
Where is your $mailer instance defined?
public function sendWelcome($email): \Swift_Mailer
return true;
Is true a valid instance of Swift_Mailer?

Related

Symfony Functional Testing - How to mock controller injected service with request(submit)

How can I mock a service in a functional test use-case where a "request"(form/submit) is being made. After I make the request all the changes and mocking I made to the container are lost.
I am using Symfony 4 or 5. The code posted here can be also found here: https://github.com/klodoma/symfony-demo
I have the following scenario:
SomeActions service is injected into the controller constructor
in the functional unit-tests I try to mock the SomeActions functions in order to check that they are executed(it sends an email or something similar)
I mock the service and overwrite it in the unit-tests:
$container->set('App\Model\SomeActions', $someActions);
Now in the tests I do a $client->submit($form); which I know that it terminates the kernel.
My question is: HOW can I inject my mocked $someActions in the container after $client->submit($form);
Below is a sample code I added to the symfony demo app
https://github.com/symfony/demo
in services.yaml
App\Model\SomeActions:
public: true
SomeController.php
<?php
namespace App\Controller;
use App\Model\SomeActions;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
/**
* Controller used to send some emails
*
* #Route("/some")
*/
class SomeController extends AbstractController
{
private $someActions;
public function __construct(SomeActions $someActions)
{
//just dump the injected class name
var_dump(get_class($someActions));
$this->someActions = $someActions;
}
/**
* #Route("/action", methods="GET|POST", name="some_action")
* #param Request $request
* #return Response
*/
public function someAction(Request $request): Response
{
$this->someActions->doSomething();
if ($request->get('send')) {
$this->someActions->sendEmail();
}
return $this->render('default/someAction.html.twig', [
]);
}
}
SomeActions
<?php
namespace App\Model;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Email;
class SomeActions
{
private $mailer;
public function __construct(MailerInterface $mailer)
{
$this->mailer = $mailer;
}
public function doSomething()
{
echo 'doSomething';
}
public function sendEmail()
{
echo 'sendEmail';
$email = (new Email())
->from('hello#example.com')
->to('you#example.com')
->subject('Time for Symfony Mailer!')
->text('Sending emails is fun again!')
->html('<p>See Twig integration for better HTML integration!</p>');
$this->mailer->send($email);
}
}
SomeControllerTest.php
<?php
namespace App\Tests\Controller;
use App\Model\SomeActions;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class SomeControllerTest extends WebTestCase
{
public function testSomeAction()
{
$client = static::createClient();
// gets the special container that allows fetching private services
$container = self::$container;
$someActions = $this->getMockBuilder(SomeActions::class)
->disableOriginalConstructor()
->getMock();
//expect that sendEmail will be called
$someActions->expects($this->once())
->method('sendEmail');
//overwrite the default service: class: Mock_SomeActions_e68f817a
$container->set('App\Model\SomeActions', $someActions);
$crawler = $client->request('GET', '/en/some/action');
//submit the form
$form = $crawler->selectButton('submit')->form();
$client->submit($form);
//after submit the default class injected in the controller is "App\Model\SomeActions" and not the mocked service
$response = $client->getResponse();
$this->assertResponseIsSuccessful($response);
}
}
The solution is to disable the kernel reboot:
$client->disableReboot();
It makes sense if ones digs deep enough to understand what's going on under the hood;
I am still not sure if there isn't a more straight forward answer.
public function testSomeAction()
{
$client = static::createClient();
$client->disableReboot();
...

Can someone give me an Idea, how to make an event that is triggered onDelete(remove) in Symfony4

I'd like to make an event that is triggered on delete.
When someone deletes an article I take the user email from the article and send an email with information which article is deleted and when.
I work with the Symfony 4 framework.
I have no idea how to start?
I have in Article controller for CRUD.
My solution for this problem that works.
<?php
namespace App\EventListener;
use App\Entity\Article;
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Doctrine\ORM\Events;
use Twig\Environment;
class ArticleDeleteListener implements EventSubscriber
{
private $mailer;
private $twig;
public function __construct(\Swift_Mailer $mailer, Environment $twig)
{
$this->twig = $twig;
$this->mailer = $mailer;
}
public function getSubscribedEvents()
{
return [
Events::preRemove,
];
}
public function preRemove(LifecycleEventArgs $args)
{
$article = $args->getEntity();
if (!$article instanceof Article) {
return;
}
$emailAddress = $article->getAuthor()->getEmail();
$email = (new \Swift_Message())
->setFrom('send#example.com')
->setTo($emailAddress)
->setBody(
$this->twig->render('layouts/article/onDeleteEmail.html.twig', [
'article' => $article,
'author' => $article->getAuthor(),]
)
);
$this->mailer->send($email);
}
}
Services.yaml
App\EventListener\ArticleDeleteListener:
tags:
- { name: 'doctrine.event_listener', event: 'preRemove' }

How to use Swift Mailer in Command

I have this code...
namespace AppBundle\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
class NotificationCommand extends ContainerAwareCommand
{
protected function configure()
{
$this->setName('app:notif');
}
public function execute(InputInterface $input, OutputInterface $output)
{
$message = \Swift_Message::newInstance()
->setFrom([
'from#example.com' => 'Example'
])
->setTo('to#example.com')
->setSubject('Subject')
->setBody(
$this->getContainer()->renderView(
'emails/notify.html.twig', [
'foo' => 'bar',
]
),
'text/html'
);
$this->getContainer()->get('mailer')->send($message);
}
}
And I get an error in response
Attempted to call an undefined method named "renderView" of class
"ContainerV5drzta\appDevDebugProjectContainer".
How do I use Swift Mailer in my Command (Symfony 3.4)?
You can dependency inject these services that should solve the problem and it's also the way Symfony is trying to turn towards.
public function __construct(
EngineInterface $templating,
\Swift_Mailer $mailer,
) {
$this->templating = $templating;
$this->mailer = $mailer;
parent::__construct();
}
You can now in your execute() render a template like so:
$message = (new \Swift_Message('My message'))
->setFrom('foo#bar.com')
->setTo('bar#foo.com')
->setBody($this->templating->render('mytemplate.html.twig'), 'text/html');
$this->mailer->send($message);
You can read more about dependency injecting in Symfony here or a more generic article here.

Symfony 3 - Some difficulties to use templating service to send mail

I have a Mail.php file that contains a sendMail function that will be used by several of my controllers.
I got to have to use the "templating" service. But I have problems putting it in place.
My Services.yml:
email_management:
class: Site\PagesBundle\Utils\Mails
arguments: ['#templating']
public: true
My Mail.php:
<?php
namespace Site\PagesBundle\Utils;
use Site\PagesBundle\Entity\User;
use Site\PagesBundle\Entity\UserCas;
class Mails
{
private $templating;
public function __construct(EngineInterface $templating)
{
$this->templating = $templating;
}
public function sendMail($user,$raisonMail)
{
$transport = \Swift_SmtpTransport::newInstance();
$mailer = new \Swift_Mailer($transport);
// Entête
$message = \Swift_Message::newInstance()
->setFrom(array('############' => '############'))
//->setTo($user->getEmail());
->setTo("############")
->setCharset('utf-8')
->setContentType('text/html');
switch($raisonMail)
{
case 'formulaireInscription':
dump($user);
// (1) Confirmation de demande d'inscription
$message->setSubject("subject")
->setBody($this->templating->render("#Pages/swiftmail/CreationCompte/DemandeCreationCompte.html.twig",array(
'prenom'=>$user->getPrenom(),
'nom'=>$user->getNom(),
)));
break;
//... other cases
In my controller :
$templating = new EngineInterface;
$mail = new Mail($templating);
$mail->get('email_management')->sendEmail($user,$motif);
But now I've this error :
You must set a loader first.
Can someone help me please ? Thanks !
Assuming that the intention is to go for the service based option. Please note in general that the service class is intended to be moved into different folder in the project (to be under PagesBundle/Service folder).
services.yml (please note the changed path)
email_management:
class: Site\PagesBundle\Service\EmailManagementService
arguments: ['#templating']
public: true
EmailManagementService.php (please note the changed location & namespace)
<?php
namespace Site\PagesBundle\Service;
use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface;
use Site\PagesBundle\Entity\User;
use Site\PagesBundle\Entity\UserCas;
class Mails
{
private $templating;
public function __construct(EngineInterface $templating)
{
$this->templating = $templating;
}
...
}
Usage in controller:
$this->get('email_management')->sendMail($user,'formulaireInscription');

Call to a member function has() on a non-object on send mail in swifmailer

i have created a service
services:
app.EmailAndSms:
class: AppBundle\PublicFunctions\EmailAndSms
arguments: ["%parameter1%","%parameter2%"]
and
namespace AppBundle\PublicFunctions;
use Symfony\Bundle\SwiftmailerBundle\SwiftmailerBundle;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class EmailAndSms extends Controller{
public function __construct($parameter1,$parameter2) {
.....
....
}
public static function sendEMail() {
$Con= new Controller;
$message = \Swift_Message::newInstance()
->setSubject($maildata['sub'])
->setFrom('notification#xxxxx.com')
->setTo($maildata['To'])
->setReturnPath('notification#xxxxx.com') ->setBody($Con->renderView( 'Emails/EMailTemplate.html.twig', array('content' => $Passtemplate)), 'text/html');
}
}
got error
Error: Call to a member function has() on a non-object
"file": "/var/www/html/xxxx_rest/vendor/symfony/symfony/src/Symfony/Bundle/FrameworkBundle/Controller/Controller.php",
"line": 162,
You should start by cleaning up your code.
Remove the static modifier, static methods are to be avoided in general.
You don't need the new Controller instance since you already are extending the controller class so instead of
$Con->renderView('Emails/EMailTemplate.html.twig', array('content' => $Passtemplate)), 'text/html');
Just do
$this->renderView('Emails/EMailTemplate.html.twig', array('content' => $Passtemplate)), 'text/html');
the non-object has() is called on is the service container of your controller because as you instantiate your controller yourself the container is not injected.
In the end you don't need to exten Controller either, you should just get the twig service since this is what you need and not the whole service container.
To fix all this inject twig in your service as well as swiftmailer to send your email:
services:
app.EmailAndSms:
class: AppBundle\PublicFunctions\EmailAndSms
arguments: ["%parameter1%","%parameter2%", '#twig', #mailer]
Then in your class:
namespace AppBundle\PublicFunctions;
class EmailAndSms {
private $twig;
private $mailer;
public function __construct($parameter1,$parameter2, \Twig_environment $twig, $mailer) {
.....
....
$this->twig = $twig;
$this->mailer = $mailer;
}
public function sendEMail($maildata) {
$message = \Swift_Message::newInstance()
->setSubject($maildata['sub'])
->setFrom('notification#xxxxx.com')
->setTo($maildata['To'])
->setReturnPath('notification#xxxxx.com')
->setBody($this->twig->render('Emails/EMailTemplate.html.twig', array('content' => $Passtemplate)));
$success = $this->mailer->send($message);
return $success;
}
}
Now to use this service from a controller :
$this->get('app.EmailAndSms')->sendEmail($maildata);

Categories