Laravel failed job doesn't send notification - laravel

When a job fails my notification doesn't arrive, and I'm not sure why. In my Job I have:
use Notification;
use App\Notifications\MonitoringMessage;
public function handle()
{
asdf;
}
public function failed(Exception $exception)
{
$message = ':warning: A job failed.';
Notification::route('slack', config('services.slack.webhook'))->notify(new MonitoringMessage($message));
}
The notification is not queued using use Queueable; etc because I've read that that might cause the issue because the job itself is also queued.
The code above will cause the job to fail of course, and I can see it in the failed_jobs table, but the notification is not send. If I put the notification code somewhere else (eg in a controller) and execute it the notification is sent so that code is correct.

Any job is handled through the handle() method. Make sure this method doesn't fail because of a syntax mistake. If your code fails on a syntax error, the rest of you code can't possibly be executed as it will never be processed. Force a conceptual mistake, for example a division by zero forced error:
use Notification;
use App\Notifications\MonitoringMessage;
public function handle()
{
$result = 1/0;
}
public function failed()
{
$message = ':warning: A job failed.';
Notification::route('slack', config('services.slack.webhook'))->notify(new MonitoringMessage($message));
}
As you have stated yourself, the Exception class implementation is not needed because it's not used.

Related

Database notifications don't show up after testing notify

I have a unit test with the following:
use \Illuminate\Notifications\DatabaseNotification;
public function testMailSentAndLogged()
{
Notification::fake();
$user = factory(User::class)->create();
$emailAddress = $user->emailAddress;
$emailAddress->notify(new UserCreated);
Notification::assertSentTo(
$emailAddress,
UserCreated::class
);
error_log('DatabaseNotification '.print_r(DatabaseNotification::get()->toArray(), 1));
$this->assertEquals(1, $emailAddress->notifications->count());
}
My Notification has this for the via():
public final function via($notifiable)
{
// complex logic...
error_log('mail, database');
return ['mail', 'database'];
}
The code fails on the $this->assertEquals code. the error_log produces the following:
[03-Jan-2018 01:23:01 UTC] mail, database
[03-Jan-2018 01:23:01 UTC] DatabaseNotification Array
(
)
WHY don't the $emailAddress->notifications pull up anything? Why doesn't DatabaseNotification::get() pull anything?;
In your test, you are calling the method
Notification::fake();
As stated in Laravel's documentation on Mocking,
You may use the Notification facade's fake method to prevent
notifications from being sent.
Actually, this bit of code is the assertion that the Notification would have been sent, under normal circumstances (ie in prod) :
Notification::assertSentTo();
If you remove the call to Notification::fake(), your notification should appear in your testing database.
So you kinda have two solutions. The first one is to remove the call to fake(), thus really sending the notification, which will appear in the database. the second is not to test if the notification was written successfully in the database : it's Laravel's responsibility, not your application's. I recommand the second solution :)

Laravel 5.3 Passing AuthorizationException a message when a Policy fails

