Laravel Queue How to get data in job handle method - php

I have created a sequence of messsages to be sent in text messages through twilio.
I have created a controller to put the messages in a queue with the data received in the post request. Here is my controller to make queue:
public function make_queue(Request $request)
{
$data = array (
'phone' => $request->input('phone'),
'message'=> $request->input('message'),
'token'=> $request->input('token')',
'sid'=> $request->input('sid')
);
ProcessMessages::dispatch($data)
->delay(now()->addSeconds(15));
return 'message will be sent';
}
And in handle the job , in the handle function
public function handle()
{
$token = should_come_from job;
$sid = should_come_from job;
$ids = should_come_from job;
$msg = should_come_from job;
try{
// send message
}
catch (exception $e)
{
handle exception
}
}
I am not able to figure out how do I get the values in the handle function to actually send the message....

You need to add a constructor method in your job handler class (ProcessMessages), for example:
// namespace and use statements...
class ProcessMessages implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $data;
public function __construct(array $data)
{
$this->data = $data;
}
public function handle()
{
$token = $this->data['token'];
// ...
}
}
Once you have written your job class, you may dispatch it using the
dispatch method on the job itself. The arguments passed to the
dispatch method will be given to the job's constructor. Read about dispatching Jobs.

Related

How to test Laravel 5 jobs?

I try to catch an event, when job is completed
Test code:
class MyTest extends TestCase {
public function testJobsEvents ()
{
Queue::after(function (JobProcessed $event) {
// if ( $job is 'MyJob1' ) then do test
dump($event->job->payload());
$event->job->payload()
});
$response = $this->post('/api/user', [ 'test' => 'data' ], $this->headers);
$response->assertSuccessful($response->isOk());
}
}
method in UserController:
public function userAction (Request $request) {
MyJob1::dispatch($request->toArray());
MyJob2::dispatch($request->toArray());
return response(null, 200);
}
My job:
class Job1 implements ShouldQueue {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $data = [];
public function __construct($data)
{
$this->data= $data;
}
public function handle()
{
// Process uploaded
}
}
I need to check some data after job is complete but I get serialized data from
$event->job->payload() in Queue::after And I don't understand how to check job ?
Well, to test the logic inside handle method you just need to instantiate the job class & invoke the handle method.
public function testJobsEvents()
{
$job = new \App\Jobs\YourJob;
$job->handle();
// Assert the side effect of your job...
}
Remember, a job is just a class after all.
Laravel version ^5 || ^7
Synchronous Dispatching
If you would like to dispatch a job immediately (synchronously), you may use the dispatchNow method. When using this method, the job will not be queued and will be run immediately within the current process:
Job::dispatchNow()
Laravel 8 update
<?php
namespace Tests\Feature;
use App\Jobs\ShipOrder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Support\Facades\Bus;
use Tests\TestCase;
class ExampleTest extends TestCase
{
public function test_orders_can_be_shipped()
{
Bus::fake();
// Perform order shipping...
// Assert that a job was dispatched...
Bus::assertDispatched(ShipOrder::class);
// Assert a job was not dispatched...
Bus::assertNotDispatched(AnotherJob::class);
}
}
This my generic method, using a route
Route::get('job-tester/{job}', function ($job) {
if(env('APP_ENV') == 'local'){
$j = "\\App\Jobs\\".$job;
$j::dispatch();
}
});

Send through object reference from Laravel Job (simple OOP?)

