Laravel 8: render exception as html for email - laravel

I upgraded my Laravel app to the latest version Laravel 8. My problem now is that I cannot figure out how to render an exception thrown by the app to html in order to send that html as an email.
My current code (that worked for Laravel 5) to render and send an exception within the App\Exceptions\Handler class:
public function sendEmail(Exception $exception)
{
try {
$e = FlattenException::create($exception);
$handler = new SymfonyExceptionHandler();
$html = $handler->getHtml($e);
$routeName = URL::full();
Mail::send(new ExceptionEmail($html, $routeName));
} catch (Exception $ex) {
if (env("APP_DEBUG") == true) {
dd($ex);
}
}
}
The problem is that class \Symfony\Component\Debug\Exception\FlattenException does not exist anymore in my upgraded app.
What is the appropriate way to render exceptions as html now in Laravel 8?
Thank you very much in advance.

Alright i managed to receive email, here is the code
Composer requirement
"require": {
"php": "^7.3|^8.0",
.....
"jeremykenedy/laravel-exception-notifier": "^2.2",
"laravel/framework": "^8.40",
.....
},
app/Exceptions/Handler.php
<?php
namespace App\Exceptions;
use App\Mail\ExceptionOccurred;
use Exception;
use Throwable;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;
use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer;
use Symfony\Component\ErrorHandler\Exception\FlattenException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Symfony\Component\HttpFoundation\Response;
class Handler extends ExceptionHandler
{
/**
* A list of the exception types that are not reported.
*
* #var array
*/
protected $dontReport = [
//
];
/**
* A list of the inputs that are never flashed for validation exceptions.
*
* #var array
*/
protected $dontFlash = [
'current_password',
'password',
'password_confirmation',
];
/**
* Register the exception handling callbacks for the application.
*
* #return void
*/
public function register()
{
$this->reportable(function (Throwable $e) {
//
});
}
/**
* Report or log an exception.
*
* #param Exception $e
* #return void
* #throws Throwable
*/
public function report(Throwable $e)
{
if ($this->shouldReport($e)) {
$this->sendEmail($e); // sends an email
}
parent::report($e);
}
/**
* Render an exception into an HTTP response.
*
* #param Request $request
* #param Throwable $e
* #return Response
*
* #throws Throwable
*/
public function render($request, Throwable $e): Response
{
return parent::render($request, $e);
}
public function sendEmail(Throwable $exception)
{
try {
$e = FlattenException::createFromThrowable($exception);
$handler = new HtmlErrorRenderer(true);
$css = $handler->getStylesheet();
$content = $handler->getBody($e);
Mail::to('your_email_address_here')->send(new ExceptionOccurred($content,$css));
} catch (Throwable $exception) {
Log::error($exception);
}
}
}
app/Mail/ExceptionOccurred.php
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
class ExceptionOccurred extends Mailable
{
use Queueable, SerializesModels;
private $content;
private $css;
/**
* Create a new message instance.
*
* #return void
*/
public function __construct($content,$css)
{
$this->content = $content;
$this->css = $css;
}
/**
* Build the message.
*
* #return $this
*/
public function build()
{
$emailsTo = str_getcsv(config('exceptions.emailExceptionsTo'), ',');
$ccEmails = str_getcsv(config('exceptions.emailExceptionCCto'), ',');
$bccEmails = str_getcsv(config('exceptions.emailExceptionBCCto'), ',');
$fromSender = config('exceptions.emailExceptionFrom');
$subject = config('exceptions.emailExceptionSubject');
if ($emailsTo[0] === null) {
$emailsTo = config('exceptions.emailExceptionsToDefault');
}
if ($ccEmails[0] === null) {
$ccEmails = config('exceptions.emailExceptionCCtoDefault');
}
if ($bccEmails[0] === null) {
$bccEmails = config('exceptions.emailExceptionBCCtoDefault');
}
if (! $fromSender) {
$fromSender = config('exceptions.emailExceptionFromDefault');
}
if (! $subject) {
$subject = config('exceptions.emailExceptionSubjectDefault');
}
return $this->from($fromSender)
->to($emailsTo)
->cc($ccEmails)
->bcc($bccEmails)
->subject($subject)
->view(config('exceptions.emailExceptionView'))
->with('content', $this->content)
->with('css', $this->css);
}
}
resources/views/emails/exception.blade.php
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<style>{!! $css ?? '' !!}</style>
</head>
<body>
{!! $content ?? '' !!}
</body>
</html>
Please let me know if that works.