I'm trying to find a clean way to override the AuthorizationException to take a dynamic string that can be passed back when a Policy fails.
Things I know I can do are:
Wrap the Policy in the Controller with a try-catch, then rethrow a custom exception that takes a specific string, which seems a bit verbose
abort(403, '...') in the Policy prior to returning, which seems a bit hacky since policies are already doing the work
and then in /Exceptions/Handler::render I can send back the response as JSON
Is there a nicer way to do this to get a message in the response of a policy failure? Or is 1 or 2 my best choices.
I noticed if you throw AuthorizationException($message) in a policy using Laravel's exception it jumps you out of the policy, but continues execution in the controller, and doesn't progress to Handler::render. Which I'm assuming this is them handling the exception somehow, but I couldn't find where they were doing it... so if anyone finds where this is happening I'd still like to know.
If you create your own AuthorizationException and throw it, it will stop execution as expected, and drop into Handler::render so I ended up adding this method to my policy:
use App\Exceptions\AuthorizationException;
// ... removed for brevity
private function throwExceptionIfNotPermitted(bool $hasPermission = false, bool $allowExceptions = false, $exceptionMessage = null): bool
{
// Only throw when a message is provided, or use the default
// behaviour provided by policies
if (!$hasPermission && $allowExceptions && !is_null($exceptionMessage)) {
throw new \App\Exceptions\AuthorizationException($exceptionMessage);
}
return $hasPermission;
}
New exception for throwing in policies only in \App\Exceptions:
namespace App\Exceptions;
use Exception;
/**
* The AuthorizationException class is used by policies where authorization has
* failed, and a message is required to indicate the type of failure.
* ---
* NOTE: For consistency and clarity with the framework the exception was named
* for the similarly named exception provided by Laravel that does not stop
* execution when thrown in a policy due to internal handling of the
* exception.
*/
class AuthorizationException extends Exception
{
private $statusCode = 403;
public function __construct($message = null, \Exception $previous = null, $code = 0)
{
parent::__construct($message, $code, $previous);
}
public function getStatusCode()
{
return $this->statusCode;
}
}
Handle the exception and provide the message in a JSON response in Handler::render():
public function render($request, Exception $exception)
{
if ($exception instanceof AuthorizationException && $request->expectsJson()) {
return response()->json([
'message' => $exception->getMessage()
], $exception->getStatusCode());
}
return parent::render($request, $exception);
}
and I also removed it from being logged in Handler::report.
What I found was not "passing" a custom message to authorize, just defining a custom message in the policy it selfs, so, for example, if you have the method "canUseIt", in your UserPolicy, like the following:
public function canUseIt(User $user, MachineGun $machineGun)
{
if ($user->isChuckNorris()) {
return true;
}
return false;
}
You can change it and do something like this:
public function canUseIt(User $user, MachineGun $machineGun)
{
if ($user->isChuckNorris()) {
return true;
}
$this->deny('Sorry man, you are not Chuck Norris');
}
It uses the deny() method from the HandlesAuthorization trait.
Then when you use it like $this->authorize('canUseIt', $user) and it fails, it will return a 403 HTTP error code with the message "Sorry man, you are not Chuck Norris".
Laravel does have an option to pass arguments to customize the errors in the authorize() method of a Controller Class accessed through the Gate Class's implementation of the GateContract made available by the Gate Facade.
However, it seems that they forgot to pass those arguments to the allow()/deny() methods responsible for returning error messages, implemented in the HandlesAuthorization Trait.
You need to pass those arguments by following these steps:
Modify the authorize method in the vendor/laravel/framework/src/Illuminate/Auth/Access/Gate.php file
public function authorize($ability, $arguments = []) {
$result = $this->raw($ability, $arguments);
if ($result instanceof Response) {
return $result;
}
return $result ? $this->allow() : $this->deny($arguments);
}
Call authorize from the controller with an extra argument, ie: your custom $message -
$message = "You can not delete this comment!";
$response = $this->authorize('delete', $message);
I have made a pull request to fix this, hopefully someone will merge it soon.
I think the best way to think about Policies is they are simply a way to split controller logic, and move all authorization related logic to a separate file. Thus abort(403, 'message') is the right way to do this, in most cases.
The only downside is you may want some policies to be 'pure' logic for use in business logic only, and thus not to have any response control. They could be kept separate, and a commenting system could be used distinguish them.

Queueable entity App\Setting not found for ID error in Laravel

