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
Morning all,
I am trying to create a command that I can schedule to check if a certification date has expired and if it has, update the boolean from 0 to 1. I have never used commands before and I have read the OctoberCMS documentation but I found it confusing.
If anyone could help me, that would be perfect.
Here is what I have so far.
<?php
namespace Bitpixlimited\Concert\Console;
use Illuminate\Console\Command;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use BitpixLimited\ConCert\Models\Certification;
use Carbon\Carbon;
/**
* CheckExpiredCertifications Command
*/
class CheckExpiredCertifications extends Command
{
/**
* #var string name is the console command name
*/
protected $name = 'concert:checkexpiredcertifications';
/**
* #var string description is the console command description
*/
protected $description = 'No description provided yet...';
/**
* handle executes the console command
*/
public function handle()
{
$certifications = Certification::all();
$date = now();
$expiredValue = '1';
foreach ($certifications as $certification) {
if ($certification->expiry_date < $date) {
$certification->status = $expiredValue;
}
$certification->save();
}
}
/**
* getArguments get the console command arguments
*/
protected function getArguments()
{
return [];
}
/**
* getOptions get the console command options
*/
protected function getOptions()
{
return [];
}
}
Take a look at this code:
public function handle()
{
$certifications = Certification::query()
->where('expiry_date', '<', now())
->update(['status' => '1'])
}
It does what you are trying to achieve, it's a simplified version of your code and it is more performant.
We don't actually get the records, we update them directly
We update all records that have a expiry_date before now()
All these records now have the status equals to 1
Since we don't store the records in memory and we don't need to "build" the Collection, it's far better performance wise.
The drawback is that you lose model events (if you declared any) and mutators, but I assume that's not the case here.
If you need to access all models mutators, methods, events (now or in the future), then use the following code:
public function handle()
{
$certifications = Certification::query()
->where('expiry_date', '<', now())
->each(function(Certification $certification){
$certification->status = '1';
$certification->save();
});
}
The main difference is that we actually retrieve the records and build all the Certification instances. It gives you more capabilities but the performances will take a hit.
There are more optimized ways to do this, especially if you have a large number of rows, but this is another topic.
You should run this command in your scheduler at the frequency you wish, for instance every minute:
protected function schedule(Schedule $schedule)
{
$schedule->command('concert:checkexpiredcertifications')->everyMinute();
}
Every minute, we will update all records that have expiry_date in the past and set their status to '1'.
Of course, you must have a working scheduler to do this, but that's a little bit off topic here (docs here: https://laravel.com/docs/8.x/scheduling#running-the-scheduler).
I have a job that looks like this:
<?php
namespace App\Jobs;
use App\User;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Redis;
class FakeJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/** #var User */
private $user;
public $tries = 30;
/**
* Create a new job instance.
*
* #param User $user
*/
public function __construct(User $user)
{
$this->user = $user;
}
/**
* Execute the job.
*
* #return void
*/
public function handle()
{
try {
Redis::throttle('key')->allow(1)->every(60)->then(function () {
// Job logic...
throw new \Exception('fake exception');
$this->logInfo($this->user->name);
});
} catch(\Illuminate\Contracts\Redis\LimiterTimeoutException $e) {
$this->logInfo('inside LimiterTimeout catch, ' . $e->getMessage());
$this->release(1);
}
}
public function failed(\Exception $exception) {
// Send user notification of failure, etc...
$this->logInfo('inside failure, ' . $exception->getMessage());
}
public function logInfo($message) {
$path = storage_path('logs/FakeJob.log');
$logText = time() . ' ' . $message;
file_put_contents($path, $logText.PHP_EOL , FILE_APPEND | LOCK_EX);
}
}
Now you'll notice that I have a Redis throttler in the handle function, and that I immediately throw an error inside the callback. This is what I'm testing, and that error is what's going wrong.
You see, I'm trying to distinguish between LimiterTimeoutExceptions and exceptions thrown inside the Throttler callback. When I run the job through the sync connection, everything works as expected: FakeJob::dispatch($user)->onConnection('sync'); Errors that happen because I made a request within 60 seconds of a previous request go into the LimiterTimeoutException catch block, and errors that happen inside the Throttle callback go to the failed function.
But when I schedule the job through my default scheduler, which is Database, it seems as though EVERY error is going through the LimiterTimeoutException catch block, until I hit the retry limit - that error goes into the failed function, but not the 'fake exception' error.
I feel very confused about this.
Of note: even though my 'fake exception' ends up being caught by the LimiterTimeoutException catch block, the $e->getMessage() function does NOT return fake exception there for some reason. But I'm 100% certain that it is that error that is causing it to go there, because it's definitely not due to the throttler in those tests.
OK so actually what's happening is that it won't end up in the failed() function until it hits the retry limit, no matter what, regardless of any exception thrown. If I want to deal with LimiterTimeoutException errors in a different way from every other error, I'll have to wrap my Redis::throttle call in a try-catch and just check the type of error in the catch.
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!');
}
}
}