Related

How to get file extension from SwiftAttachment object in Laravel Email Transport

This is Laravel 8. I'm extending Illuminate\Mail\Transport\Transport class to create a custom mail transport in order to utilize the company's custom mail API with Illuminate\Mail\Mailable. I got most of it working, including file attachments, however the Swift_Mime_SimpleMimeEntity and the classes deriving from it contain getBody(), getFilename(), getSize(), and getContentType() but no methods to extract file extension.
<?php
namespace App\CustomMailDriver;
use GuzzleHttp\ClientInterface;
use Illuminate\Mail\Transport\Transport;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Swift_Mime_SimpleMessage;
class CustomTransport extends Transport
{
/**
* Guzzle client instance.
*
* #var \GuzzleHttp\ClientInterface
*/
protected $client;
/**
* API key.
*
* #var string
*/
protected $key;
/**
* The API URL to which to POST emails.
*
* #var string
*/
protected $url;
/**
* Create a new Custom transport instance.
*
* #param \GuzzleHttp\ClientInterface $client
* #param string|null $url
* #param string $key
* #return void
*/
public function __construct(ClientInterface $client, string $url, string $key)
{
$this->key = $key;
$this->client = $client;
$this->url = $url;
}
/**
* {#inheritdoc}
*/
public function send(Swift_Mime_SimpleMessage $message, &$failedRecipients = null)
{
$this->beforeSendPerformed($message);
$payload = $this->getPayload($message);
try {
// ignore ssl (esp when working in DEV/QA)
$response = Http::withoutVerifying()->withHeaders([
'X-Authorization' => $this->key
])->post($this->url, $payload);
Log::info($response->body());
} catch (\Exception $e) {
Log::error($e->getMessage());
}
$this->sendPerformed($message);
return $this->numberOfRecipients($message);
}
/**
* Get the HTTP payload for sending the message.
*
* #param \Swift_Mime_SimpleMessage $message
* #return array
*/
protected function getPayload(Swift_Mime_SimpleMessage $message): array
{
// to
if (!empty($message->getTo())) {
$payload['payload']['to']['email'] = key($message->getTo());
}
// cc
if (!empty($message->getCc())) {
$payload['payload']['cc']['email'] = key($message->getCc());
}
// bcc
if (!empty($message->getBcc())) {
$payload['payload']['bcc']['email'] = key($message->getBcc());
}
// subject
$payload['payload']['subject'] = $message->getSubject();
// html
$payload['payload']['message']['html'] = $message->getBody();
// message children contains plain text, attachments, etc
$children = $message->getChildren();
if (!empty($children)) {
foreach($children as $child) {
// attachments
if (get_class($child) === 'Swift_Attachment') {
$payload['payload']['attachments'][] = [
'content' => base64_encode($child->getBody()),
'filename' => $child->getFilename(),
];
}
// plain text
if (get_class($child) === 'Swift_MimePart') {
$payload['payload']['message']['text'] = $child->getBody();
}
}
}
return $payload;
}
}
I had to go different route. Instead of searching the extension inside Transport class using Swift_Mime_SimpleMessage, I passed the filename with the original extension to Transport class from Illuminate\Mail\Mailable
public function build()
{
$tempUpload = request()->file('file_attachment');
$filename = $tampUpload->getClientOriginalName() . "." . $tempUpload->getClientOriginalExtension();
return $this->from($this->from_email, $this->from_name)
->subject('subject line')
->attach($tempUpload, ['as' => $filename)
->view('emails.gce.supplier_supplier')
->with($this->data);
}

Add Multiple OneSignal APP in Custom Notification in laravel

I have two Notification channels
app/Channels/Reseller/Web/OneSignalWeb.php
<?php
namespace App\Channels\Reseller\Web;
use Berkayk\OneSignal\OneSignalClient;
use Illuminate\Notifications\Notification;
use Illuminate\Support\Facades\Log;
use NotificationChannels\OneSignal\Exceptions\CouldNotSendNotification;
use NotificationChannels\OneSignal\OneSignalChannel;
class OneSignalWeb extends OneSignalChannel
{
public function __construct()
{
$client = new OneSignalClient(
env("ONESIGNAL_RESELLER_APP_ID"),
env("ONESIGNAL_RESELLER_REST_API_KEY"),
''
);
parent::__construct($client);
}
/**
* Send the given notification.
*
* #param mixed $notifiable
* #param \Illuminate\Notifications\Notification $notification
*
* #return \Psr\Http\Message\ResponseInterface
* #throws \NotificationChannels\OneSignal\Exceptions\CouldNotSendNotification
*/
public function send($notifiable, Notification $notification)
{
if (!$userIds = $notifiable->devices()->where('platform', 'web')->pluck('uuid')->toArray()) {
return;
}
/** #var ResponseInterface $response */
$response = $this->oneSignal->sendNotificationCustom(
$this->payload($notifiable, $notification, $userIds)
);
if ($response->getStatusCode() !== 200) {
throw CouldNotSendNotification::serviceRespondedWithAnError($response);
}
return $response;
}
}
app/Channels/Merchant/Web/OneSignalWeb.php
<?php
namespace App\Channels\Merchant\Web;
use Berkayk\OneSignal\OneSignalClient;
use Illuminate\Notifications\Notification;
use Illuminate\Support\Facades\Log;
use NotificationChannels\OneSignal\Exceptions\CouldNotSendNotification;
use NotificationChannels\OneSignal\OneSignalChannel;
class OneSignalWeb extends OneSignalChannel
{
public function __construct()
{
$client = new OneSignalClient(
env("ONESIGNAL_MERCHANT_APP_ID"),
env("ONESIGNAL_MERCHANT_REST_API_KEY"),
''
);
parent::__construct($client);
}
/**
* Send the given notification.
*
* #param mixed $notifiable
* #param \Illuminate\Notifications\Notification $notification
*
* #return \Psr\Http\Message\ResponseInterface
* #throws \NotificationChannels\OneSignal\Exceptions\CouldNotSendNotification
*/
public function send($notifiable, Notification $notification)
{
if (!$userIds = $notifiable->devices()->where('platform', 'web')->pluck('uuid')->toArray()) {
return;
}
/** #var ResponseInterface $response */
$response = $this->oneSignal->sendNotificationCustom(
$this->payload($notifiable, $notification, $userIds)
);
if ($response->getStatusCode() !== 200) {
throw CouldNotSendNotification::serviceRespondedWithAnError($response);
}
return $response;
}
}
In both of these channel only the difference is that in __constructwe load different keys for both Reseller and Merchant
public function __construct()
{
$client = new OneSignalClient(
env("ONESIGNAL_RESELLER_APP_ID"),
env("ONESIGNAL_RESELLER_REST_API_KEY"),
''
);
parent::__construct($client);
}
And this is the Nofications/Base.php where i've load both ResellerWeb and MerchantWeb Notification in via methods
public function via($notifiable)
{
return [
'database',
'broadcast',
ResellerWeb::class,
MerchantWeb::class,
];
}
I want to optimize the norification where instead of loading MerchantWeb::class and ResellerWeb::cass i want to create and load a general channel lets say NotificationWeb::class and want to use it for both Reseller and Merchant. And when i use it i will need to switch the env() keys based on for which i use.
How can this be achieved

Date and Time localization not works in Laravel-mix

I have Laravel mix installed on my server. there is a chat part on website and I use some kind of class :
class ActivityCell extends Component {
getTimestamp() {
const {message} = this.props;
return (
<span className="font-weight-semi-bold">
{utcDateCalendarTime(message.created_at)}
</span>
);
}
And here is my AppServiceProvider.php file :
<?php
namespace App\Providers;
use Illuminate\Http\Resources\Json\Resource;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* #return void
*/
public function boot()
{
setlocale(LC_ALL, Config::get('app.lc_all'));
Carbon::setLocale(Config::get('app.locale'));
}
public function register()
{
$this->registerPlugins();
}
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
$this->bootDatabase();
$this->bootResource();
}
/**
* Boot database schema
*
* #return void
*/
private function bootDatabase()
{
Schema::defaultStringLength(191);
}
/**
* Boot resource
*
* #return void
*/
private function bootResource()
{
Resource::withoutWrapping();
}
/**
* Register plugins
*
* #return void
*/
private function registerPlugins()
{
$pluginDirs = File::directories(base_path('app/Plugins'));
foreach ($pluginDirs as $pluginDir) {
$class = "App\\Plugins\\" . basename($pluginDir) . "\\PluginServiceProvider";
if (class_exists($class) && is_subclass_of($class, ServiceProvider::class)) {
$this->app->register($class);
}
}
}
}
I tried to put setlocale(LC_TIME, 'tr'); on top of the class file but there is no success. Then tried to use carbon in order to make the date is viewed in different languages when I change the website language.
I added the following codes in app/config.php :
'locale' => env('APP_LOCALE', 'az'),
'lc_all' => env('APP_LC_ALL', 'az_AZ.UTF-8'),
and added following to the env file :
APP_LOCALE = az
APP_LC_ALL = az_AZ.UTF-8
in both methods, I was not successful. I am pretty sure that I am doing a mistake somewhere but can not find where exactly. Maybe I am missing to add something else to add. Any help would be highly appreciated.
EDIT : Adding Chat.php :
<?php
namespace App\Models;
use App\Events\ChatParticipationChanged;
use App\Events\ChatUpdated;
use App\Http\Resources\ChatMessage as ChatMessageResource;
use App\Http\Resources\MarketplaceTrade as MarketplaceTradeResource;
use ArrayObject;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
use JSsVPSDioNXpfRC;
use DateTimeInterface;
class Chat extends Model
{
protected $lastMessageAttribute;
protected $lastMarketplaceTradeAttribute;
/**
* The attributes that aren't mass assignable.
*
* #var array
*/
protected $guarded = [];
/**
* The event map for the model.
*
* #var array
*/
protected $dispatchesEvents = [
'updated' => ChatUpdated::class
];
/**
* Indicates if the IDs are auto-incrementing.
*
* #var bool
*/
public $incrementing = false;
/**
* Get the route key for the model.
*
* #return string
*/
protected function serializeDate(DateTimeInterface $date)
{
return $date->translatedFormat('A B M');
}
public function getRouteKeyName()
{
return 'id';
}
/**
* #return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function creator()
{
return $this->belongsTo(User::class, 'creator_id', 'id');
}
/**
* Participants for this chat
*
* #return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function participants()
{
return $this->hasMany(ChatParticipant::class, 'chat_id', 'id');
}
/**
* Messages for this chat
*
* #return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function messages()
{
return $this->hasMany(ChatMessage::class, 'chat_id', 'id');
}
/**
* Update user's participation record
*
* #param User $user
*/
public function updateParticipation($user)
{
$this->participants()->where('user_id', $user->id)
->update(['last_read_at' => now()]);
broadcast(new ChatParticipationChanged($this, $user));
}
/**
* All marketplace trades hosted by this chat
*
* #return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function marketplaceTrades()
{
return $this->hasMany(MarketplaceTrade::class, 'chat_id', 'id')
->has('buyer')->has('seller');
}
/**
* #return Model|\Illuminate\Database\Eloquent\Relations\HasMany|mixed|object|null
*/
public function getLatestMarketplaceTrade()
{
if (!isset($this->lastMarketplaceTradeAttribute)) {
$trade = $this->marketplaceTrades()->latest()->first();
$this->lastMarketplaceTradeAttribute = new MarketplaceTradeResource($trade);
}
return $this->lastMarketplaceTradeAttribute;
}
/**
* Last chat message
*
* #return ChatMessageResource|ArrayObject|mixed
*/
public function getLatestMessage()
{
if (!isset($this->lastMessageAttribute)) {
$message = $this->messages()->latest()->first();
if ($message) {
$this->lastMessageAttribute = new ChatMessageResource($message);
} else {
$this->lastMessageAttribute = new ArrayObject();
}
}
return $this->lastMessageAttribute;
}
/**
* #param User $user
* #return array
*/
public function getParticipation($user)
{
$participant = $this->participants()
->where('user_id', $user->id)->without('user')
->first();
$unreadMessagesCount = ($participant && $participant->last_read_at) ?
$this->messages()->where('user_id', '!=', $user->id)
->where('created_at', '>', $participant->last_read_at)
->count() :
$this->messages()->where('user_id', '!=', $user->id)
->count();
return [
'user_id' => $user->id,
'unread_messages_count' => $unreadMessagesCount
];
}
/**
* If user should be allowed in this chat
*
* #param User $user
* #return bool
*/
public function shouldAllowUser($user)
{
$isParticipant = $this->participants()
->where('user_id', $user->id)->exists();
return (
$isParticipant ||
$user->can('moderate_chats')
);
}
/**
* #return string
*/
public function attachmentsDir()
{
return "chats/{$this->id}/message-attachments";
}
}
The problem is on your namespace :
// Using PHP callable syntax
use Carbon\Carbon;
Or,
// Using string syntax
\Carbon\Carbon::setLocale('ru');
You also need to use translatedFormat() method on your blade for use the translate format, like :
{{ Carbon\Carbon::now()->translatedFormat('A B M') }} // утра 428 фев
You can use serializeDate() method on your model, to change timestamp column as a translated dataTime format :
use DateTimeInterface;
protected function serializeDate(DateTimeInterface $date)
{
return $date->translatedFormat('A B M');
}

