My project is based on Laravel 5.8.
I have a console command which perform some heavy tasks (generating very big PDF files, sending massive emails, etc.)
I tried to move these tasks to a background processes using jobs.
Here is what I did in order to test how it works:
php artisan make:job TestJob
The job file:
class TestJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
private $data;
/**
* Create a new job instance.
*
* #return void
*/
public function __construct(array $data)
{
$this->data = $data;
}
/**
* Execute the job.
*
* #return void
*/
public function handle()
{
sleep(5);
$date = new \DateTime();
$currentDateTime = $date->format('Ymd-His');
$logFileName = "logs/testjob-$currentDateTime.log";
$content = var_export($this->data, true);
$res = Storage::disk('local')->put($logFileName, $content);
echo "[TestJob] Print to log file: $logFileName\n";
}
}
Console command file:
public function handle()
{
echo "[Console Command] Starting...\n";
$someData = [
'First name' => 'John',
'Surname' => 'Doe'
];
TestJob::dispatch($someData);
echo "[Console Command] Finished!\n";
}
On execution, this is the output:
[Console Command] Starting...
<<< delay 5 sec.
[TestJob] Print to log file: logs/testjob-20210628-114321.log
[Console Command] Finished!
The problem:
The job is executed inside the script, and not in background.
What should I do to make it run in background?
You should change your queue connection (driver) from sync to redis (or another supported queue driver). You can do it in your .env file (for example: QUEUE_CONNECTION=database).
https://laravel.com/docs/5.8/queues#driver-prerequisites
Related
I am having trouble externally processing jobs on the queue while in test mode using PHPUnit. I have a job that writes a message to the the log file which is dispatched when I visit a route, I thought it could work like the way it does in development where there is a terminal window listening for work with php artisan queue:work and the other running server.
Test.php
public function testBasicTest()
{
$message = "Sample message job " . date("l jS \of F Y h:i:s A");
$filename = "laravel.log";
$this->json('GET', route('test.test-try-log-job'), ['message' => $message]);
$this->assertDatabaseHas('jobs', [
'id' => 1,
]);
exec('php artisan queue:work'); // Artisan::call("queue:work");
}
Controller
class TestController extends Controller
{
public function tryLogJob(Request $request){
dispatch(new TestJob($request->message))->onQueue('default');
return response()->json(['success'=>true], Response::HTTP_OK);
}
}
Job
class TestJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $message;
public function __construct($message)
{
$this->message = $message;
}
public function handle()
{
Log::info($this->message);
sleep(5);
}
}
when I use Artisan::call("queue:work"); the job is processed but when I use exec('php artisan queue:work');, it is not processed.
Is there a way I can get this to work? I really need to use the exec() function.
The phpunit.xml was configured to SQLite but the .env file was configured to MySQL.
During the test the job was being added to SQLite and not MySQL on which exec('php artisan queue:work'); is run.
I set the database variables in phpunit.xml to match those .env (MySQL) and the jobs are being handled correctly.
I've made CRON Job using Laravel's task scheduling. But what I need is to store somewhere when that task was last ran,
Does anyone have any methods of how they store that and also, If Laravel outputs anything that can tell you when it was last ran?
Thanks
Not possible directly, however it is possible if you cache a date-time string on each run (either at the beginning or end of your script).
Cache::rememberForever('name_of_artisan_task', function () {
return now()->toDateTimeString();
});
The example shows using the Cache facade's ::rememberForever method to create a key/value of the last time the task was ran. As the name suggests, this is saved forever.
You can easily retrieve this date and time using the cache() helper:
cache('name_of_artisan_task');
The con with this method is that if your cache is cleared, you will not longer have this stored.
Using a cache is not a safe way to do this, as #thisiskelvin hinted, clearing the cache will remove the data (which should happen on each deployment) but he didn't provide an alternative
So here is one if you need this date reliably (if you use it to know the interval to run an export for instance)
In which case I recommend creating a model php artisan make:model ScheduleRuns -m
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
/**
* #property string $task
*/
class ScheduleRuns extends Model
{
public const UPDATED_AT = false;
public $timestamps = true;
protected $attributes = [
'task' => '',
];
protected $fillable = [
'task',
'created_at',
];
}
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('schedule_runs', function (Blueprint $table) {
$table->id();
$table->string('task');
$table->timestamp('created_at')->nullable();
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::dropIfExists('schedule_runs');
}
};
Then use schedule hooks to create it (or do it within the task if you want to avoid possible seconds differences)
$schedule->command('export:users')
->weekly()->onSuccess(fn () => ScheduleRuns::create(['task' => 'export:users']))
And to retrieve the latest run
ScheduleRuns::query()->where('task', 'export:users')->latest();
Just write log each time the task was run, or you can push it into database.
<?php
namespace App\Console\Commands\Tasks;
use Illuminate\Console\Command;
class ScheduledTask extends Command
{
public function handle()
{
//
// ...handle you task
//
$file = 'logs/jobs/' . __CLASS__ . '.log';
$message = 'Executed at: ' . date('Y-m-d H:i:s', time());
file_put_contents(storage_path($file), $message, FILE_APPEND);
}
}
In kohana framework I can call controller via command line using
php5 index.php --uri=controller/method/var1/var2
Is it possible to call controller I want in Laravel 5 via cli? If yes, how to do this?
There is no way so far (not sure if there will ever be). However you can create your own Artisan Command that can do that. Create a command CallRoute using this:
php artisan make:console CallRoute
For Laravel 5.3 or greater you need to use make:command instead:
php artisan make:command CallRoute
This will generate a command class in app/Console/Commands/CallRoute.php. The contents of that class should look like this:
<?php namespace App\Console\Commands;
use Illuminate\Console\Command;
use Symfony\Component\Console\Input\InputOption;
use Illuminate\Http\Request;
class CallRoute extends Command {
protected $name = 'route:call';
protected $description = 'Call route from CLI';
public function __construct()
{
parent::__construct();
}
public function fire()
{
$request = Request::create($this->option('uri'), 'GET');
$this->info(app()['Illuminate\Contracts\Http\Kernel']->handle($request));
}
protected function getOptions()
{
return [
['uri', null, InputOption::VALUE_REQUIRED, 'The path of the route to be called', null],
];
}
}
You then need to register the command by adding it to the $commands array in app/Console/Kernel.php:
protected $commands = [
...,
'App\Console\Commands\CallRoute',
];
You can now call any route by using this command:
php artisan route:call --uri=/route/path/with/param
Mind you, this command will return a response as it would be sent to the browser, that means it includes the HTTP headers at the top of the output.
I am using Laravel 5.0 and I am triggering controllers using this code:
$ php artisan tinker
$ $controller = app()->make('App\Http\Controllers\MyController');
$ app()->call([$controller, 'myMethodName'], []);
the last [] in the app()->call() can hold arguments such as [user_id] => 10 etc'
For Laravel 5.4:
php artisan make:command CallRoute
Then in app/Console/Commands/CallRoute.php:
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Http\Request;
class CallRoute extends Command
{
/**
* The name and signature of the console command.
*
* #var string
*/
protected $signature = 'route:call {uri}';
/**
* The console command description.
*
* #var string
*/
protected $description = 'php artsian route:call /route';
/**
* Create a new command instance.
*
* #return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* #return mixed
*/
public function handle()
{
$request = Request::create($this->argument('uri'), 'GET');
$this->info(app()->make(\Illuminate\Contracts\Http\Kernel::class)->handle($request));
}
}
Then in app/Console/Kernel.php:
protected $commands = [
'App\Console\Commands\CallRoute'
];
Call like: php artisan route:call /path
Laravel 5.7
Using tinker
// URL: http://xxx.test/calendar?filter[id]=1&anotherparam=2
$cc = app()->make('App\Http\Controllers\CalendarController');
app()->call([$cc, 'getCalendarV2'], ['filter[id]'=>1, 'anotherparam' => '2']);
You can do it in this way too. First, create the command using
php artisan command:commandName
Now in the handle of the command, call the controller and trigger the method.
Eg,
public function handle(){
$controller = new ControllerName(); // make sure to import the controller
$controller->controllerMethod();
}
This will actually do the work. Hope, this helps.
DEPENDENCY INJECTION WON'T WORK
To version 8 of laravel.
First step: type command in terminal
php artisan tinker
Secound step:
$instante = new MyController(null);
Or if argument by an instance of model, then, pass name model class.
Example:
$instante = new MyController(new MyModelHere());
Press enter.
Finally, call method with $instante->myMethod() here.
See:
I am trying to dispatch my send email action using Laravel database queue
however this process still continues in my browser instead of working behind.
this is my controller
protected function importUserExcel(UploadedFile $file, Request $request){
$user_role = Role::where('name','=','user')->first();
\Excel::load($file, function($reader) use ($user_role) {
$excel = $reader->select()->get();
foreach($excel[0] as $line){
$user = User::firstOrnew([
'email' => $line['email']]);
$user->email = $line['email'];
$user->name = $line['name'];
$user->password= bcrypt(srand(15));
$user->town = $line['town'];
$user->dealer_code = $line['dealer_code'];
$user->type = $line['type'];
// $user->save();
$user->sendUserEmail();
//$user->attachRole($user_role);
}
});
}
this is my model function
public function sendUserEmail()
{
$delay = Carbon::now()->addMinutes(15);
\Log::info("Request Begins");
$user = new SendEmails($this);
$user->delay($delay);
dispatch($user);
\Log::info("Request Ends");
}
and this is my job
class SendEmails implements ShouldQueue
{
use InteractsWithQueue, Queueable, SerializesModels;
/**
* Create a new job instance.
*
* #return void
*/
public function __construct(User $user)
{
$this->handle($user);
}
/**
* Execute the job.
*
* #return void
*/
public function handle(User $user)
{
$broker = $user->broker;
$brokerInstance = \Password::broker($broker);
view()->share('locale', app()->getLocale());
$response = $brokerInstance->sendResetLink([ 'email' => $user->email ], function (Message $message) {
$message->subject(trans('emails.welcome_subject'));
});
}
}
however result seems coming eventually not delaying or queueing anything.
Meanwhile my browser also process instead of putting process to behind.
Your job's constructor should not call the handle() method; it should just set properties needed for the handle method. It's up to your queue worker to call the handle method.
Your call to app()->getLocale() may be incorrect if you're setting the locale per-request; a job is executed from another process and without middlewares or an associated http request.
class SendEmails implements ShouldQueue { use InteractsWithQueue, Queueable, SerializesModels;
protected $user;
public function __construct(User $user) {
$this->user = $user;
}
public function handle() {
$user = $this->user;
$broker = $user->broker;
$brokerInstance = \Password::broker($broker);
view()->share('locale', app()->getLocale());
$response = $brokerInstance->sendResetLink([ 'email' => $user->email ], function (Message $message) {
$message->subject(trans('emails.welcome_subject'));
});
}
}
You can try again in the following way (I assume that you did instructions in Laravel docs but someday it's not working):
drop table jobs in your database.
run command php artisan migrate in console
run command php artisan queue:work in console
retry your app
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` ...