How to properly call a function with instance in Symfony 4 - php

I'm trying to send an email with SMTP in Symfony 4 and I have the following function as per the docs.
public function send_smtp(\Swift_Mailer $mailer)
{
$message = (new \Swift_Message('Hello Email'))
->setFrom('send#example.com')
->setTo('MyEmail#gmail.com')
->setBody('test email content');
$mailer->send($message);
}
However, I want to call this function from a different one like
$this->send_smtp();
But it complains about 'No parameters are passed' and
$this->send_smtp(\Swift_Mailer);
also gives the error message
Undefined constant 'Swift_Mailer'
What can be the problem and how could it be solved?

There are a few solutions possible. You can add the parameter with typehinting in your action and then use it to call your function:
/**
* #Route("/something")
*/
public function something(\Swift_Mailer $mailer)
{
$this->send_smtp($mailer);
}
or you can retrieve the service from the container in the send_smtp function:
public function send_smtp()
{
$mailer = $this->get('mailer');
$message = (new \Swift_Message('Hello Email'))
->setFrom('send#example.com')
->setTo('MyEmail#gmail.com')
->setBody('test email content');
$mailer->send($message);
}

Create service e.g.
namespace App\Service\Mailer;
class Mailer
{
private $mailer;
public function __construct(\Swift_Mailer $mailer)
{
$this->mailer = $mailer;
}
public function send_smtp()
{
$message = (new \Swift_Message('Hello Email'))
->setFrom('send#example.com')
->setTo('MyEmail#gmail.com')
->setBody('test email content');
$this->mailer->send($message);
}
}
And now you can inject this service wherever you want (e.g. to your Controller action or in another service) via __construct:
public function __construct(Mailer $mailer)
{
$this->mailer = $mailer;
}
public function someAction()
{
$this->mailer->send_smtp()
}
Or you can inject it via method or via property. You can read more about injections here: https://symfony.com/doc/current/components/dependency_injection.html
P.S. I don't recommend you use container's method get because this method works only for public services, but in Symfony 4 services are private by default.

Related

Symfony 5 : send both sync and async mails in the app