I'm trying to pass a newed up Model Object as a reference from a Laravel Job to the called Object and method. I need to new up the particular model in the Job because I need it in the Job's failed() method to update that model (and persist to database) as it functions as a log.
class ScrapeJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $store;
protected $scrape;
public function __construct(Store $store)
{
$this->store = $store;
}
public function handle()
{
$this->scrape = new \App\Scrape; // This is the log object.
// Here I call the SuperStoreScraper
$class = '\App\Scraper\\' . $this->store->scraper;
(new $class($this->store, $this->scrape))->scrape();
}
public function failed(\Exception $e)
{
// Update the Scrape object and persist to database.
$data = $this->scrape->data; // here I get the error... ->data is not found?
$data['exception'] = [ // this should work since I'm casting data to an array in the Model class.
'message' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine()
];
$this->scrape->data = $data;
$this->scrape->status = 'failed';
$this->scrape->save();
}
}
class SuperStoreScraper extends Scraper
{
public function __construct(Store $store, Scrape &$scrape) {
parent::__construct($store, $scrape);
}
public function scrape() {
$this->start();
}
}
abstract class Scraper
{
protected $store;
protected $scrape;
public function __construct(Store $store, Scrape &$scrape) {
$this->store = $store;
$this->scrape = &$scrape;
}
protected function start()
{
$this->scrape->fill([
'data' => [
'store' => $this->store->name,
'scraper' => $this->store->scraper
],
'status' => 'scraping'
])->save();
}
}
Everything seems to work fine. The newed up object is passed to both the SuperStoreScraper and the parent Scraper class (through the constructor), BUT when I persist it to the database in the Scraper object start() method, it doesn't reflect that up to the ScrapeJob (which newed up the Scrape object) and then I get an error in the Job's failed() method when trying to update the persisted object.
ErrorException: Trying to get property of non-object in ...\app\Jobs\ScrapeJob.php:54
What am I missing here?
I think I have kind of solved my issue now.
I've moved the \App\Scrape instantiation to the Job's __constructor, but also persist it to the database, like this:
protected $scrape;
public function __construct(Store $store)
{
$this->store = $store;
$this->scrape = \App\Scrape::create(['status' => 'queued']);
}
This works, I can access the Scrape model from both the Scraper and the Job's failed() method, BUT, if I have multiple tries on the Job I'd like to create new Scrape instantiations for each try. Right now the same Scrape instance (same id) is just updated with each Job retry.

Laravel 5.3 Queue Job is not working

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

Mocking class parameter that returns a mock

I am new to unit testing and trying to test a controller method in Laravel 5.1 and Mockery.
I am trying to test a registerEmail method I wrote, below:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Response;
use Mailchimp;
use Validator;
/**
* Class ApiController
* #package App\Http\Controllers
*/
class ApiController extends Controller
{
protected $mailchimpListId = null;
protected $mailchimp = null;
public function __construct(Mailchimp $mailchimp)
{
$this->mailchimp = $mailchimp;
$this->mailchimpListId = env('MAILCHIMP_LIST_ID');
}
/**
* #param Request $request
* #return \Illuminate\Http\JsonResponse
*/
public function registerEmail(Request $request)
{
$this->validate($request, [
'email' => 'required|email',
]);
$email = $request->get('email');
try {
$subscribed = $this->mailchimp->lists->subscribe($this->mailchimpListId, [ 'email' => $email ]);
//var_dump($subscribed);
} catch (\Mailchimp_List_AlreadySubscribed $e) {
return Response::json([ 'mailchimpListAlreadySubscribed' => $e->getMessage() ], 422);
} catch (\Mailchimp_Error $e) {
return Response::json([ 'mailchimpError' => $e->getMessage() ], 422);
}
return Response::json([ 'success' => true ]);
}
}
I am attempting to mock the Mailchimp object to work in this situation.
So far, my test looks as follows:
<?php
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class HomeRouteTest extends TestCase
{
use WithoutMiddleware;
public function testMailchimpReturnsDuplicate() {
$listMock = Mockery::mock('Mailchimp_Lists')
->shouldReceive('subscribe')
->once()
->andThrow(\Mailchimp_List_AlreadySubscribed::class);
$mailchimp = Mockery::mock('Mailchimp')->lists = $listMock;
$this->post('/api/register-email', ['email'=>'duplicate#email.com'])->assertJson(
'{"mailchimpListAlreadySubscribed": "duplicate#email.com is already subscribed to the list."}'
);
}
}
I have phpUnit returning a failed test.
HomeRouteTest::testMailchimpReturnsDuplicate
Mockery\Exception\InvalidCountException: Method subscribe() from Mockery_0_Mailchimp_Lists should be called exactly 1 times but called 0 times.
Also, if I assert the status code is 422, phpUnit reports it is receiving a status code 200.
It works fine when I test it manually, but I imagine I am overlooking something fairly easy.
I managed to solve it myself. I eventually moved the subscribe into a seperate Job class, and was able to test that be redefining the Mailchimp class in the test file.
class Mailchimp {
public $lists;
public function __construct($lists) {
$this->lists = $lists;
}
}
class Mailchimp_List_AlreadySubscribed extends Exception {}
And one test
public function testSubscribeToMailchimp() {
// create job
$subscriber = factory(App\Models\Subscriber::class)->create();
$job = new App\Jobs\SubscribeToList($subscriber);
// set up Mailchimp mock
$lists = Mockery::mock()
->shouldReceive('subscribe')
->once()
->andReturn(true)
->getMock();
$mailchimp = new Mailchimp($lists);
// handle job
$job->handle($mailchimp);
// subscriber should be marked subscribed
$this->assertTrue($subscriber->subscribed);
}
Mockery will expect the class being passed in to the controller be a mock object as you can see here in their docs:
class Temperature
{
public function __construct($service)
{
$this->_service = $service;
}
}
Unit Test
$service = m::mock('service');
$service->shouldReceive('readTemp')->times(3)->andReturn(10, 12, 14);
$temperature = new Temperature($service);
In laravel IoC it autoloads the classes and injects them, but since its not autoloading Mailchimp_Lists class it won't be a mock object. Mailchimp is requiring the class atop it's main class require_once 'Mailchimp/Lists.php';
Then Mailchimp is then loading the class automatically in the constructor
$this->lists = new Mailchimp_Lists($this);
I don't think you'll be able to mock that class very easily out of the box. Since there isn't away to pass in the mock object to Mailchimp class and have it replace the instance of the real Mailchimp_Lists
I see you are trying to overwrite the lists member variable with a new Mock before you call the controller. Are you certain that the lists object is being replaced with you mocked one? Try seeing what the classes are in the controller when it gets loaded and see if it is in fact getting overridden.

