On the Laravel docs, it states:
Using The Notification Facade
Alternatively, you may send
notifications via the Notification facade. This is useful primarily
when you need to send a notification to multiple notifiable entities
such as a collection of users. To send notifications using the facade,
pass all of the notifiable entities and the notification instance to
the send method:
Notification::send($users, new InvoicePaid($invoice));
So I am doing this within my controller:
public function index()
{
$subscribers = Subscribers::all();
Notification::send($subscribers, new NewVacancy($subscribers));
}
And here is my Notification class
class NewVacancy extends Notification implements ShouldQueue
{
use Queueable;
public $subscriber;
public function __construct( $subscribers)
{
$this->subscriber = $subscribers;
}
public function toMail($notifiable)
{
return (new MailMessage)->view(
'mail.new-vacancy',
['uuid' => $this->subscriber->uuid]// This fails as $subscriber is a collection
);
}
....
The problem is that within the NewVacancy class, the $subscriber that is passed in is a full collection of all subscribers and not the individual notification being sent.
Now I know I could do a loop over $subscribers and fire the Notification::send() each time but that defeats the point of using facade to begin with.
The general goal is to send emails to all $subscribers with the ability to pass in unique subscriber data using a blade template.
I found out you can access the current user via the $notifiable entity thats passed into the toMail() method.
public function toMail($notifiable)
{
return (new MailMessage)->view(
'mail.new-vacancy',
['uuid' => $notifiable->uuid]
);
}
Please note that $notifiable represents the user object which is getting notified.
$user_id = $notifiable->id;
Related
I need to test the Laravel Mailer using PHPunit, I am using CRUD Operations, where if any one the method fails, It should trigger the mail. I need to test the mail part, below is the code.
public function index()
{
$response = Http::withBasicAuth(userName,passWord)
->get(connection());
$this->html_mail($response);
return $response->json();
}
public function show($id)
{
$response = Http::withBasicAuth(userName, passWord)
->get(connection());
// check response & send mail if error
$this->html_mail($response);
$record = collect($response->json() ['output'])
->where($this->primaryKeyname, $id)->first();
return $record;
}
Mailer method:
public function html_mail($response)
{
if ($response->failed() || $response->serverError() || $response->clientError()) {
Mail::send([], [], function ($message) use ($response) {
$message->to('foo#example.com');
$message->subject('Sample test');
$message->setBody($response, 'text/html');
});
}
return 'Mail Sent Successfully';
}
}
Could someone please help to test the Mailer method using PHPunit.
Thanks.
It looks like there might be some code missing in your examples, but generally you're looking for Laravel's Mail::fake() method:
# tests/Feature/YourControllerTest.php
use Illuminate\Support\Facades\Mail;
/**
* #test
*/
public function index_should_send_an_email_if_authentication_fails(): void
{
Mail::fake();
$this->withToken('invalidToken', 'Basic')
->get('your.route.name');
Mail::assertSent(function ($mail) {
// Make any assertions you need to in here.
return $mail->hasTo('foo#example.com');
});
}
There's also an opportunity to clean up your controller methods here by leveraging middleware for authentication rather than repeating it in every method.
Digging into Illuminate\Auth\SessionGuard, Laravel automaticallys fire an Illuminate\Auth\Events\Failed event if authentication fails. Instead of sending directly from your controller, you might consider registering an event listener and attaching it to that event, then letting the listener dispatch a mailable notification.
# app/Providers/EventServiceProvider
/**
* The event listener mappings for the application.
*
* #var array
*/
protected $listen = [
'Illuminate\Auth\Events\Failed' => [
'App\\Listeners\\FailedAuthAttempt',
],
];
With those changes, your testing also becomes easier:
# tests/Feature/Notifications/FailedAuthAttemptTest.php
use App\Notifications\FailedAuthAttempt;
use Illuminate\Notifications\AnonymousNotifiable;
use Illuminate\Support\Facades\Notification;
/**
* #test
*/
public function it_should_send_an_email_upon_authentication_failure(): void
{
Notification::fake();
$this->withToken('invalidToken', 'Basic')
->get('your.route.name');
Notification::assertSentTo(new AnonymousNotifiable(), FailedAuthAttempt::class);
}
Now, any route in your application that uses Laravel's auth.basic middleware will automatically send the FailedAuthAttempt notification upon failure. This also makes it easier to, for example, send these notices to a Slack channel rather than sending emails.
Hi I am a beginner for events and listeners in laravel. So please explain me how to achieve this :
Aim :
Send an email to user. And know whether email is sent or not.
My Understanding :
Laravel has in-built event Illuminate\Mail\Events\MessageSent to fire after email is sent and I have to write a listener to listen the event.
What I did :
To send email :
Mail::to($receiverAddress)->send(new SendNewUserPassword($content));
This is working fine. Able to send email to user successfully.
To listen messageSent event, I created this listener :
<?php
namespace App\Listeners;
use Illuminate\Mail\Events\MessageSent;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
class LogSentMessage
{
/**
* Create the event listener.
*
* #return void
*/
public function __construct()
{
//
}
/**
* Handle the event.
*
* #param MessageSent $event
* #return void
*/
public function handle(MessageSent $event)
{
return $event->message;
}
}
To Register Event :
protected $listen = [
'App\Events\Event' => [
'App\Listeners\EventListener',
],
'Illuminate\Mail\Events\MessageSent' => [
'App\Listeners\LogSentMessage',
],
];
In Controller :
event(new MessageSent())
Please guide me how to return the message handled in Listener from controller. If my above approach is wrong explain me how to achieve it. This I am using for an api, so if sending mail is success/fail I want to know.
You can pass data from the controller to the mailable, and then from the mailable to the listener
For example I have a model called SendOrder that I use to keep track the status of the email, so I pass this model from the controller to the listener
This is what you have to do from scratch
In your controller
Pass the model to your Mailable constructor
$send_order = SendOrder::create(['status' => 'received', 'email' => 'foo#example.com']);
Mail::to($receiverAddress)->send(new SendNewMail($send_order));
In the Mailable SendNewMail
Class Mailable has a method withSwiftMessage() which you can use to store variables/objects that you can access later from the listener.
We will make a constructor that pass the model to the build() method where we can execute the withSwiftMessage() to store it for later.
use App\SendOrder;
class SendNewMail extends Mailable
{
protected $send_order;
public function __construct( SendOrder $send_order )
{
$this->send_order = $send_order;
}
public function build()
{
$send_order = $this->send_order;
$this->withSwiftMessage(function ($message) use($send_order) {
$message->send_order = $send_order;
});
// Do more stuffs
}
}
Create the listener
Register the event and listener in the file app/Providers/EventServiceProvider.php
protected $listen =
'Illuminate\Mail\Events\MessageSent' => [
'App\Listeners\LogSentMessage',
],
];
Now execute the command:
php artisan event:generate
This will automatically generate the new listener app\Listeners\LogSentMessage with a template code that's connected to the built-in event Illuminate\Mail\Events\MessageSent
In your Listener LogSentMessage
You can access the model this way:
public function handle(MessageSent $event)
{
$send_order = $event->message->send_order;
$send_order->update(['status' => 'sent']);
}
In your EventServiceProvider add your event and listener
protected $listen = [
'Illuminate\Notifications\Events\NotificationSent' => [
'App\Listeners\YourListenerClass',
],
];
and in YourListnerClass
public function handle(NotificationSent $event)
{
//access your $event data here
//which includes notification details too
}
Doing php php artisan event:generate will generate App\Listeners\LogSentMessage for you.
Edit the file for example:
public function handle(MessageSent $event)
{
dd($event->message->getBody(), $event->message->toString());
}
I was stuck forever and ever trying to get my EventServiceProvider to register the Illuminate\Mail\MessageSent event to a local listener. Every time that I tried to do
php artisan event:generate
I would get
Your application doesn't have any events matching the given criteria.
I finally found that my EventService provider was using
use Illuminate\Support\ServiceProvider;
when I switched this to use
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
The event was registered properly! Maybe this will help someone.
I have the following Notification entity:
As you can see, there is a field called "objectId" where I want to store the related object id depending on the notification type. Then I add the notification to an email queue. When the queue gets processed I have a problem to fetch the object from the specific service class. For example:
Notification Type 1: UserService::getUser($objectId)
Notification Type 2: CompanyService::getCompany($objectId)
So how could I define that relations without having troubles to add more and more notification types. It feels bad to inject all the needed services and handle it through thousands of "if this than that" :)
If you injected the object instead of the id, you wouldn't need to call an additional service inside the notification to get the appropriate instance.
If the Notification doesn't need to know about what kind of object its using, just depend on an interface that both User and Company implement, and inject those objects directly into Notification.
E.g.:
interface EmailNotifiableEntity {
function getLabel()
function getEmailAddress()
}
class User implements EmailNotifiableEntity {
public function getLabel() {
return $this->getName() . " " . $this->getFullName();
}
public function getEmailAddress() {
return this->getEmailAddress();
}
}
class Company implements EmailNotifiableEntity {
public function getLabel() {
return $this->getCompanyName();
}
public function getEmailAddress() {
return this->getNotificationsEmail();
}
}
class Notification {
public function __construct(EmailNotifiableEntity $entity) {
$this->entity = $entity;
}
public function send() {
$address = $entity->getEmailAddress();
$label = $entity->getLabel();
// do your thing to send your notification
}
(Implementation is a bit bare-bones, so take what you need and build upon it). This way, when you instantiate your Notification you inject the depended upon entity without knowing its specific kind.
I have a back office developed in laravel that allow to insert data that then android and ios app get.
Now i need to implement onsignal notification for example for when a new product was inserted in back office the app user receives a notification.
I setup my onesignal notification and install in my laravel project this package: https://github.com/laravel-notification-channels/onesignal.
I set OneSignal App ID and REST API Key too.
After that I create a new controller and put the example code that is in package link.
Controller:
use Illuminate\Http\Request;
use NotificationChannels\OneSignal\OneSignalChannel;
use NotificationChannels\OneSignal\OneSignalMessage;
use NotificationChannels\OneSignal\OneSignalWebButton;
use Illuminate\Notifications\Notification;
class NotificationsController extends Controller
{
public function via($notifiable)
{
return [OneSignalChannel::class];
}
public function toOneSignal($notifiable)
{
return OneSignalMessage::create()
->subject("Your {$notifiable->service} account was approved!")
->body("Click here to see details.")
->url('http://onesignal.com')
->webButton(
OneSignalWebButton::create('link-1')
->text('Click here')
->icon('https://upload.wikimedia.org/wikipedia/commons/4/4f/Laravel_logo.png')
->url('http://laravel.com')
);
}
}
But now I don't konw how to use it.
For example, how can I send a notification when a new product was added?
I need to setup routes?
Let me know if don't explain well my problem.
Thank you
Hi you have to create a Custom Channel OneSignal Notification, so you don't have to do that on your Controller.
1.- First, you need to create a OneSignal Notification for each "event" that you want to notify
For instance when a Product was added
php artisan make:notification ProductAdded
This will generate a Notification File inside of App\Notifications\ProductAdded
2.- On the new File ProductAdded you will need to add that logic of the notification to OneSignal
<?php
// App\Notifications\ProductAdded.php
class OneSignal extends Notification
{
use Queueable;
private $data; //this is the "model" data that will be passed through the notify method
/**
* Create a new notification instance.
*
* #return void
*/
public function __construct($data)
{
$this->data = $data;
}
/**
* Get the notification's delivery channels.
*
* #param mixed $notifiable
* #return array
*/
public function via($notifiable)
{
return [OneSignalChannel::class];
}
public function toOneSignal($notifiable)
{
//now you can build your message with the $this->data information
return OneSignalMessage::create()
->subject("Your {$notifiable->service} account was approved!")
->body("Click here to see details.")
->url('http://onesignal.com')
->webButton(
OneSignalWebButton::create('link-1')
->text('Click here')
->icon('https://upload.wikimedia.org/wikipedia/commons/4/4f/Laravel_logo.png')
->url('http://laravel.com')
);
}
}
3.- Use the Notifiable trait in your Model
<?php
class YourModel extends Model
{
use Notifiable;
public function sendNotification()
{
$this->notify(new ProductAdded($this)); //Pass the model data to the OneSignal Notificator
}
public function routeNotificationForOneSignal()
{
/*
* you have to return the one signal player id tat will
* receive the message of if you want you can return
* an array of players id
*/
return $this->data->user_one_signal_id;
}
}
Additionally you can use the Notification Facade wherever you want
$users it's a collection of player id's
$data it's the data to build the notification
Notification::send($users, new ProductAdded($dataToNotify));
I hope this helps :)
Also you can read more of this stuff on Laravel Docs
https://laravel.com/docs/5.4/notifications
PD.
If you feel overwhelmed with the notification system, you can also use this package https://github.com/berkayk/laravel-onesignal it's a simple wrapper that you can use with ease and that has a lot of useful handy methods.
I am using the FOS_user bundle and when I create a user I want to link it to another Entity which is Company. I have tried to create a Listener using the following code:
public static function getSubscribedEvents()
{
return array(
FOSUserEvents::REGISTRATION_SUCCESS => 'onRegistrationSuccess',
);
}
public function onRegistrationSuccess(FormEvent $event) {
}
This is working good and I have access to $event in the function. Now, I want to access the name of the company that the user added in the form. Also, I want to create a company and bind it to the new user, so something like this:
$company = new Entity\Company();
$company->setTitle($theInputFromTheForm);
$user->addCompany($company);
I don't know how to access the data of the form and save the company in the user. Is using a listener the proper way to do it?
The event FOSUserEvents::REGISTRATION_SUCCESS receive a FormEvent object. You can get the registration form in your listener using $event->getForm()
So in you case;
public function onRegistrationSuccess(FormEvent $event) {
$registrationForm = $event->getForm();
$registrationFormData = registrationForm->getData();
}
I ended up creating a form type CompanyType that adds the title of my company. In my RegistrationFormType of my UserBundle I added a collection to this form type. I created a listener on FOSUserEvents::REGISTRATION_INITIALIZE to add an empty company.
public function onRegistrationInitialize(UserEvent $event) {
$user = $event->getUser();
$user->addCompany($company);
}