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.
Related
I was wondering how to write a service that is executed only on kernel.terminate.
For instance when I call:
$message = (new \Swift_Message('A new book has been added'))
->setFrom('system#example.com')
->setTo('contact#les-tilleuls.coop')
->setBody(sprintf('The book #%d has been added.', $book->getId()));
$this->mailer->send($message);
I know that the kernel will send a response to the client and only AFTER that will send the email.
I have a lot of service that could be called on kernel.terminate but I don't know how to write them in order to be called only on that event.
For now, I am writing write code in a subscriber:
public static function getSubscribedEvents()
{
return [
KernelEvents::TERMINATE => [['doHeavyStuff', EventPriorities::POST_RESPOND]]
];
}
But working this way mean that I have to deal only with the Request and the Response and I do not want to depend on the Response.
if($method===Request::METHOD_PUT && isset($response['#type']) && $response['#type']==='Partner'){
$this->notificationManager->sendNotificationAboutCart($response['partner']['id'],
$response['partner']['name']);
}
I do not know if it is clear, but it would be great to call the notificationManager wherever I want in my code and that manager works only on kernel.terminate.
A simple, naive implementation.
Your service's send() method doesn't actually send anything, but simply adds another message to be sent when the time is ripe.
class ShinyService {
private $messages = [];
public function send($message) {
$this->messages[] = $message;
}
public function processMessages()
{
foreach ($this->messages as $message) {
// do the actual work, e.g. sending the message
}
}
}
Then a subscriber that depends on this service, so it gets injected into the subscriber instance:
class ShinySubscriber implements EventSubscriberInterface
{
private $service;
public function __construct(ShinyService $service) {
$this->service = $service;
}
public static function getSubscribedEvents()
{
return [
KernelEvents::TERMINATE => [
['processMessages', 10]
],
];
}
public function processMessages(TerminateEvent $event)
{
$this->service->processMessages();
}
}
This way you can inject ShinyService anywhere, call ShinyService::send() at any point, and messages will only be sent at KernelEvents::TERMINATE.
Remember that this event only runs after the response is sent if you are using PHP-FPM. From the docs:
Internally, the HttpKernel makes use of the fastcgi_finish_request PHP function. This means that at the moment, only the PHP FPM server API is able to send a response to the client while the server's PHP process still performs some tasks. With all other server APIs, listeners to kernel.terminate are still executed, but the response is not sent to the client until they are all completed.
I am working in laravel v4.2 project and I have different queus tasks that I want to perform. But now I want to perform this task by using two or more drivers fot this. For example I have an queue to send registration email and now I want to send email using redis server.
Second queue I have is to send push notifications to users for this I want to use database drive. So is it possible that use two or more queue drivers on one project.
Please educate me.
Thank you
Just write another QueueManager extend it from the Base QueueManager
Extend Your Base Queue Driver
use Illuminate\Queue\Connectors\RedisConnector;
use Illuminate\Queue\QueueManager;
class RedisQueueManager extends QueueManager
{
public function __construct(\Illuminate\Foundation\Application $app)
{
parent::__construct($app);
$this->registerRedisConnector();
}
/**
* Get the name of the default queue connection.
*
* #return string
*/
public function getDefaultDriver()
{
return 'redis';
}
protected function registerRedisConnector()
{
$app = $this->app;
$this->addConnector('redis', function () use ($app) {
return new RedisConnector($app['redis']);
});
}
}
Now create a service provider to access it through app
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use App\Extensions\RedisQueueManager;
class RedisQueueServiceProvider extends ServiceProvider
{
/**
* Register the service provider.
*
* #return void
*/
public function register()
{
$this->app->bindShared('redis-queue', function ($app) {
return new RedisQueueManager($app);
});
}
}
Finally Register Your Service provider in config/app.php providers array.
And use it.
Route::get('/', function () {
$default = app('queue');
$redis = app('redis-queue');
});
I have a Lumen controller class that fires an event when a user is created. I am using an event dispatcher. The event gets fired as it should, but the listener does not handle the event.
I am sure I have followed every step of the Lumen documentation.
// UserController.php
class UserController extends ApiController
{
protected $event = null;
public function __construct(Dispatcher $event)
{
$this->event = $event;
}
/**
* Store a newly created resource in storage.
*
* #param Request $request
* #return Response
*/
public function store(Request $request)
{
$this->acceptContentType($request, 'json');
$this->input = $request->json()->all();
$this->withEncryptedParameters();
$this->validateParameterNames(array_keys($this->validationRules));
$this->validateParameterContent($this->validationRules);
$roles = $this->getRelationInput('roles');
$user = User::create($this->input);
$this->addRelation($user, $roles, Role::class, 'roles');
$this->event->fire(new UserCreated($user));
return $this->respondCreated($user->id);
}
}
So I basically want to store a user into the database and fire an event when that happens.
// UserCreated.php
class UserCreated extends Event
{
public $user;
public function __construct(User $user)
{
$this->user = $user;
}
}
The event is fired correctly, so if I put an "echo" or a "var_dump" into the event's constructor, I can see that it works. If I so the same for the listener, it does not react.
// UserCreatedEmail.php
class UserCreatedEmail extends Listener
{
public function handle(UserCreated $event)
{
echo 'Hello?';
}
}
I have registered it in the EventServiceProvider.
// EventServiceProvider.php
class EventServiceProvider extends ServiceProvider
{
/**
* The event listener mappings for the application.
*
* #var array
*/
protected $listen = [
UserCreated::class => [
UserCreatedEmail::class
]
];
}
And uncommented it in the bootstrap area.
// bootstrap/app.php
$app->register(WISSIT\UserService\Providers\EventServiceProvider::class);
I have absolutely no idea why is doesn't work. I could use "$event->listen" but then it would also listen when I use testing. According to the Lumen documentation, it should also work without that.
And yes, the namespaces are set correctly. And no, I do not want to use Facades.
In bootstrap/app.php under 'Register Service Providers' comment out the registration of the service provider.
// $app->register(App\Providers\EventServiceProvider::class);
into
$app->register(App\Providers\EventServiceProvider::class);
I think I already got this problem, so this is how I resolve this:
In your EventServiceProvider change the event class and listener class to a real path, dont use ::class in EventServiceProvider. I.e:
// EventServiceProvider.php
class EventServiceProvider extends ServiceProvider
{
/**
* The event listener mappings for the application.
*
* #var array
*/
protected $listen = [
'WISSIT\UserService\Events\UserCreated' => [
'WISSIT\UserService\Listeners\UserCreatedEmail'
]
];
}
Okay, so it appears like the event is only listened when using \Illuminate\Contracts\Event\Dispatcher in the controller. I used \Illuminate\Events\Dispatcher. I don't know why that is the case, but it worked.
I had a similar issue when working with Lumen and event listeners. The fired event never reached my custom listener and I was struggling for a day to figure out where the problem is.
At the end I figured out, that I had a wrong signature of handle method on my listener. It was my mistake, but Dispatcher did not notify me about that issue. When I changed the method to accept given arguments, it just started working.
I think the problem lies in the Illuminate\Events\Dispatcher in the fire method. Function call_user_func_array returns false if the signature of the method is wrong, but dispatcher just breaks out of the loop on error. And does not notify user about an issue.
Normally Laravel expects that it queued up any messages that it later consumes. It creates a payload with a job attribute that later indicates how to handle the queue message. When you do queue up jobs with Laravel, and later process them with Laravel, it works great!
However, I have some non-Laravel apps that are posting json messages to a queue. I need Laravel to pick up these messages and handle them.
I can write a command bus job to handle the messages, but I haven't been able to figure out how to tell queue:work to send the messages on to my specific handler.
It seems Laravel has a hard assumption that any queue messages it is asked to handle will be properly formatted, serialized, and structured the way it expects them to be.
How can I have Laravel pick up these raw json payloads, ignore the structure (there's nothing there for it to understand), and simply hand the payload off to my handler?
For example, if I have a queue message similar to:
{
"foo" : "bar"
}
So again, there's nothing for Laravel to inspect or understand here.
But I have a job handler that knows how to handle this:
namespace App\Jobs;
class MyQueueHandler {
public function handle($payload) {
Log::info($payload['foo']); // yay!
}
}
Now how to get queue:work and queue:listen to simply hand off any payloads to this App\Jobs\MyQueueHandler handler, where I can do the rest on my own?
If you're using Laravel 5.6+, check out this package.
You didn't specify which version of Laravel, so I'm guessing 5.1 (huge difference in how this is handled in L4.2 and L5.x).
If you've already set up your App\Jobs\MyQueueHandler, and want to queue up a job from a controller, using any data you wish, you can just do this:
use App\Jobs\MyQueueHandler;
class MyController
{
public function myFunction()
{
$this->dispatch(new MyQueueHandler(['foo' => 'bar']));
}
}
In your MyQueueHandler-class, the payload actually enters your constructor. The handle-method is still fired when your queue is processed though. You can however use parameters on your handle-method if you rely on dependancy injection (read more here, just above "When Things Go Wrong") So something like this should do it:
namespace App\Jobs;
class MyQueueHandler
{
protected $payload;
public function __construct($payload)
{
$this->payload = $payload;
}
public function handle() {
Log::info($this->payload['foo']); // yay!
}
}
Note: If you want to dispatch the job from outside a main controller (that inherits from the standard App\Http\Controller-class, use the DispatchesJobs trait;
MyClass
{
use DispatchesJobs;
public function myFunction()
{
$this->dispatch(new MyQueueHandler(['foo' => 'bar']));
}
}
(Code tested with Laravel 5.1.19 and the beanstalkd queue-adapter).
What you ask for is not possible as Laravel tries to execute the Gearman payload (see \Illuminate\Bus\Dispatcher).
I was in the same situation and just created a wrapper command around the Laravel job class. This is not the nicest solution as it will re-queue events, coming on the json queue, but you don't have to touch existing job classes. Maybe someone with more experience knows how to dispatch a job without actually sending it over the wire again.
Lets assume we have one regular Laravel worker class called GenerateIdentApplicationPdfJob.
class GenerateIdentApplicationPdfJob extends Job implements SelfHandling, ShouldQueue
{
use InteractsWithQueue, SerializesModels;
/** #var User */
protected $user;
protected $requestId;
/**
* Create a new job instance.
*
* QUEUE_NAME = 'ident-pdf';
*
* #param User $user
* #param $requestId
*/
public function __construct(User $user, $requestId)
{
$this->user = $user;
$this->requestId = $requestId;
}
/**
* Execute the job.
*
* #return void
*/
public function handle(Client $client)
{
// ...
}
}
To be able to handle this class, we need to provide the constructor arguments our own. Those are the required data from our json queue.
Below is a Laravel command class GearmanPdfWorker, which does all the boilerplate of Gearman connection and json_decode to be able to handle the original job class.
class GearmanPdfWorker extends Command {
/**
* The console command name.
*
* #var string
*/
protected $name = 'pdf:worker';
/**
* The console command description.
*
* #var string
*/
protected $description = 'listen to the queue for pdf generation jobs';
/**
* #var \GearmanClient
*/
private $client;
/**
* #var \GearmanWorker
*/
private $worker;
public function __construct(\GearmanClient $client, \GearmanWorker $worker) {
parent::__construct();
$this->client = $client;
$this->worker = $worker;
}
/**
* Wrapper listener for gearman jobs with plain json payload
*
* #return mixed
*/
public function handle()
{
$gearmanHost = env('CB_GEARMAN_HOST');
$gearmanPort = env('CB_GEARMAN_PORT');
if (!$this->worker->addServer($gearmanHost, $gearmanPort)) {
$this->error('Error adding gearman server: ' . $gearmanHost . ':' . $gearmanPort);
return 1;
} else {
$this->info("added server $gearmanHost:$gearmanPort");
}
// use a different queue name than the original laravel command, since the payload is incompatible
$queueName = 'JSON.' . GenerateIdentApplicationPdfJob::QUEUE_NAME;
$this->info('using queue: ' . $queueName);
if (!$this->worker->addFunction($queueName,
function(\GearmanJob $job, $args) {
$queueName = $args[0];
$decoded = json_decode($job->workload());
$this->info("[$queueName] payload: " . print_r($decoded, 1));
$job = new GenerateIdentApplicationPdfJob(User::whereUsrid($decoded->usrid)->first(), $decoded->rid);
$job->onQueue(GenerateIdentApplicationPdfJob::QUEUE_NAME);
$this->info("[$queueName] dispatch: " . print_r(dispatch($job)));
},
[$queueName])) {
$msg = "Error registering gearman handler to: $queueName";
$this->error($msg);
return 1;
}
while (1) {
$this->info("Waiting for job on `$queueName` ...");
$ret = $this->worker->work();
if ($this->worker->returnCode() != GEARMAN_SUCCESS) {
$this->error("something went wrong on `$queueName`: $ret");
break;
}
$this->info("... done `$queueName`");
}
}
}
The class GearmanPdfWorker needs to be registered in your \Bundle\Console\Kernel like this:
class Kernel extends ConsoleKernel
{
protected $commands = [
// ...
\Bundle\Console\Commands\GearmanPdfWorker::class
];
// ...
Having all that in place, you can call php artisan pdf:worker to run the worker and put one job into Gearman via commandline: gearman -v -f JSON.ident-pdf '{"usrid":9955,"rid":"ABC4711"}'
You can see the successful operation then
added server localhost:4730
using queue: JSON.ident-pdf
Waiting for job on `JSON.ident-pdf` ...
[JSON.ident-pdf] payload: stdClass Object
(
[usrid] => 9955
[rid] => ABC4711
)
0[JSON.ident-pdf] dispatch: 1
... done `JSON.ident-pdf`
Waiting for job on `JSON.ident-pdf` ...
Is there a way to add default headers to all emails in Laravel 5.1? I want all emails to be sent with the following header:
x-mailgun-native-send: true
Laravel uses SwiftMailer for mail sending.
When you use Mail facade to send an email, you call send() method and define a callback:
\Mail::send('emails.reminder', ['user' => $user], function ($m) use ($user) {
$m->to($user->email, $user->name)->subject('Your Reminder!');
});
Callback receives $m variable that is an \Illuminate\Mail\Message object, that has getSwiftMessage() method that returns \Swift_Message object which you can use to set headers:
$swiftMessage = $m->getSwiftMessage();
$headers = $swiftMessage->getHeaders();
$headers->addTextHeader('x-mailgun-native-send', 'true');
I know this is an old post, but I'm passing by the same problem right now and I think this could be useful to others.
If you're using a structure exactly as shown in Laravel (6.x and 7.x), like this:
/**
* Build the message.
*
* #return $this
*/
public function build()
{
return $this->from('example#example.com')
->view('emails.orders.shipped');
}
you could add headers to the E-mail in the format below:
public function build()
{
return $this->from('example#example.com')
->view('emails.orders.shipped')
->withSwiftMessage(function ($message) {
$message->getHeaders()
->addTextHeader('Custom-Header', 'HeaderValue');
});
}
I hope this could be useful.
link to the current documentation:https://laravel.com/docs/7.x/mail#customizing-the-swiftmailer-message
Slight modification to #maxim-lanin's answer. You can use it like this, fluently.
\Mail::send('email.view', ['user' => $user], function ($message) use ($user) {
$message->to($user->email, $user->name)
->subject('your message')
->getSwiftMessage()
->getHeaders()
->addTextHeader('x-mailgun-native-send', 'true');
});
For those who need to add this configuration to all emails (like me), there is a practical way using listeners.
Create a Listener:
php artisan make:listener MessageSendingListener
Add the following content to the MessageSendingListener:
<?php
namespace App\Listeners;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Events\MessageSending;
use Illuminate\Queue\InteractsWithQueue;
class MessageSendingListener
{
/**
* Handle the event.
*
* #param \Illuminate\Mail\Events\MessageSending $event
* #return void
*/
public function handle(MessageSending $event)
{
$event->message
->getHeaders()
->addTextHeader('x-mailgun-native-send', 'true');
}
}
Add the configuration to the application's event mapping array in EventServiceProvider:
<?php
namespace App\Providers;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
class EventServiceProvider extends ServiceProvider
{
/**
* The event listener mappings for the application.
*
* #var array
*/
protected $listen = [
// ...
\Illuminate\Mail\Events\MessageSending::class => [
\App\Listeners\MessageSendingListener::class,
],
];
}
Obs.: Tested with Laravel 7.