symfony2 email not sent in command - symfony

I'm developing one application with symfony2
In one side of application I'm sending emails, everything ok with this.
But now I create one command to run in crontab, but this dont send emails.
this is my command:
use Doctrine\ORM\EntityManager;
use Symfony\Component\Templating\EngineInterface;
class Sender {
protected $em; protected $twig; protected $mailer;
public function __construct($em, \Twig_Environment $twig, \Swift_Mailer $mailer) {
$this->em = $em;
$this->twig = $twig;
$this->mailer = $mailer;
}
public function runSender() {
$proj = $this->em->createQuery ...
$maillist = $this->em->createQuery ...
$templateFile = "projectprojBundle:MailList:emailNew.html.twig";
$templateContent = $this->twig->loadTemplate($templateFile);
$body = $templateContent->render(array('proj' => $proj));
foreach ($maillist as $m) {
$message = \Swift_Message::newInstance()->setSubject('New projects')
->setFrom('...')->setTo($m['email'])
->setContentType('text/html')
->setBody(trim($body));
$this->mailer->send($message);
} } }
everything is ok with the queries, i tested.
and if i can send from other classes why i cant here?

Add this at the end of the execution of your command:
$spool = $this->mailer->getTransport()->getSpool();
$transport = $this->getContainer()->get('swiftmailer.transport.real');
$spool->flushQueue($transport);
You have to extend the ContainerAwareCommand class to have access to the service container in your command.

Probably your spool settings in config.yml
Use spool: { type: memory } to send e-mails instantly
# app/config/config.yml
swiftmailer:
# ...
spool: { type: memory }

Related

Symfony 4 Accessing Swift_Mailer in Service

