Create an email service in Symfony - php

I try to make a service in my Symfony Sonata bundle to send an email to a specific person as soon as an order is created. The person to whom the email is send is the person the user select to approve the order.
I try to follow the service container documentation on Symfony's website, but it feels too incomplete for me. I want to see a complete example and not just a few snippets.
This is my email service class so far;
<?php
namespace Qi\Bss\BaseBundle\Lib\PurchaseModule;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Authorization\AuthorizationChecker;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
use Doctrine\ORM\EntityManager;
/**
*
*/
class Notifier
{
/**
* Service container
* #var type
*/
private $serviceContainer;
public function notifier($subject, $from, $to, $body) {
$message = \Swift_Message::newInstance()
->setSubject($subject)
->setFrom($from)
->setTo($to)
->setBody($body)
;
$this->serviceContainer->get('mailer')->send($message);
}
/**
* Sets the sales order exporter object
* #param type $serviceContainer
*/
public function setServiceContainer($serviceContainer)
{
$this->serviceContainer = $serviceContainer;
}
}
My service inside my services.yml file looks like this;
bss.pmod.order_notifier:
class: Qi\Bss\BaseBundle\Lib\PurchaseModule\Notifier
arguments: ["#mailer"]
And when I call the service in a controller action I use this line;
$this->get('bss.pmod.order_notifier')->notifier();
The error I'm getting state;
Notice: Undefined property:
Qi\Bss\FrontendBundle\Controller\PmodOrderController::$serviceContainer
Like I said before, I've looked at the service container documentation but I can't understand it.
Can someone please help me with a nice full example explaining everything?

You don't need setServiceContainer method in your service class, instead of it you should have __construct accepting mailer as first argument:
class Notifier
{
protected $mailer;
public function __construct($mailer)
{
$this->mailer = $mailer;
}
public function notifier() {
$message = \Swift_Message::newInstance()
->setSubject('Simon Koning')
->setFrom('noreply#solcon.nl')
->setTo('simon#simonkoning.co.za')
->setBody('The quick brown fox jumps over the lazy dog.')
;
$this->mailer->send($message);
}
}

Related

List of users in Symfony 5 (Bolt 4)

I'm using Bolt 4 CMS which is based on Symfony 5. In a controller I wrote, I would like to list all the users from my database, to retrieve their email addresses and send them an email. For now I am simply trying to retrieve the email address from the username.
In this example https://symfony.com/doc/current/security/user_provider.html, it shoes how to create your own class to deal with users from the database:
// src/Repository/UserRepository.php
namespace App\Repository;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Symfony\Bridge\Doctrine\Security\User\UserLoaderInterface;
class UserRepository extends ServiceEntityRepository implements UserLoaderInterface
{
// ...
public function loadUserByUsername(string $usernameOrEmail)
{
$entityManager = $this->getEntityManager();
return $entityManager->createQuery(
'SELECT u
FROM App\Entity\User u
WHERE u.username = :query
OR u.email = :query'
)
->setParameter('query', $usernameOrEmail)
->getOneOrNullResult();
}
}
In my custom controller, I then call this class and function:
// src/Controller/LalalanEventController.php
namespace App\Controller;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Email;
use App\Repository\LalalanUserManager;
class LalalanEventController extends AbstractController
{
/**
* #Route("/new_event_email")
*/
private function sendEmail(MailerInterface $mailer)
{
$userManager = new LalalanUserManager();
$email = (new Email())
->from('aaa.bbb#ccc.com')
->to($userManager('nullname')->email)
->subject('Nice title')
->text('Sending emails is fun again!')
->html('<p>See Twig integration for better HTML integration!</p>');
$mailer->send($email);
}
}
Unfortunately, in the example, the class extends from ServiceEntityRepository, which requires a ManagerRegistry for the constructor. Does anyone have a clue what could I change to solve this?
Thanks in advance!
As said in the doc,
User providers are PHP classes related to Symfony Security that have two jobs:
Reload the User from the Session
Load the User for some Feature
So if you want only to get list of users, you have just to get the UserRepository like this:
/**
* #Route("/new_event_email")
*/
private function sendEmail(MailerInterface $mailer)
{
$userRepository = $this->getDoctrine()->getRepository(User::class);
$users = $userRepository->findAll();
// Here you loop over the users
foreach($users as $user) {
/// Send email
}
}
Doctrine reference: https://symfony.com/doc/current/doctrine.html
You need also to learn more about dependency injection here: https://symfony.com/doc/current/components/dependency_injection.html

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();
...