Laravel - postman return error 500 internal server error

im trying to return error message according to the exceptions types:
i have this code in my App\Exception\Handler.php:
<?php
namespace App\Exceptions;
use Exception;
use App\Traits\ApiResponser;
use Asm89\Stack\CorsService;
use Illuminate\Database\QueryException;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Session\TokenMismatchException;
use Illuminate\Validation\ValidationException;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
class Handler extends ExceptionHandler
{
use ApiResponser;
/**
* A list of the exception types that should not be reported.
*
* #var array
*/
protected $dontReport = [
//
];
/**
* A list of the inputs that are never flashed for validation exceptions.
*
* #var array
*/
protected $dontFlash = [
'password',
'password_confirmation',
];
/**
* Report or log an exception.
*
* This is a great spot to send exceptions to Sentry, Bugsnag, etc.
*
* #param \Exception $exception
* #return void
*/
public function report(Exception $exception)
{
parent::report($exception);
}
/**
* Render an exception into an HTTP response.
*
* #param \Illuminate\Http\Request $request
* #param \Exception $exception
* #return \Illuminate\Http\Response
*/
public function render($request, Exception $exception)
{
$response = $this->handleException($request, $exception);
app(CorsService::class)->addActualRequestHeaders($response, $request);
return $response;
}
public function handleException($request, Exception $exception)
{
if ($exception instanceof ValidationException) {
return $this->convertValidationExceptionToResponse($exception, $request);
}
if ($exception instanceof ModelNotFoundException) {
$modelo = strtolower(class_basename($exception->getModel()));
return $this->errorResponse("No existe ninguna instancia de {$modelo} con el id especificado", 404);
}
if ($exception instanceof AuthenticationException) {
return $this->unauthenticated($request, $exception);
}
if ($exception instanceof AuthorizationException) {
return $this->errorResponse('No posee permisos para ejecutar esta acción', 403);
}
if ($exception instanceof NotFoundHttpException) {
return $this->errorResponse('No se encontró la URL especificada', 404);
}
if ($exception instanceof MethodNotAllowedHttpException) {
return $this->errorResponse('El método especificado en la petición no es válido', 405);
}
if ($exception instanceof HttpException) {
return $this->errorResponse($exception->getMessage(), $exception->getStatusCode());
}
if ($exception instanceof QueryException) {
$codigo = $exception->errorInfo[1];
if ($codigo == 1451) {
return $this->errorResponse('No se puede eliminar de forma permamente el recurso porque está relacionado con algún otro.', 409);
}
}
if ($exception instanceof TokenMismatchException) {
return redirect()->back()->withInput($request->input());
}
if (config('app.debug')) {
return parent::render($request, $exception);
}
return $this->errorResponse('Falla inesperada. Intente luego', 500);
}
/**
* Convert an authentication exception into an unauthenticated response.
*
* #param \Illuminate\Http\Request $request
* #param \Illuminate\Auth\AuthenticationException $exception
* #return \Illuminate\Http\Response
*/
protected function unauthenticated($request, AuthenticationException $exception)
{
if ($this->isFrontend($request)) {
return redirect()->guest('login');
}
return $this->errorResponse('No autenticado.', 401);
}
/**
* Create a response object from the given validation exception.
*
* #param \Illuminate\Validation\ValidationException $e
* #param \Illuminate\Http\Request $request
* #return \Symfony\Component\HttpFoundation\Response
*/
protected function convertValidationExceptionToResponse(ValidationException $e, $request)
{
$errors = $e->validator->errors()->getMessages();
if ($this->isFrontend($request)) {
return $request->ajax() ? response()->json($errors, 422) : redirect()
->back()
->withInput($request->input())
->withErrors($errors);
}
return $this->errorResponse($errors, 422);
}
private function isFrontend($request)
{
return $request->acceptsHtml() && collect($request->route()->middleware())->contains('web');
}
}
i have this code in my file App\Traits\ApiResponser:
<?php
namespace App\Traits;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
trait ApiResponser
{
private function successResponse($data, $code)
{
return response()->json($data, $code);
}
protected function errorResponse($message, $code)
{
return response()->json(['error' => $message, 'code' => $code], $code);
}
protected function showAll(Collection $collection, $code = 200)
{
return $this->successResponse(['data' => $collection], $code);
}
protected function showOne(Model $instance, $code = 200)
{
return $this->successResponse(['data' => $instance], $code);
}
}
i have this in postman when i try to return a user that doesnt exist:
but i should get this error message in json:
"No existe ninguna instancia de usuario con el id especificado"