I want to send emails in my symfony app both in sync and async mode. I followed the documentation of symfony messenger, but I have an issue with one key parameter : routing
framework:
messenger:
async: '%env(MESSENGER_TRANSPORT_DSN)%'
routing:
'Symfony\Component\Mailer\Messenger\SendEmailMessage': async
With this configuration, all messages are sent in queue.
I have 2 services where I send emails sync in the first and async in the second :
<?php
namespace App\Service;
use Symfony\Component\Mime\Address;
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
use Symfony\Component\Mailer\MailerInterface;
class MailManagerAsync
{
protected $mailer;
public function __construct(MailerInterface $mailer)
{
$this->mailer = $mailer;
}
protected function sendMessage($subject, $body, $bodyText, $to, $context=[])
{
$email = (new TemplatedEmail())
->from($this->from)
->to(new Address($to))
->subject($subject)
// path of the Twig template to render
->htmlTemplate($body)
->textTemplate($bodyText)
// pass variables (name => value) to the template
->context($context)
;
$this->mailer->send($email);
}
// function where i send emails
}
The second is the same as this first, called MailManagerSync.
Now here is the problem :
when i change routing like this :
routing : 'App\Service\MailManagerAsync': async
the app is sending 2 emails, 1 queued and one sent. I would like to send only one QUEUED.
Any suggestions ?
EDIT : I understand that I have to create a message class, and send it to the bus, but:
is there any easy solution to have both sync and async emails without having to develop all I said above ?
Ok here is a solution that could help other people. I think I did not come with it beacause this messenger thing is relatively new.
First create a message class :
<?php
namespace App\Message;
class EmailAsync
{
private $subject;
private $body;
private $bodyText;
private $recipient;
private $context = [];
public function __construct($subject, $body, $bodyText, $recipient, $context)
{
$this->subject = $subject;
$this->body = $body;
$this->bodyText = $bodyText;
$this->recipient = $recipient;
$this->context = $context;
}
// setters and getter
This class will transport the email.
Then here is the handler :
<?php
namespace App\MessageHandler;
use App\Message\EmailAsync;
use Symfony\Component\Mime\Address;
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Messenger\Handler\MessageHandlerInterface;
class EmailAsyncHandler implements MessageHandlerInterface
{
protected $mailer;
public function __construct(MailerInterface $mailer)
{
$this->mailer = $mailer;
}
public function __invoke(EmailAsync $email)
{
$subject = $email->getSubject();
$body = $email->getBody();
$bodyText = $email->getBodyText();
$recipient = $email->getRecipient();
$context = $email->getContext();
$emailToSend = (new TemplatedEmail())
->from("my-address#hello.com")
->to(new Address("your-address#hello.com"))
->subject($subject)
// path of the Twig template to render
->htmlTemplate($body)
->textTemplate($bodyText)
// pass variables (name => value) to the template
->context($context)
;
$this->mailer->send($emailToSend);
}
}
and now you can set the right routing parameter :
framework:
messenger:
transports:
async: '%env(MESSENGER_TRANSPORT_DSN)%'
routing:
# Route your messages to the transports
'App\Message\EmailAsync': async
Now if i want to send an async mail, i use the bus within my service :
<?php
namespace App\Service;
use App\Message\EmailAsync;
use Symfony\Component\Messenger\MessageBusInterface;
class MailManagerAsync
{
protected $bus;
public function __construct(MessageBusInterface $bus)
{
$this->bus = $bus;
}
protected function sendMessage($subject, $body, $bodyText, $to, $context = [])
{
$emailAsync = new EmailAsync($subject, $body, $bodyText, $to, $context);
$this->bus->dispatch($emailAsync);
}
// SEND EMAILS
}

Symfony 5 Mailer undefined method named "htmlTemplate"

I'm looking to use Symfony Mailer Inside a personal project. The idea is the user subscribe to a newsletter, then he received a mail that confirm he subscribe.
I've made a function send Mail inside my controller to call this function when the form is submit.
function sendEmail(MailerInterface $mailer, $useremail)
{
$email = (new Email())
->from('mail#mail.exemple')
->to($useremail)
->subject('Great !')
->htmlTemplate('emails/signup.html.twig');
$mailer->send($email);
return $email;
}
/**
* #Route("/", name="homepage")
*/
public function index(Request $request, MailerInterface $mailer)
{
$mailing = new Mailing();
$form = $this->createForm(MailingType::class, $mailing);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$task = $form->getData();
$this->sendEmail($mailer , $request->request->get('mailing')['email']);
return $this->redirectToRoute('homepage');
}
When I submit the form, Everything is okay but when it come inside my sendEmail function the following error appear :
Attempted to call an undefined method named "htmlTemplate" of class
"Symfony\Component\Mime\Email".
Do you have any idea about why this error appear ? I'don't understand what's happening.
THank you.
To use a template, you need to use a TemplatedEmail as described in the docs
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
function sendEmail(MailerInterface $mailer, $useremail)
{
$email = (new TemplatedEmail())
->from('mail#mail.exemple')
->to($useremail)
->subject('Great !')
->htmlTemplate('emails/signup.html.twig');
$mailer->send($email);
return $email;
}
You need to use 'TemplatedEmail' rater than 'Email' class

Symfony SwiftMailer: not sending if the controller does not return a $this->render() response

I have a Symfony project which (very simplified) looks like this:
Controller/MyToolController.php
namespace App\Controller;
use App\Services\ToolsService;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class MyToolController extends AbstractController
{
private $toolsService;
/**
* #param ToolsService|null $toolsService
*/
public function __construct(ToolsService $toolsService = null) {
$this->toolsService = $toolsService;
}
public function run() {
// some more code here...
$mailContent = $this->render('site/mail.html.twig', $data)->getContent();
$this->toolsService->sendMail($from, $to, $bcc, $mailContent, $subject);
// if I remove the following line, no emails are sent out!
return $this->render('site/thankyou.html.twig', $data);
}
}
Services/MyToolService.php
namespace App\Services;
class ToolsService
{
/** #var \Swift_Mailer */
private $mailer;
/**
* #param \Swift_Mailer $mailer
*/
public function __construct(\Swift_Mailer $mailer)
{
$this->mailer = $mailer;
}
public function sendMail($from, $to, $bcc, $body, $subject) {
$mail = ( new \Swift_Message() )
->setSubject($subject)
->setFrom($from)
->setTo($to)
->setBcc($bcc)
->addPart($body, 'text/html');
$res = $this->mailer->send($mail);
$this->logger->info('Email sent');
return $res;
}
}
If you look at MyToolController.php you see that I call a service which sends the email.
If I return a Response object in my run() function, everything goes well - but if I omit this, nothing is sent. Same if I send multiple emails in a loop and I run into a timeout.
Strangely $mailer->send() is called in any case - and it returns 1 - and I see an entry in the log I am writing in the sendmail() function. But no email leaves the server.
This is my SwiftMailer configuration:
swiftmailer:
url: '%env(MAILER_URL)%'
spool: { type: 'memory' }
Your mails are being spooled in memory, which means:
When you use spooling to store the emails to memory, they will get sent right before the kernel terminates. This means the email only gets sent if the whole request got executed without any unhandled exception or any errors.
If you do not return a Response object from your controller, there will be an unhandled exception, the whole request won't be executed, and the mails will never be sent.
Controllers need to return a Symfony\Component\HttpFoundation\Response so the request can be handled normally. If you want to return an empty response, you can simply do:
return new Response();

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

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?

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