Laravel/Lumen | Failing to dispatch event

This is my first time using events in Laravel/Lumen.
I am actually using Lumen and I am trying to dispatch an instance of Mailable when a new user signs up in order to send an email in the background.
I believe I have set it up right, but I keep getting this error...
Type error: Argument 1 passed to Illuminate\Mail\Mailable::queue() must implement interface Illuminate\Contracts\Queue\Factory, instance of Illuminate\Queue\DatabaseQueue given
I can't actually see within the error message itself where the issue is coming from e.g. there is no line numbers.
However, this is my code...
AuthenticationContoller.php
$this->dispatch(new NewUser($user));
NewUser.php
<?php
namespace App\Mail;
use App\Models\User;
use Illuminate\Mail\Mailable;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Queue\ShouldQueue;
class NewUser extends Mailable implements ShouldQueue
{
use Queueable, SerializesModels;
protected $user;
public function __construct(User $user)
{
$this->user = $user;
}
/**
* Build the message.
*
* #return $this
*/
public function build()
{
return $this->view('test')->to('test#test.com', 'Test')
->from('test#test.com', 'test')->replyTo('test#test.com', 'test')
->subject('Welcome to the blog!');
}
}
I ran into the same issue. It seems like Lumen and Illuminate/Mailer don't work together all that well.
However I found quite an easy fix in a Github thread.
Basically you just have to create a new service provider in your app/Providers directory.
MailServiceprovider.php
<?php
namespace App\Providers;
use Illuminate\Mail\Mailer;
use Illuminate\Mail\MailServiceProvider as BaseProvider;
class MailServiceProvider extends BaseProvider
{
/**
* Register the Illuminate mailer instance.
*
* #return void
*/
protected function registerIlluminateMailer()
{
$this->app->singleton('mailer', function ($app) {
$config = $app->make('config')->get('mail');
// Once we have create the mailer instance, we will set a container instance
// on the mailer. This allows us to resolve mailer classes via containers
// for maximum testability on said classes instead of passing Closures.
$mailer = new Mailer(
$app['view'], $app['swift.mailer'], $app['events']
);
// The trick
$mailer->setQueue($app['queue']);
// Next we will set all of the global addresses on this mailer, which allows
// for easy unification of all "from" addresses as well as easy debugging
// of sent messages since they get be sent into a single email address.
foreach (['from', 'reply_to', 'to'] as $type) {
$this->setGlobalAddress($mailer, $config, $type);
}
return $mailer;
});
$this->app->configure('mail');
$this->app->alias('mailer', \Illuminate\Contracts\Mail\Mailer::class);
}
}
And then you just have to register this service provider in your bootstrap/app.php instead of the default one by adding the following line:
$app->register(\App\Providers\MailServiceProvider::class);

Symfony2 accessing mailer service