I have been looking at the Symfony 4.1 documentation on using the Swift_mailer. However, it appears the documentation is only assumed it being used in the Controller classes. I'm trying to create a Service with some reusable functions that send email.
I created a EmailService.php file in my service directory. When creating a new instance of this service, it quickly throws and error:
"Too few arguments to function
App\Service\EmailService::__construct(), 0 passed in
*MyApp\src\Controller\TestController.php on line 33
and exactly 1 expected"
I'm not sure how to pass \Swift_Mailer $mailer into the __construct correctly? I have auto wiring enabled in the services.yaml, so i'm not sure what I need to do differently?
class EmailService
{
private $from = 'support#******.com';
private $mailer;
public function __construct(\Swift_Mailer $mailer)
{
$this->mailer = $mailer;
}
How do I pass the \Swift_Mailer into this EmailService construct?
I tried adding this to my config\services.yaml with no success:
App\Service\EmailService:
arguments: ['#mailer']
As mentioned by dbrumann in a comment, I needed to follow the proper way of injecting services.
First, I needed to add the services to config/services.yaml
#config/services.yaml
emailservice:
class: App\Service\EmailService
arguments: ['#swiftmailer.mailer.default', '#twig']
public: true
Second, I need to setup the service to accept both the mailer, and twig for rendering the template.
#App/Service/EmailService.php
<?php
namespace App\Service;
class EmailService
{
private $from = 'support#*****.com';
private $mailer;
private $templating;
public function __construct(\Swift_Mailer $mailer, \Twig\Environment $templating)
{
$this->mailer = $mailer;
$this->templating = $templating;
}
public function userConfirmation(string $recipient, string $confCode) : bool
{
$message = (new \Swift_Message())
->setSubject('Some sort of string')
->setFrom($this->from)
->setTo($recipient)
->setBody(
$this->templating->render(
'email/UserConfirmation.html.twig',
array('confCode' => $confCode)
),
'text/html'
)
/*
* If you also want to include a plaintext version of the message
->addPart(
$this->renderView(
'emails/UserConfirmation.txt.twig',
array('confCode' => $confCode)
),
'text/plain'
)
*/
;
return $this->mailer->send($message);
}
}
Third, to call it from the controller, make sure your controller is extending Controller and not the AbstractController! Crucial step!! Here is an example based on the parameters I require in my service:
public function userConfirmation()
{
$emailService = $this->get('emailservice');
$sent = $emailService->userConfirmation('some#emailaddress.com', '2ndParam');
return new Response('Success') //Or whatever you want to return
}
I hope this helps people. AbstractController does not give you the proper access to the service containers.
#config/services.yaml
App\Service\EmailService
arguments: ['#swiftmailer.mailer.default']
public: true
And in your controller :
public function userConfirmation(EmailService $emailService)
{
$sent = $emailService->userConfirmation('some#emailaddress.com', '2ndParam');
return new Response('Success') //Or whatever you want to return
}
Use FQCN "App\Service\MyService" to declare services in services.yaml and a proper legacy_aliases.yaml file to declare legacy aliases like "app.service.my.service" it helps keep your services.yaml clean...

Symfony 4 Cannot inject scalar value on Service

I have the ff class:
namespace App\Component\Notification\RealTimeNotification;
use App\Component\Notification\NotificationInterface;
class EmailNotification implements NotificationInterface
{
private $logNotification;
private $mailer;
private $engine;
// This will appear on From field on Email.
private $mailerFrom;
public function __construct(LogNotification $logNotification, \Swift_Mailer $mailer, \Twig_Environment $twig, string $from)
{
$this->logNotification = $logNotification;
$this->mailer = $mailer;
$this->twig = $twig;
$this->mailerFrom = $mailerFrom;
}
public function send(array $options): void
{
// Resolve options
$this->resolveOptions($options);
$sendTo = $options['sendTo'];
$subject = $options['subject'];
$template = $options['template'];
$data = $options['data'];
$body = $this->createTemplate($template, $data);
$this->sendEmail($sendTo, $subject, $body);
}
protected function sendEmail($sendTo, $subject, $body): void
{
dump($this->mailerFrom);
$message = (new \Swift_Message())
->setSubject($subject)
->setFrom($this->mailerFrom)
->setTo($sendTo)
->setBody($body, 'text/html')
;
$this->mailer->send($message);
}
protected function createTemplate($template, $data): string
{
return $this->twig->render($template, $data);
}
protected function resolveOptions(array $options): void
{
}
protected function createLog(array $email): void
{
$message = 'Email has been sent to: ' . $email;
$this->logNotification->send([
'message' => $message,
]);
}
}
I tried to manually wire all the arguments with the following:
# Notification
app.log_notification:
class: App\Component\Notification\RealTimeNotification\LogNotification
app.email_notification:
class: App\Component\Notification\RealTimeNotification\EmailNotification
decorates: app.log_notification
decoration_inner_name: app.log_notification.inner
arguments:
$logNotification: '#app.log_notification.inner'
$mailer: '#mailer'
$twig: '#twig'
$from: '%mailer_from%'
However, when I run the app it throws the exception:
Cannot autowire service
"App\Component\Notification\RealTimeNotification\EmailNotification":
argument "$from" of method "__construct()" must have a type-hint or be
given a value explicitly
Why is this event happening?
Thanks!
Answer by #Matteo is great! You can even drop service definition and delegate to parameter binding since Smyfony 3.4+/2018+:
# config/services.yaml
services:
_defaults:
bind:
$adminEmail: 'manager#example.com'
# same as before
App\:
resource: '../src/*'
Do you want more example and logic behind it? Find it here: https://www.tomasvotruba.cz/blog/2018/01/22/how-to-get-parameter-in-symfony-controller-the-clean-way/#change-the-config
Autowiring only works when your argument is an object. But if you have a scalar argument (e.g. a string), this cannot be autowired: Symfony will throw a clear exception.
You should Manually Wiring Arguments and explicitly configure the service, as example:
# config/services.yaml
services:
# ...
# same as before
App\:
resource: '../src/*'
exclude: '../src/{Entity,Migrations,Tests}'
# explicitly configure the service
App\Updates\SiteUpdateManager:
arguments:
$adminEmail: 'manager#example.com'
Thanks to this, the container will pass manager#example.com to the
$adminEmail argument of __construct when creating the
SiteUpdateManager service. The other arguments will still be
autowired.
Hope this help

How to get current firewall's check_path?

Question: How to get the form_login.check_path by given firewall name?
We subscribe to Symfony\Component\Security\Http\SecurityEvent::INTERACTIVE_LOGIN in order to log successful logins inside an Application that has multiple firewalls.
One firewall uses JWT tokens via Guard authentication which has the negative effect that this event is triggered for every request with a valid token.
We have currently solved this by manually checking whether the current route matches the firewall's check-path and stopping the event-propagation together with an early return otherwise.
As we're adding more firewalls (with different tokens) I'd like to solve this more generally. Therefore I want to check whether the current route matches the current firewalls check-path without hardcoding any route or firewall-name.
There is a class to generate Logout URLs for the current firewall used by Twig logout_path() method which gets the logout route/path from the firewall listeners somehow. (Symfony\Component\Security\Http\Logout\LogoutUrlGenerator)
Before I hop into a long debugging session I thought maybe someone has solved this case before ;)
Any ideas?
Example code:
class UserEventSubscriber implements EventSubscriberInterface
{
/** #var LoggerInterface */
protected $logger;
/** #var FirewallMapInterface|FirewallMap */
protected $firewallMap;
public function __construct(LoggerInterface $logger, FirewallMapInterface $firewallMap)
{
$this->logger = $logger;
$this->firewallMap = $firewallMap;
}
public function onInteractiveLogin(InteractiveLoginEvent $event)
{
$request = $event->getRequest();
$firewallName = $this->firewallMap->getFirewallConfig($request)->getName();
$routeName = $request->get('_route');
if (('firewall_jwt' === $firewallName) && ('firewall_jwt_login_check' !== $routeName)) {
$event->stopPropagation();
return;
}
$this->logger->info(
'A User has logged in interactively.',
array(
'event' => SecurityEvents::INTERACTIVE_LOGIN,
'user' => $event->getAuthenticationToken()->getUser()->getUuid(),
));
The check_path option is only available from authentication factory/listener, so you could pass this configuration manually to the subscriber class while the container is building.
This solution take account that check_path could be a route name or path, that's why HttpUtils service is injected too:
namespace AppBundle\Subscriber;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
use Symfony\Component\Security\Http\FirewallMapInterface;
use Symfony\Component\Security\Http\HttpUtils;
use Symfony\Component\Security\Http\SecurityEvents;
class UserEventSubscriber implements EventSubscriberInterface
{
private $logger;
private $httpUtils;
private $firewallMap;
private $checkPathsPerFirewall;
public function __construct(LoggerInterface $logger, HttpUtils $httpUtils, FirewallMapInterface $firewallMap, array $checkPathsPerFirewall)
{
$this->logger = $logger;
$this->httpUtils = $httpUtils;
$this->firewallMap = $firewallMap;
$this->checkPathsPerFirewall = $checkPathsPerFirewall;
}
public function onInteractiveLogin(InteractiveLoginEvent $event)
{
$request = $event->getRequest();
$firewallName = $this->firewallMap->getFirewallConfig($request)->getName();
$checkPath = $this->checkPathsPerFirewall[$firewallName];
if (!$this->httpUtils->checkRequestPath($request, $checkPath)) {
$event->stopPropagation();
return;
}
$this->logger->info('A User has logged in interactively.', array(
'event' => SecurityEvents::INTERACTIVE_LOGIN,
'user' => $event->getAuthenticationToken()->getUser()->getUsername(),
));
}
public static function getSubscribedEvents()
{
return [SecurityEvents::INTERACTIVE_LOGIN => 'onInteractiveLogin'];
}
}
After regiter this subscriber as service (AppBundle\Subscriber\UserEventSubscriber) we need implement PrependExtensionInterface in your DI extension to be able to access the security configuration and complete the subscriber definition with the check paths per firewall:
namespace AppBundle\DependencyInjection;
use AppBundle\Subscriber\UserEventSubscriber;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\Extension;
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
class AppExtension extends Extension implements PrependExtensionInterface
{
// ...
public function prepend(ContainerBuilder $container)
{
$checkPathsPerFirewall = [];
$securityConfig = $container->getExtensionConfig('security');
foreach ($securityConfig[0]['firewalls'] as $name => $config) {
if (isset($config['security']) && false === $config['security']) {
continue; // skip firewalls without security
}
$checkPathsPerFirewall[$name] = isset($config['form_login']['check_path'])
? $config['form_login']['check_path']
: '/login_check'; // default one in Symfony
}
$subscriber = $container->getDefinition(UserEventSubscriber::class);
$subscriber->setArgument(3, $checkPathsPerFirewall);
}
}
I hope it fits your need.
for PHP8
In __construct :
public function __construct(
private RequestStack $requestStack,
private FirewallMapInterface $firewallMap
)
{
}
use this :
$firewallName = $this->firewallMap->getFirewallConfig($this->requestStack->getCurrentRequest())->getName();

Calling ipn paypal, PayumBundle

I have problems with paypal calling the notification function, i think i have everything set up correctly, however there is little documentation available about this
use Maxim\CMSBundle\Entity\NotificationDetails;
use Payum\Action\ActionInterface;
use Payum\Request\NotifyTokenizedDetailsRequest;
use Symfony\Bridge\Doctrine\RegistryInterface;
use Maxim\CMSBundle\Entity\Visitor;
class StoreNotificationAction implements ActionInterface
{
protected $doctrine;
protected $logger;
public function __construct(RegistryInterface $doctrine, $logger) {
$this->doctrine = $doctrine;
$this->logger = $logger;
}
/**
* {#inheritDoc}
*/
public function execute($request)
{
/** #var NotifyTokenizedDetailsRequest $request */
$this->logger->err("hi");
$notification = new NotificationDetails;
$notification->setPaymentName($request->getTokenizedDetails()->getPaymentName());
$notification->setDetails($request->getNotification());
$notification->setCreatedAt(new \DateTime);
$this->doctrine->getManager()->persist($notification);
$this->doctrine->getManager()->flush();
}
/**
* {#inheritDoc}
*/
public function supports($request)
{
return $request instanceof NotifyTokenizedDetailsRequest;
}
}
which i am calling with a service defined in services.yml and also this is my configuration according to the git example:
payum:
contexts:
paypal_express_checkout_plus_doctrine:
paypal_express_checkout_nvp:
api:
options:
username: 'MYUSER'
password: 'MYPAS'
signature: 'MYSIGNATURE'
sandbox: true
actions:
- myname.action.store_notification
storages:
myname\mybundle\Entity\PaypalExpressPaymentDetails:
doctrine:
driver: orm
payment_extension: true
myname\mybundle\Entity\TokenizedDetails:
doctrine:
driver: orm
payment_extension: true
Please check you correctly setup NOTIFY_URL. For this you have to generate tokenForNotifyRoute and use its targetUrl as notify. See an example of prepareAction in the sandbox.

Send email outside Controller Action in Symfony2

I am using Symfony2 and FOSUserBundle
I have to send email using SwiftMailer in my mailer class which is not a controller or its action. I am showing what I have coded
<?php
namespace Blogger\Util;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class FlockMailer {
public function SendEmail(){
$message = \Swift_Message::newInstance()
->setSubject('Hello Email')
->setFrom('send#example.com')
->setTo('to#example.com')
->setBody('testing email');
$this->get('mailer')->send($message);
}
}
But I am getting the following error
Fatal error: Call to undefined method Blogger\Util\FlockMailer::get() ....
How can I proceed?
EDIT: as i din't tested the code you should also specify the transport layer if you don't use the service container for getting the instance of the mailer. Look at: http://swiftmailer.org/docs/sending.html
You're doing it wrong. You basically want a service, not a class that extends Controller. It's not working because service container is not available in SendMail() function.
You have to inject the service container into your own custom helper for sending email. A few examples:
namespace Blogger\Util;
class MailHelper
{
protected $mailer;
public function __construct(\Swift_Mailer $mailer)
{
$this->mailer = $mailer;
}
public function sendEmail($from, $to, $body, $subject = '')
{
$message = \Swift_Message::newInstance()
->setSubject($subject)
->setFrom($from)
->setTo($to)
->setBody($body);
$this->mailer->send($message);
}
}
To use it in a controller action:
services:
mail_helper:
class: namespace Blogger\Util\MailHelper
arguments: ['#mailer']
public function sendAction(/* params here */)
{
$this->get('mail_helper')->sendEmail($from, $to, $body);
}
Or elsewhere without accessing the service container:
class WhateverClass
{
public function whateverFunction()
{
$helper = new MailerHelper(new \Swift_Mailer);
$helper->sendEmail($from, $to, $body);
}
}
Or in a custom service accessing the container:
namespace Acme\HelloBundle\Service;
class MyService
{
protected $container;
public function setContainer($container) { $this->container = $container; }
public function aFunction()
{
$helper = $this->container->get('mail_helper');
// Send email
}
}
services:
my_service:
class: namespace Acme\HelloBundle\Service\MyService
calls:
- [setContainer, ['#service_container']]
Just forget about the setter and the getter:
$transport = \Swift_MailTransport::newInstance();
$mailer = \Swift_Mailer::newInstance($transport);
$helper = new MailHelper($mailer);
$helper->sendEmail($from, $to, $body,$subject);
That worked for me with the MailHelper called from a listener method.

Resources