I am trying to learn laravel database queue from its official documentation. I have done configuration as given in documentation .
Here my jobs :
<?php
namespace App\Jobs;
use App\SearchLog;
use App\Jobs\Job;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Bus\SelfHandling;
use Illuminate\Contracts\Queue\ShouldQueue;
class SendTicket extends Job implements SelfHandling, ShouldQueue
{
use InteractsWithQueue, SerializesModels;
protected $log;
protected $searchLog = array();
public function __construct(SearchLog $log , $data = array())
{
$this->log = $log;
$this->searchLog = $data;
}
/**
* Execute the job.
*
* #return void
*/
public function handle()
{
$this->log->create($this->searchLog);
}
In my controller I call like this
public function store(Request $request)
{
$searchLog = array();
// searchLog contains the data to be inserted into database
$log = new SearchLog();
$this->dispatch(new SendTicket($log , $searchLog));
}
When I run the php artisan queue:listen I get the error like
[Illuminate\Database\Eloquent\ModelNotFoundException] No query
results for model [App\SearchLog].
But when I edit the jobs like this
//only edited code
public function __construct($data = array())
{
$this->searchLog = $data;
}
/**
* Execute the job.
*
* #return void
*/
public function handle()
{
SearchLog::create($this->searchLog);
}
And when call from like this
public function store(Request $request)
{
$searchLog = array();
// searchLog contains the data to be inserted into database
$this->dispatch(new SendTicket($searchLog));
}
It works fine and the data is inserted .
Here, my question is :
How to send the object to the queue ?
What is best way to send data to queue so that we can process ?
The problem is in the use Illuminate\Queue\SerializesModels;, which tries to serialize the SearchLog $log param which you're passing to the constructor. Since your model is not saved yet, it doesn't have the identifier.
According to Laravel documentation:
If your queued job accepts an Eloquent model in its constructor, only
the identifier for the model will be serialized onto the queue. When
the job is actually handled, the queue system will automatically
re-retrieve the full model instance from the database.
The workaround would be to remove SerializesModels trait from the Job class you're willing to save your model in.
I ran into the same issue.
It seems if you want to push an Model to the queue only its ID will be saved in the payload. So if you haven't saved the Model yet, it wont be available in the handler.
After all I found it in the documentation in the section Queues and Eloquent Models.
To solve this issue I see two solutions, the first is to make sure you have a persistent Model:
public function store(Request $request)
{
$searchLog = array();
// searchLog contains the data to be inserted into database
$log = new SearchLog();
//Save the Searchlog beforehand if it doesn't have any data constraints
$log->save();
//Dispatch the Job with the saved Model
$this->dispatch(new SendTicket($log , $searchLog));
}
or put the full Model Saving process to the handler only, like you did in your example.
//Full saving/initializing is happening here
public function handle()
{
SearchLog::create($this->searchLog);
}
In my case I had to save the Model to a database in a high performance process, so i put it fully in the handler that my process wasn't dependent on the db saving speed. So i would put it in the handle() function.
This issue is usually raised if the Model is not saved or persisted yet. Check your model instance and persistance. Another thing may raise this issue is Multi-Tenancy. So if you are using or building a Multi Tenant application with Multi Databases, you may need to figure out a way to pass the database connection to the queue or just use arrays of data as stated by Tylor Otwell here [5.4] Support queued jobs with unpersisted models
Related
I have a Laravel job that is supposed to accept a newly created Eloquent model as a constructor attribute, then perform a save() operation on it in the handle() method. However, as soon as I try saving the model to the private property, the handle() method is not executed anymore. Here's the code:
MyController.php:
$category = new Category;
SaveModelJob::dispatch($category);
SaveModelJob.php
<?php
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;
class SaveModelJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
private $model;
/**
* Create a new job instance.
*
* #return void
*/
public function __construct($model)
{
//
dump('saving as property'); // this gets executed
$this->model = $model; // <--- PROBLEMATIC CODE
dump('saved as property'); // this also gets executed
}
/**
* Execute the job.
*
* #return void
*/
public function handle()
{
dump("handling"); // <--- This stops being executed as soon as the PROBLEMATIC CODE above is added
// a $this->model->save() operations is intended here
}
}
In my real controller, some Model attributes are set before passing the $category to the SaveModelJob, but for simplicity's sake, I've removed most of the irrelevant code. To get some of the possible issues out of the way:
The Category model is a simple Eloquent model
I have QUEUE_CONNECTION=sync set in my .env and the handle() method actually gets executed if the problematic code is not there, so I don't think the problem is directly related to the queue
If I pass an existing Category object from the database instead of newly created (but not saved) one, the handle() method is reached.
I am using Laravel 8.34.
I have a suspicion that the Job object does not act like a regular class and the handle() method is reached much later, outside of the initial request lifecycle and this might have implications in the way the private property is accessed at a later time (especially when the queue driver is not "sync", but say, Redis), but I get no such errors in the log.
Do you know what the issue can be?
When you dispatch an object to the queue, the object will be saved into the queue, but if you’re passing an Eloquent Model, it will be referenced from the Database by its id (or primary key) instead of being saved entirely into the payload
To avoid that using an unsavec model, just send it as an array.
Controller
$category = new Category;
SaveModelJob::dispatch($category->toArray()); //or build the array from scratch
SaveModelJob
public function __construct($model)
{
//
dump('saving as property');
$this->model = Category::make($model);
dump('saved as property');
}
If you use the same script for all your models, add another attribute for the class
Controller
$category = new Category;
SaveModelJob::dispatch($category->toArray(), Category::class); //or build the array from scratch
SaveModelJob
public function __construct($model, $eloquentClass)
{
//
dump('saving as property');
$this->model = $eloquentClass::make($model);
dump('saved as property');
}
I don't see you have saved your model. Please create or save your model, then send it to job:
$category->save();
Also to make sure correct model is passed to job, it's better to inject model:
public function __construct(Category $category)
{
$this->model= $category;
}
But it still works if you don't inject model class.
I have an observer for my User model. Inside my observer->created event i have some code.
public function created(User $user)
{
sendEmail();
}
So, the idea is, when a user is created, the system will send for the user email notification that the account was created.
Question: When the database is seeding, it also calls this method 'created' and sends users (that are in the seeds) email notification.
So, my question is, how can i check, probably inside this 'created' method if at the moment laravel is seeding data -> do not send email of do not run the 'created' observer method.
Tried to google, found something, but not working correct.
Something like YourModel::flushEventListeners();
You can use YourModel::unsetEventDispatcher(); to remove the event listeners for a model temporary.
If you need them after seeding in the same execution, you can read the dispatchers, unset them and then set them again.
$dispatcher = YourModel::getEventDispatcher();
// Remove Dispatcher
YourModel::unsetEventDispatcher();
// do stuff here
// Re-add Dispatcher
YourModel::setEventDispatcher($dispatcher);
namespace Database\Seeders;
use App\Models\Blog;
use Illuminate\Database\Seeder;
class BlogsTableSeeder extends Seeder
{
public function run()
{
Blog::withoutEvents(function () {
// normally
Blog::factory()
->times(10)
->hasUploads(1) //hasOne
->hasComments(2) //hasMany
->create();
});
}
}
You may mute event with WithoutModelEvents trait
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
class SomeSeeder extends Seeder
{
use WithoutModelEvents;
public function run()
{
User::factory( 30 )->create();
}
}
or you may try createQuietly method of a factory, for example
class SomeSeeder extends Seeder
{
public function run()
{
User::factory( 30 )->createQuietly();
}
}
You could use the saveQuietly() function https://laravel.com/docs/8.x/eloquent#saving-a-single-model-without-events
This allows you to disable all events for a single model.
If you wanna disable a single event for a single model, read about it here: http://derekmd.com/2019/02/conditionally-suppressing-laravel-event-listeners/
I would like to hear your tips or solutions to this problem. Even more helpful would be links to some working!! open source projects which implements some of these mechanisms.
For example i want to synchronize all github's gist with Laravel model. I think about two form.
Option 1
Write separate classes for example Importer and call it's method from controllers and commands and interact with Model.
Option 2
Put data extracting logic (api calls) inside Laravel's model hierarchy (traits,interfaces and so) and within artisan's command let's say synchronize:all loop through all model and check for any changes and update each row.
example
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Sample\Http\Client;
class Gist extends Model
{
/**
* Example of fetching new gists
*/
public static function getNew($gistId = '6cad326836d38bd3a7ae')
{
$resposne = Client::request('GET', 'https://api.github.com/gists/'.$gistId)
->response()
->json();
$Gist = new static($response);
$Gist->save();
return $Gist;
}
/**
* Get list of all gist from external source and ... who knows
*/
public function listAllGistFromApi()
{
$resposne = Client::request('GET', 'https://api.github.com/gists/')
->response()
->json();
return $response;
}
/**
* Example of updating existing gist
*/
public function updateFromApi()
{
$gistId = $this->id; // primary key also api uid
$resposne = Client::request('GET', 'https://api.github.com/gists/'.$gistId)
->response()
->json();
return $this->setAttributes($response)->save(); // simplified logic
}
}
Your solutions...
You can use observers, what it does? it keeps an eye on the model changes, there you can perform your Apis to the out data source, take a look here how to apply observes in Laravel
I have a job in my Laravel project (v.4.2). which is used for inserting data into database. The class named "ProductUpdate". I use Amazon SQS for queue service.
What makes me confuse now is, when I changed the code in class "ProductUpdate",
it seems that the job is running by using old version of the class.
I even deleted all lines of code in the class but the jobs can still be able to run ( it stills inserts data).
Following is the job class.
The file of this class is at app/jobs/ProductUpdate.php
In my understanding, job class is the only place that will be called from queue, but why it can still be able to run when I deleted all the codes?
<?php
/**
* Here is a class to run a queued item sent from SQS
* Default method to use is fire()
**/
class ProductUpdate
{
public function fire($job, $data)
{
// Disable query log
DB::connection()->disableQueryLog();
// Set the job as a var so it will be used across functions
$this->job = $job;
$product = Product::find($productID);
if($product->product_type != 18) {
// Call the updater from library
$updater = App::make('Product\PriceUpdater');
$updater->update($product);
}
// Done and delete
$this->success();
}
private function success()
{
$this->job->delete();
}
private function fail($messages = array())
{
Log::error('Job processing fail', $messages);
$this->job->delete();
}
}
Your problem is related to cache.
Run this command in terminal to remove all cached data.
php artisan cache:clear
other way:-
Illuminate\Cache\FileStore has the function flush, so you can also use it:
Cache::flush();
This link will also help you :)
I am building a notification system at the moment, and the notifications get delivered via model events. Some of the notifications are dependent on things happening with the models relationships.
Example: I have a project model that has a one:many relationship with users,
public function projectmanager() {
return $this->belongsToMany('User', 'project_managers');
}
I am wanting to monitor for changes on this relationship in my project model events. At the moment, I am doing this by doing this following,
$dirtyAttributes = $project->getDirty();
foreach($dirtyAttributes as $attribute => $value) {
//Do something
}
This is run in the ::updating event of the model but only looks at the models attributes and not any of it's relational data, is it possible get old relational data and new relational data to compare and process?
You should be using an observer class for this.
This has already been covered fairly simply and well by this SO answer, although that answer uses a slightly older method where the class itself needs to call upon its observer. The documentation for the current version (5.3 as of this answer) recommends registering the observer in your application service provider, which in your example would look similar to:
<?php
namespace App\Providers;
use App\Project;
use App\Observers\ProjectObserver;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
Project::observe(ProjectObserver::class);
}
}
For evaluating the differences between the new model values and the old values still in the relational DB, Laravel provides methods for both: getDirty() and getOriginal().
So your observer would look something like:
<?php
namespace App\Observers;
use App\Project;
class ProjectObserver
{
/**
* Listen to the Project updating event.
*
* #param Project $project
* #return void
*/
public function updating(Project $project)
{
$dirtyAttributes = $project->getDirty();
$originalAttributes = $project->getOriginal();
// loop over one and compare/process against the other
foreach ($dirtyAttributes as $attribute => $value) {
// do something with the equivalent entry in $originalAttributes
}
}
}