I am working with Symfony2 and am trying to access mailer service but constantly get this error message:
{"errors":{"code":500,"message":"Error: Call to a member function get() on a non-object"}}
my code:
<?php
namespace TestBundle\UserBundle\Utilities;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class EmailServiceClass extends Controller
{
public function sendEmail($subject, $to, $body)
{
$msg = \Swift_Message::newInstance();
$msg->setSubject($subject);
$msg->setTo($to);
$msg->setBody($body);
$msg->setContentType('text/html');
$msg->setCharset('utf-8');
$msg->setFrom('test#gmail.com');
$this->get('mailer')->send($msg);
}
}
The error comes from this line: $this->get('mailer')->send($msg);
From what I understand when I extend Controller calls I should be able to access this service without having to specifically create a service.
You should do it another way.
It's the best when your services are POPO (Plain Old PHP Object). Also dependencies should be passed via constructor, so let's refactor your service a little bit:
class EmailServiceClass //no need to extend anything
{
private $mailerService; //dependency as private property
//we're passing dependencies via constructor
public function __construct(\Swift_Mailer $mailerService)
{
$this->mailerService = $mailerService;
}
public function sendEmail($subject, $to, $body)
{
$msg = \Swift_Message::newInstance();
$msg->setSubject($subject);
$msg->setTo($to);
$msg->setBody($body);
$msg->setContentType('text/html');
$msg->setCharset('utf-8');
$msg->setFrom('test#gmail.com');
//now you can access mailer service like that
$this->mailerService->send($msg);
}
}
Now of course you need to modify the way you configure this service in Service Container.
You probably have something like this now:
services:
your_mailer:
class: TestBundle\UserBundle\Utilities\EmailServiceClass
Now you need to add arguments line in order to pass dependencies:
services:
your_mailer:
class: TestBundle\UserBundle\Utilities\EmailServiceClass
arguments: ['#mailer']
The last line defines arguments that will be passed to your service's constructor. mailer is the name of Swift_Mailer service.
More details about how to manage service dependencies can be found in Symfony's Book
Please don't extend the Controller class with a service class. You should inject the dependencies that you require using the services.yml. Please implement your service to be something along the lines of:
MyController.php:
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class MyController extends Controller
{
public function sendEmailAction()
{
$subject = //..
$to = //..
$body = //..
$this->get('email_service.class')->sendEmail($subject, $to, $body);
// Return a template, or redirect here..
return new Response();
}
}
EmailServiceClass.php
class EmailServiceClass
{
private $mailer;
public function __construct(\Swift_Mailer $mailer)
{
$this->mailer = $mailer;
}
public function sendEmail($subject, $to, $body)
{
$msg = \Swift_Message::newInstance();
$msg->setSubject($subject);
$msg->setTo($to);
$msg->setBody($body);
$msg->setContentType('text/html');
$msg->setCharset('utf-8');
$msg->setFrom('test#gmail.com');
$this->mailer->send($msg);
}
}
app/config/services.yml
email_service.class:
class: TestBundle\UserBundle\Utilities\EmailServiceClass
arguments: ['#mailer']

What is the difference between App\Events and App\Handlers\Events in Laravel?

I was watching this lesson and was trying to figure out which directory to put the EmailNotifier class file since it's an Event.
I don't know if it belongs in App\Events or App\Handlers\Events.
This is what I currently have:
<?php namespace App\Mailers;
use Illuminate\Mail\Mailer as Mail;
abstract class Mailer {
private $mail;
function __construct(Mail $mail)
{
$this->mail = $mail;
}
public function sendTo($user, $subject, $view, $data)
{
$this->mail->queue($view, $data, function ($message) use ($user, $subject)
{
$message->to($user->email)->subject($subject);
});
}
}
<?php namespace App\Mailers;
use App\User;
class UserMailer extends Mailer {
/**
* #param User $user
*/
public function sendWelcomeMessageTo(User $user)
{
$subject = 'Welcome To Backstage!';
$view = 'emails.registeration.confirm';
$data = [];
return $this->sendTo($user, $subject, $view, $data);
}
}
<?php namespace App\Handlers\Events;
class EmailNotifier extends Event {
private $mailer;
public function __construct(UserMailer $mailer)
{
$this->mailer = $mailer;
}
public function whenUserHasRegistered(UserHasRegistered $event)
{
$this->mailer->sendWelcomeMessageTo($event->user);
}
}
<?php namespace App\Events;
use App\Events\Event;
use Illuminate\Queue\SerializesModels;
class UserHasRegistered extends Event {
use SerializesModels;
/**
* Create a new event instance.
*
* #return void
*/
public function __construct()
{
//
}
}
That's more of a discretionary concern. You generally want to categorize similar purpose items into the same namespace. Handlers\Events sounds like a place to put event handlers maybe, or perhaps it's a place for events that stem from handlers.
It sounds like you are on the right track placing an event in an Events namespace. Convention and consistency is the key. It doesn't matter so much as to what the final namespace is, just as long as it is consistent. IMO a more logical approach would be to have App\Event for all of your events and potentially sub namespace it from there for event categories. Handlers would be more self explanatory if they were somewhere like App\EventHandler and again sub namespaced into groups as needed.
That way it is pretty clear to an outsider who may need to work with your code in the future. That's my two cents as far as a general organization structure goes.
With deeper context into Laravel as the link laracasts.com implies. The App\Event namespace is for events which is what your EmailNotifier looks to be, where App\Handlers\Events is generally for handlers, subscribers, listeners, whatever you want to call them.

Categories