I am trying to send emails in laravel 5.1 by using queues. When running queue listen command on terminal,
php artisan queue:listen
Displays below error on terminal,
[Illuminate\Contracts\Queue\EntityNotFoundException]
Queueable entity [App\Setting] not found for ID [].
Values of jobs table is not process. Any idea ?
How can I process my queue ?
I know this question is a few months old, but I'd like to add an observation of mine while encountering this very same error message. It is due to the EventListener (interface of ShouldQueue in this example for asynchronous) not being able to resolve a dependant variable correctly (out of scope or not included in scope of Event object passed through the handle(Event $event) method of EventListener).
For me, this error was fired when I put my code within the __construct block within the EventListener:
public function __construct(Event $event)
{
$localProperty = $event->property
Mail::queue(etc...);
}
public function handle()
{
// Yeah I left this blank... whoops
}
Instead, the handle() method of the EventListener takes an Event interface and when called processes the job in the queue:
In the Event:
public function __construct(Object $ticket, AnotherObject $user)
{
$this->ticket = $ticket;
$this->user = $user;
}
And in Event Listener
class SomeEventListener implements ShouldQueue
{
use InteractsWithQueue;
use SerializesModels;
public function __construct()
{
// Leave me blank!
}
public function handle(Event $event)
{
$someArray = [
'ticket' = $event->ticket,
'user' = $event->user,
];
Mail::queue('some.view', $someArray, function($email) use ($someArray) {
// Do your code here
});
}
}
Although a tad late, I hope this helps someone. Queues are similar to Events (with the exception of Jobs being the main driving force behind Queues), so most of this should be relevant.
Turned out that it was because a model was added to the queue, that has since been deleted.

Checking passed object existence in Laravel delayed queue job

I believe if I pass an Eloquent object to a delayed Laravel job a "findOrFail" method is called to "restore" the object and pass it to the controller of my Job class.
The problem is that the DB record representing the object might be gone by the time the job is actually processed.
So "findOrFail" aborts before even calling the "handle" methods.
Everything seems fine. The problem is that the job now "gets transferred" to the failed jobs list. I know I can remove it from there manually, but that doesn't sound right.
Is there a way to "know" in my job class directly that the passed object "failed to load" or "does not exist" or anything similar?
Basically I would like to be able to do something if "ModelNotFoundException" is thrown while "rebuilding" my passed objects.
Thank you
SOLUTION:
Based on Yauheni Prakopchyk's answer I wrote my own trait and used it instead of SerializesModels where I need my altered behaviour.
Here's my new trait:
<?php
namespace App\Jobs;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Database\ModelIdentifier;
use Illuminate\Database\Eloquent\ModelNotFoundException;
trait SerializesNullableModels
{
use SerializesModels {
SerializesModels::getRestoredPropertyValue as parentGetRestoredPropertyValue;
}
protected function getRestoredPropertyValue($value)
{
try
{
return $this->parentGetRestoredPropertyValue($value);
}
catch (ModelNotFoundException $e)
{
return null;
}
}
}
And that's it - now if the model loading fails I still get a null and can decide what to do with it in my handle method.
And if this is only needed in a job class or two I can keep using original trait everywhere else.
If i'm not mistaken, you have to override getRestoredPropertyValue in your job class.
protected function getRestoredPropertyValue($value)
{
try{
return $value instanceof ModelIdentifier
? (new $value->class)->findOrFail($value->id) : $value;
}catch(ModelNotFoundException $e) {
// Your handling code;
}
exit;
}

Passing Exception Message to Queue::failing() in Laravel 5.1?

Using Laravel 5.1's Queues, I'm throwing an exception when a job fails.
throw new \Exception('No luck');
As Laravel recommends when dealing with failed jobs, I'm "catching" the exception in the AppServiceProvider, and using that to send our team an email.
public function boot()
{
Queue::failing(function ($connection, $job, $data) {
$info['data'] = $data;
\Mail::send('emails.jobs.failed', $info, function($message) {
$message->to('test#email.com')->subject('Job failed');
});
});
}
Within the email, I would like to place the exception's message (in this case "No luck."). But I can't figure out how to pass that along to Queue::failing().
Any ideas?
After calling the failing callback, Laravel rethrows the exception.
It seems if you really need the error message, you'll have to catch the exception yourself.

Resources