Im trying to send an email, with ShouldQueue, and passing data into the email blade view, but it fails if i implement ShouldQueue.
See below for my code.
The code as it is below, does execute the mail and places it in jobs queue (database driver). It works and i can see the job in jobs table.
Then i run php artisan queue:work to start worker on my computer.
When worker tries to execute job, it fails. It is now removed from jobs table and new entry to failed_jobs table. The email is not sent.
In failed_jobs table, exception column, i get this error (stacktrace removed)
Next Facade\Ignition\Exceptions\ViewException: Undefined variable $data (View: /Users/xxx/xxx/xxx/resources/views/mails/ContactForm.blade.php) in /Users/xxx/xxx/xxx/resources/views/mails/ContactForm.blade.php:2
IF I remove "implements ShouldQueue" from the ContactFormMail class, everything works and email is being sent, but then, the email is not being put in the queue.
I have tried passing data both ways described in Laravel docs and i have also tried restarting worker.
Any ideas where i go wrong?
If it was not clear, I want to pass data to ContactForm.blade.php and also place email in queue.
Here is my code
Controller:
public function submit()
{
$this->validate();
$data = [
'name' => $this->name,
'email' => $this->email,
'message' => $this->message,
];
try {
Mail::to('xxx#xxx.xx')->send(new ContactFormMail($data) );
$this->name = "";
$this->email = "";
$this->message = "";
$this->status = "Message sent!";
} catch (Exception $e) {
$this->status = "Something went wrong!";
}
}
ContactFormMail class:
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Queue\ShouldQueue;
class ContactFormMail extends Mailable implements ShouldQueue
{
use Queueable, SerializesModels;
public $data;
/**
* Create a new message instance.
*
* #return void
*/
public function __construct($data)
{
$this->data = $data;
}
/**
* Build the message.
*
* #return $this
*/
public function build()
{
return $this->view('mails.ContactForm');
}
}
ContactForm blade file:
<div>
{{$data['name']}}<br>
{{$data['email']}}<br>
{{$data['message']}}<br>
</div>
Please try restarting the queue worker. Probably something got stuck. After restarting the queue it "should" work.
php artisan queue:restart
Related
Laravel Version: 8.78.1
PHP Version: 8.0.10
I've created a custom command to run on a schedule and email a notification.
My Command class handle method:
public function handle()
{
$sql = "SELECT * FROM Licences WHERE (Expired = 1)";
$list = DB::select($sql);
return (new NotifyExpiredLicences($list))->toMail('me#gmail.com');
}
My notification method:
public function toMail($notifiable)
{
return (new MailMessage)
->subject('Clients with Expired Licences')
->markdown('vendor/notifications/expiredlicences',
['clients' => $this->list, 'toname' => 'Me']);
}
Whenever I test this by running it manually with php artisan email:expired-licences I get the following error Object of class Illuminate\Notifications\Messages\MailMessage could not be converted to int from my command class in the handle method.
However, the preview of my email works fine & displays as expected:
Route::get('/notification', function () {
return (new SendExpiredLicences())->handle();
});
If I remove the return statement from my handle() method, then although I get no errors, neither in my console or in storage\logs, also the preview stops working.
At this point I'm sure I've missed something important from the way this is supposed to be done, but after going through the Laravel docs and looking at online tutorials/examples, I've no idea what.
I've got everything working - though not entirely sure it's the "Laravel way".
If anyone's got suggestions for improving it - add a comment or new answer and I'll try it out.
Console\Kernel.php:
protected function schedule(Schedule $schedule)
{
$schedule->command('email:expired-licences')
->weekdays()
->at('08:30');
}
App\Console\Commands\SendExpiredLicences.php:
class SendExpiredLicences extends Command
{
protected $signature = 'email:expired-licences';
protected $description = 'Email a list of expired licences to Admin';
private $mail;
public function _construct()
{
$clients = DB::select("[Insert SQL here]");
$this->mail = (new NotifyExpiredLicences($clients))->toMail('admin#example.com');
parent::__construct();
}
public function handle()
{
Mail::to('admin#example.com')->send($this->mail);
return 0;
}
public function preview()
{
return $this->mail;
}
}
App\Notifications\NotifyExpiredLicences.php:
class NotifyExpiredLicences extends Notification
{
public function __construct(protected $clients)
{
}
public function via($notifiable)
{
return ['mail'];
}
public function toMail($notifiable)
{
return (new Mailable($this->clients));
}
}
App\Mail\ExpiredLicences.php:
class ExpiredLicences extends Mailable
{
public function __construct(private $clients)
{
}
public function build()
{
return $this
->subject('Clients with Expired Licences')
->markdown('emails/expiredlicences',
['clients' => $this->clients, 'toname' => 'Admin']);
}
}
resources\views\emails\expiredlicences.blade.php:
#component('mail::message')
# Hi {!! $toname !!},
#component('mail::table')
| Client | Expired |
| ------------- | --------:|
#foreach ($clients as $client)
|{!! $client->CompanyName !!} | {!! $client->Expired !!}|
#endforeach
#endcomponent
<hr />
Thanks, {!! config('app.name') !!}
#endcomponent
For previewing with the browser routes\web.php:
Route::get('/notification', function () {
return (new SendExpiredLicences())->preview();
});
Ok just to save more commenting, here's what I'd recommend doing. This is all based on the Laravel docs, but there are multiple ways of doing it, including what you've used above. I don't really think of them as "right and wrong," more "common and uncommon."
Console\Kernel.php: I'd keep this mostly as-is, but pass the email to the command from a config file, rather than having it fixed in the command.
use App\Console\Commands\SendExpiredLicences;
…
protected function schedule(Schedule $schedule)
{
$recipient = config('myapp.expired.recipient');
$schedule->command(SendExpiredLicences::class, [$recipient])
->weekdays()
->at('08:30');
}
config/myapp.php:
<?php
return [
'expired' => [
'recipient' => 'admin#example.com',
],
];
App\Console\Commands\SendExpiredLicences.php: update the command to accept the email address as an argument, use on-demand notifications, and get rid of preview() method. Neither the command or the notification need to know about the client list, so don't build it yet.
<?php
namespace App\Console\Commands;
use App\Console\Command;
use App\Notifications\NotifyExpiredLicences;
use Illuminate\Support\Facade\Notification;
class SendExpiredLicences extends Command
{
protected $signature = 'email:expired-licences {recipient}';
protected $description = 'Email a list of expired licences to the given address';
public function handle()
{
$recip = $this->argument('recipient');
Notification::route('email', $recip)->notify(new NotifyExpiredLicences());
}
}
App\Notifications\NotifyExpiredLicences.php: the toMail() method should pass the notifiable object (i.e. the user getting notified) along, because the mailable will be responsible for adding the To address before the thing is sent.
<?php
namespace App\Notifications;
use App\Mail\ExpiredLicenses;
use Illuminate\Notifications\Notification;
class NotifyExpiredLicences extends Notification
{
public function via($notifiable)
{
return ['mail'];
}
public function toMail($notifiable)
{
return (new ExpiredLicenses($notifiable));
}
}
App\Mail\ExpiredLicences.php: since the mail message actually needs the list of clients, this is where we build it. We get the recipient here, either from the user's email or the anonymous object.
<?php
namespace App\Mail;
use App\Models\Client;
use Illuminate\Notifications\AnonymousNotifiable;
class ExpiredLicences extends Mailable
{
private $email;
public function __construct(private $notifiable)
{
// this allows the notification to be sent to normal users
// not just on-demand
$this->email = $notifiable instanceof AnonymousNotifiable
? $notifiable->routeNotificationFor('mail')
: $notifiable->email;
}
public function build()
{
// or whatever your object is
$clients = Client::whereHas('licenses', fn($q)=>$q->whereExpired(1));
return $this
->subject('Clients with Expired Licences')
->markdown(
'emails.expiredlicences',
['clients' => $clients, 'toname' => $this->notifiable->name ?? 'Admin']
)
->to($this->email);
}
}
For previewing with the browser routes\web.php:
Route::get('/notification', function () {
// create a dummy AnonymousNotifiable object for preview
$anon = Notification::route('email', 'no#example.com');
return (new ExpiredLicencesNotification())
->toMail($anon);
});
I would like to send a telegram message to a specific user at 17:00 using laravel's Telegram notification channel, I however can't seem to get it going. I currently use a cmd command for testing, but keep getting errors and don't know what to do.
Here are my files for the command and notification:
SendNotification.php
<?php
namespace Rogier\Lab\Console;
use Illuminate\Console\Command;
use Rogier\Lab\Notifications\DailyTelegram;
class SendNotifications extends Command
{
protected $name = 'lab:notifications:send';
protected $description = 'Send notifications';
protected $userid = 919871501;
/**
* Execute the console command.
* #return void
*/
public function handle()
{
$this->output->writeln('Sending notifications');
$notification = new DailyTelegram($this->userid);
$notification->via()->toTelegram();
$this->output->writeln('Done');
}
}
and DailyTelegram.php
<?php
namespace Rogier\Lab\Notifications;
use NotificationChannels\Telegram\TelegramChannel;
use NotificationChannels\Telegram\TelegramMessage;
use Illuminate\Notifications\Notification;
use Illuminate\Notifications\Notifiable;
class DailyTelegram extends Notification
{
protected $userid = 919871501;
public function via()
{
return [TelegramChannel::class];
}
public function toTelegram()
{
return TelegramMessage::create()
// Optional recipient user id.
->to($this->userid)
// Markdown supported.
->content("Hello there!\nYour invoice has been *PAID*");
}
}
I currently get the error "Call to a member function toTelegram() on array", but I feel like I tried everything, maybe I'm doing it completely wrong. Does anyone know how I should do it?
thanks in advance
Yes, you are doing it wrong. Notifiables have notify() method, you should use it:
$user->notify(new DailyTelegram);
In this example $user is App\User instance (which is notifiable out of the box).
You should check out both Laravel's sending notifications and laravel-notification-channels/telegram docs.
I am using an inbuilt code in Laravel to send Email Notification. Code is below. I am using smtp to send email
class RegisterNotification extends Notification
{
use Queueable;
public function __construct($token)
{
$this->token = $token;
}
public function via($notifiable)
{
return ['mail'];
}
public function toMail($notifiable)
{
return (new MailMessage)
->line('hi');
}
public function toArray($notifiable)
{
return [
//
];
}
}
Here the problem is, it takes around 5 seconds to complete the process and control does not come back. I am assuming that if it come back and do the email sending work in background...it would save a lot of time.
Is there any inbuilt work to do the same? I meant, control should come back and should say email sent...and then it should do the work in background.
Email Sending code in Controller
class apiRegisterController extends Controller
{
public function Register(RegisterRequest $request) {
$RegisterNotification = new RegisterNotification($Token);
$User->notify($RegisterNotification);
}
}
Code for Queue
Controller Code
$job = (new SendForgotPasswordEmail($Data))->onConnection('database');
dispatch($job);
Job
class SendForgotPasswordEmail implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $Data;
public $tries = 5;
public function __construct($Data)
{
$this->Data = $Data;
}
public function handle()
{
$Token = $this->Data["Token"];
$User = $this->Data["User"];
$RegisterNotification = new RegisterNotification($Token);
$User->notify($RegisterNotification);
}
}
Step 1: Change class RegisterNotification extends Notification to class RegisterNotification extends Notification implements ShouldQueue
Step 2: Implement a queue driver. In your config/queue.php make sure your driver is not set to sync like so: 'default' => env('QUEUE_DRIVER', 'sync'), and make sure your .env doesnt have QUEUE_DRIVER=sync. You can look at the Laravel documentation for queues to choose an appropriate queue driver
You can use the build-in API.
$user = User::findOrFail($id);
Mail::queue('emails.welcome', $data, function ($message) use ($user){
$message->from('hello#app.com', 'Your Application');
$message->to($user->email, $user->name)->subject('Your Reminder!');
});
But first you have to configure the queues.
Add in your .env file the line QUEUE_DRIVER=sync and then write on the terminal php artisan queue:listen.
If you want the queue to run forever on the server use Supervisor. Queues documentation explains how you can use it.
you can use laravel job queue https://laravel.com/docs/5.4/queues
Mail::queue(
I have a command in my code that is run daily by cron that sends emails to all new users. It used to work ok, but after I have swithched the queue driver to SQS and upgraded Laravel 5.2 to 5.3 it started throwing an error.
InvalidArgumentExceptionvendor/laravel/framework/src/Illuminate/Mail/Mailer.php:379
Invalid view.
I don't know what might cause the error, because I have not deleted the view. Also when I run the command manually it does not throw any errors.
Here is the command code:
public function handle()
{
$subscriptions = Purchase::where('created_at', '>=', Carbon::now()->subDay())
->groupBy('user_id')
->get();
$bar = $this->output->createProgressBar(count($subscriptions));
foreach ($subscriptions as $subscription) {
$user = $subscription->user;
// if ($user->is_active_customer) {
Mail::to($user)->bcc(env('BCC_RECEIPTS_EMAIL'))->send(new NeedHelp());
// }
$bar->advance();
}
$bar->finish();
$this->info("\nSuccess! " . number_format(count($subscriptions)) . ' emails were sent.');
}
Here is the NeedHelp class code (I have changed the email and sender name for this thread):
<?php
namespace App\Mail;
use App\User;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Queue\ShouldQueue;
class NeedHelp extends Mailable
{
use Queueable, SerializesModels;
/**
* Create a new message instance.
*
*/
public function __construct(){
}
/**
* Build the message.
*
* #return $this
*/
public function build()
{
return $this->subject('Need help?')
->from('default#mail.com', 'Sender')
->view('emails.need-help');
}
}
I have found the error. The reason was that I have accidentally connected two applications to the same queue, which caused them to process jobs and emails of each other which resulted in this error.
I'm currently developing my personal application that is about private servers (for example, Minecraft servers) and since querying the server takes some time, I decided to implement queued jobs. However, they are not working properly, and they run immediately when called even though they are delayed, causing a massive latency in a page request.
Here's my HomeController's index() which calls the job to update every server with a 30 seconds delay:
public function index()
{
$servers = Server::all();
foreach($servers as $server)
{
// Job Dispatch
$job = (new UpdateServer($server->id))->delay(30);
$this->dispatch($job);
}
return view('serverlist.index', compact('servers'));
}
The job class that updates the servers is the following:
class UpdateServer extends Job implements SelfHandling, ShouldQueue
{
use InteractsWithQueue, SerializesModels;
protected $id;
public function __construct($id)
{
$this->id = $id;
}
public function handle(){
$server = Server::findOrFail($this->id);
// Preparing the packet
$test = new RAGBuffer();
$test->addChar('255');
$test->addChar('1');
$test->addShort(1 | 8);
// Finding the server
$serverGame = new RAGServer($server->server_ip);
// Get server information
$status = $serverGame->sendPacket($test);
$server->onlinePlayers = $status->getOnline();
$server->peakPlayers = $status->getPeak();
$server->maxPlayers = $status->getMax();
if (!$server->save()) {
// Error occurred
}
}
}
Whenever the HomeController's index() is run, there's a massive delay in the page request. I followed the tutorial at Laravel's Official Webpage, and I tried to find answers, but I didn't find anything.
So, what am I doing wrong? Why isn't the job getting delayed 30 seconds and then doing this in background in my server?
Also: The handle() is doing what it is supposed to. It queries the server, sends packets, and updates my database with the correct information.
You have to set up the queue driver you want to use in your project's root dir's .env file.
By default, the queue driver is sync which does exactly what you are describing, executing queues immediately.
You can choose of some different queue drivers, such as beanstalked or redis (which would be my choice). There's an excellent freebie on laracasts.com about setting up a beanstalked queue.
To view all available queue driver options in Laravel, have a look here.
Here's a .env example
APP_ENV=local
APP_DEBUG=true
APP_KEY=SomeRandomString
DB_HOST=localhost
DB_DATABASE=homestead
DB_USERNAME=homestead
DB_PASSWORD=secret
CACHE_DRIVER=file
SESSION_DRIVER=file
QUEUE_DRIVER=sync // <-- Put the desired driver here
MAIL_DRIVER=smtp
MAIL_HOST=mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
This was driving me crazy for ages before I realised that Laravel 5.7 renamed QUEUE_DRIVER to QUEUE_CONNECTION in the .env files
For someone who has made the changes from previous answers and still didn't work, check the default value of the queue file like this: dd(Config::get('queue.default'))
For me it didn't change until flushing the config cache:
php artisan config:clear
To test locally you could set the driver to
QUEUE_DRIVER=database
And run php artisan queue:table.
And then php artisan migrate, so you would get your queue saved into the database, so you visually could see what`s going on.
And to run your queues, simply run php artisan queue:listen ... and leave it running as you do with artisan serve.
If you are running on php artisan serve, restart this and run php artisan serve again. This worked for me after hours trying to wonder what it was.
:)
If you are running tests against the queue service through phpunit, make sure that
<env name="QUEUE_DRIVER" value="X"/>
in phpunit.xml doesn't override your desired queue driver.
Ensure that
'default' => env('QUEUE_DRIVER', 'database'),
in file config/queue.php
And
QUEUE_DRIVER=database
in the .env file to ensure the database driver is used.
It's because the delay function takes an absolute date in the future
UpdateServer::dispatch($server->id)->delay(now()->addSeconds(30))
In my case, I had to implement ShouldQueue and use the Queueable trait:
class CustomNotification extends Notification implements ShouldQueue{
use Queueable;
...
Even if you have configured everything properly this can still happen. We had this problem with Laravel 5.4 where we created a lot of jobs, some delayed, and added them to the queue via Queue:bulk($jobs). This call and also Queue::push($job) completely ignore the delay and cause the job to be processed immediately.
If you want your job to be put on the queue as you configured it you must call dispatch($job).
This is the complete steps to create user API and store its history in jobs table.
In Jobs class:
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use App\Repositories\Eloquent\ApiRepo as ApiRepo;
use Log;
use App\Models\User;
class UserProcess implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* The number of times the job may be attempted and override the queue tries.
*
* #var int
*/
public $tries = 3;
/**
* Create a new job instance.
*
* #return void
*/
public $user;
public function __construct(User $user)
{
$this->user = $user;
}
/**
* Execute the job.
*
* #return void
*/
public function handle()
{
try {
// make api call
Log::info("inside handle".$this->user->id);
$apiRepo = new ApiRepo;
$response = $apiRepo->getUserDetails($this->user->id);
Log::info("Response".$response);
} catch (\Throwable $exception) {
if ($this->attempts() > 3) {
// hard fail after 3 attempts
throw $exception;
}
// requeue this job to be executes
// in 3 minutes (180 seconds) from now
$this->release(180);
return;
}
}
}
In Controller Class:
namespace App\Http\Controllers\API;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Validator;
use App\Models\User;
use App\Jobs\UserProcess;
use App\Models\UserHistory;
use Carbon\Carbon;
class UserController extends Controller
{
public function create(Request $request)
{
$rules = [
'first_name' => 'required|string|max:100',
'last_name' => 'required|string|max:100',
'email' => 'required|string|email|unique:users,email',
'phone_number' => 'required|string|max:10',
'address' => 'string',
'date_of_birth' => 'string|date_format:Y-m-d|before:today',
'is_vaccinated' => 'string|in:YES,NO',
'vaccine_name' => 'string|required_if:is_vaccinated,==,YES|in:COVAXIN,COVISHIELD'
];
$validator = Validator::make(array_map('trim', ($request->all())),$rules);
if($validator->fails()){
return response()->json($validator->errors());
}else{
$user = new User;
$user->first_name = $request->first_name;
$user->last_name = $request->last_name;
$user->email = $request->email;
$user->phone_number = $request->phone_number;
$user->address = $request->address;
$user->date_of_birth = $request->date_of_birth;
$user->is_vaccinated = $request->is_vaccinated;
$user->vaccine_name = $request->vaccine_name;
$user->save();
$token = $user->createToken('auth_token')->plainTextToken;
if($user->save()){
$job = (new UserProcess($user))->delay(Carbon::now()->addMinutes(1));
$this->dispatch($job);
return response()
->json(['data' => $user,'status' => '200','message' => 'User Added Successfully','access_token' => $token, 'token_type' => 'Bearer']);
}else{
return response()
->json(['data' => $user,'status' => '409','message' => 'Something went wrong!']);
}
}
}
}
In ApiRepo Class:
namespace App\Repositories\Eloquent;
use App\Repositories\ApiInterface;
use Illuminate\Http\Request;
use App\Http\Requests;
use Illuminate\Http\Response;
use App\Http\Controllers\Controller;
use App\Models\User;
use App\Models\UserHistory;
use Log;
class ApiRepo implements ApiInterface {
public function getUserDetails($userid) {
Log::info('User ID - '.#$userid);
$user_history = new UserHistory();
$save_user = User::find($userid);
Log::info('User Json Data - '.#$save_user);
$user_history->user_id = $userid;
$user_history->first_name = $save_user->first_name;
$user_history->last_name = $save_user->last_name;
$user_history->email = $save_user->email ;
$user_history->phone_number = $save_user->phone_number;
$user_history->address = $save_user->address;
$user_history->date_of_birth = $save_user->date_of_birth;
$user_history->is_vaccinated = $save_user->is_vaccinated;
$user_history->vaccine_name = $save_user->vaccine_name;
$user_history->save();
if($user_history->save()){
Log::info('User history Saved!');
}
}
}