How to retry a job after x minutes?

I need to retry a job after 7 minutes after failed.
I try with $this->release(7); but on sometimes the job is running more that 1 time.
<?php
namespace Froakie\Listeners;
use Froakie\Components\Locks\LocksFactory;
use Froakie\Components\CRM\CrmFactory;
use Froakie\Events\NewLeadDataIncoming;
use Froakie\Services\LeadsService;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
/**
* Class CreateLead
*
* #package Froakie\Listeners
* #author Miguel Borges <miguel.borges#edirectinsure.com>
*/
class CreateOrUpdateLead implements ShouldQueue
{
use InteractsWithQueue;
const LOCKS_PREFIX = 'lead_event_lock_';
/**
* #var \Froakie\Services\LeadsService
*/
protected $leadsService;
/**
* Create the event listener.
*
* #param \Froakie\Services\LeadsService $leadsService
*/
public function __construct(LeadsService $leadsService)
{
$this->leadsService = $leadsService;
}
/**
* Handle the event.
*
* #param \Froakie\Events\NewLeadDataIncoming $event
* #throws \Exception
*/
public function handle(NewLeadDataIncoming $event)
{
app('log')->debug('CreateOrUpdateLead listener has catch a NewLeadDataIncoming', ['event' => $event]);
$lead = $this->leadsService->getLeadById($event->leadId);
LocksFactory::getInstance()->getMutexAdapter(self::LOCKS_PREFIX . $lead->getId(), null, 60)
->synchronized(function () use ($lead, $event) {
if (!$lead->isCreated()) {
$lead->setCreated(true)->save();
try {
$lead->crm_id = CrmFactory::getInstance()->getCRMLeadAdapter($lead->getCrm())
->createLead($event->leadDto);
$lead->save();
app('log')->info("A new lead has been created in {$lead->getCrm()}", [
'reference' => $lead->getReference(),
'crm_id' => $lead->getCrmId()
]);
return;
} catch (\Exception $exception) {
$lead->setCreated(false)->save();
throw $exception;
}
}
});
if (null !== $lead->getCrmId()) {
CrmFactory::getInstance()->getCRMLeadAdapter($lead->getCrm())
->updateLead($lead->getCrmId(), $event->leadDto);
app('log')->info("A lead has been updated in {$lead->getCrm()}", [
'reference' => $lead->getReference(),
'crm_id' => $lead->getCrmId()
]);
return;
}
$this->release(7);
}
}
I would create a task scheduler entry that sweeps the failed_jobs table and retries the job based on the failed_at column:
protected function schedule(Schedule $schedule)
{
$schedule->call(function () {
$jobs = DB::table('failed_jobs')->where('failed_at', '<=', now()->subMinutes(7))->get();
foreach ($jobs as $job) {
Artisan::call('queue:retry', [
'id' => $job->id
]);
}
})->everyMinute();
}

Resources