Laravel. Pass parameters to an event subscriber

Im developing a task system and i want to mark as viewed a task and do other things when a user request the task
I wrote a Task Event Subscriber like this
<?php namespace Athena\Events;
class TaskEventSubscriber {
public function onCreate($event)
{
// Here we can send a lot of emails
}
public function onUpdate($event)
{
\Log::info('This is some useful information.');
}
public function onShow($event)
{
\Log::info('The view event is now triggerd ');
}
public function subscribe($events)
{
$events->listen('user.create', 'Athena\Events\TaskEventSubscriber#onCreate');
$events->listen('user.update', 'Athena\Events\TaskEventSubscriber#onUpdate');
$events->listen('task.show', 'Athena\Events\TaskEventSubscriber#onShow');
}
}
And my controller I fire the event like this:
public function show($id)
{
$canView = $this->canView($id);
if($canView !== true)
{
return $canView;
}
$task = $this->task->byId($id);
// We fire the showed event
$this->events->fire('task.show', $this->task);
return View::make('tasks.show')
->with('task', $task);
}
But i dont know how can I or I should catch a parameter to be used inside of the event
By the way my Task Event subscriber is register on this Service Provider
class AthenaServiceProvider extends ServiceProvider {
public function register()
{
// A lot of stuffs
}
public function boot()
{
\Event::subscribe('Athena\Events\UserEventSubscriber');
\Event::subscribe('Athena\Events\TaskEventSubscriber');
}
}
If you need more information just let me know, Thnks in advance
You will need to declare an Event object like this
<?php namespace App\Events;
use App\Events\Event;
use Illuminate\Queue\SerializesModels;
class TriggerShowTask extends Event {
use SerializesModels;
public $task;
/**
* Create a new event instance.
*
* #return void
*/
public function __construct($task)
{
$this->task = $task;
}
}
When you trigger the event with
\Event::fire(TriggerShowTask, $this->task);
The $taskobject will be passed to the event
You can then access it in the subscriber with
public function onShow($event)
{
$event->task; // A Task object
\Log::info('The view event is now triggerd ');
}
I dont know how to feel already...
I am passing the values what i need to my event there are on $event variable of each function
Note: if you need to send more than one value just put them into an array and set your event function to catch them like parameters

Categories