I'm trying to automate a process composed from multiple jobs. Every job had a goal, so I'm running a global job who is responsible for creating child jobs.
- Job-1
Global Job -> - Job-2
- Job-3
- Job-4
I want to run them one after another, for know the queue process run all the four jobs in the same time and this is what I don't want.
Is it about configration ? otherwise what it the prefect way to handle this kind of needs
This is what y Global job class looks like
<?php
namespace App\Jobs;
use App\Models\Objects;
use App\Helpers\UtilsHelper;
use Illuminate\Support\Facades\Log;
use App\Services\JobsLimitControler;
/**
* This job will have the responsability to create other ones
*/
class AutoImport extends Job
{
/**
* Job key
* #var string $key
*/
public $key;
/**
* Job type
* #var string $jobType
*/
public $type = 'auto-import';
/**
* Job type
* #var string $jobType
*/
public $title = 'Automatic import';
/**
* Job params array
* #var array $params
*/
public $params;
/**
* Location
* #var Objects $object
*/
public Locations $object;
/**
* The number of seconds after which the job's unique lock will be released.
*
* #var int
*/
public $uniqueFor = 600;
/**
* The unique ID of the job.
*
* #return string
*/
public function uniqueId()
{
return $this->key;
}
/**
* Create a new job instance.
* #param string $id Job id
* #param Objects $object
*/
public function __construct(string $id,Objects $object)
{
# Job key attribute
$this->key = $id;
# Preparing job to be trackable
$this->prepareStatus(['key' => $id]);
# Location instance
$this->object = $object;
}
/**
* Execute the job
*/
public function handle()
{
try{
# First we verify if the object is well setted
if($this->object){
$this->callJobOne();
$this->callJobTwo();
$this->callJobThree();
$this->callJobFour();
}else{
Log::channel('auto-import')->info("some log");
}
}catch(\Exception $e){
# Mark the job as failed
$this->fail($e);
# We throw the error to be intercepted by the job tracker
throw $e;
}
}
}
You can use Laravel's Job Batches for grouping purpose.
https://laravel.com/docs/8.x/queues#job-batching
And Job Chaining to progress sequentially.
https://laravel.com/docs/8.x/queues#job-chaining
You may define a set of chained jobs within a batch by placing the chained jobs within an array
https://laravel.com/docs/8.x/queues#chains-within-batches
Related
I need to functionally test the a subscriber in Symfony 4 and I'm having problems finding how. The Subscriber has the following structure
/**
* Class ItemSubscriber
*/
class ItemSubscriber implements EventSubscriberInterface
{
/**
* #var CommandBus
*/
protected $commandBus;
/**
* Subscriber constructor.
*
* #param CommandBus $commandBus
*/
public function __construct(CommandBus $commandBus)
{
$this->commandBus = $commandBus;
}
/**
* {#inheritdoc}
*/
public static function getSubscribedEvents()
{
return [
CommandFailedEvent::NAME => 'onCommandFailedEvent',
];
}
/**
* #param CommandFailedEvent $event
*
* #throws Exception
*/
public function onCommandFailedEvent(CommandFailedEvent $event)
{
$item = $event->getItem();
$this->processFailed($item);
}
/**
* Sends message
*
* #param array $item
*
* #throws Exception
*/
private function processFailed(array $item)
{
$this->commandBus->handle(new UpdateCommand($item));
}
}
The flow of the subscriber is receiving an internal event and send a message by rabbit through the command bus to another project.
How can I test that dispatching the event CommandFailedEvent the line in processFailed(array $item) is executed?
Does anyone has documentation on best practices to test Events and Subscribers in Symfony 4?
If you want to test the process of a command bus handler being call you could test dependency method calls thanks to mock expects. You have some examples in the PHPUnit documentation.
For instance, you would have something like:
$commandBus = $this->getMockBuilder(CommandBus::class)->disableOriginalConstructor()->getMock();
$commandBus->expects($this->once())->method('handle');
// Create your System Under Test
$SUT = new CommandFailedSubscriber($commandBus);
// Create event
$item = $this->getMockBuilder(YourItem::class)->getMock();
$event = new CommandFailedEvent($item);
// Dispatch your event
$dispatcher = new EventDispatcher();
$dispatcher->addSubscriber($SUT);
$dispatcher->dispatch($event);
I hope this would be enough for you to explore possibilities and to have the coverage needed for your feature.
Have a nice time testing!
I'm building an API with Laravel and want to send push notification using the Laravel Notifications system. I've a model for matches (which is basically a post), another user can like this match. When the match is liked, the creator of the post will get a push notification. It's just like Instagram, Facebook, etc.
Often the push notification wasn't send to the user. I installed Laravel Horizon to see if there where errors. Sometimes the notification was send and sometimes it wasn't. With the exact same data:
The notification fails sometimes with the exact same data (same user, same match).
The error is as followed:
Illuminate\Database\Eloquent\ModelNotFoundException: No query results
for model [App\Models\Match] 118 in
/home/forge/owowgolf.com/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Builder.php:312
I'm sure the match and the user exists in the database, I've verified that before sending the notification. Does anybody know what's going wrong? Everything I could find online is that people didn't save their model before sending the notification into the queue. But the line where the code send's the notification into the queue wouldn't even be reached if the model didn't exists. Because of Implicit Binding in the route/controller.
Controller method:
/**
* Like a match.
*
* #param \App\Models\Match $match
* #return \Illuminate\Http\JsonResponse
*/
public function show(Match $match)
{
$match->like();
$players = $match->players()->where('user_id', '!=', currentUser()->id)->get();
foreach ($players as $user) {
$user->notify(new NewLikeOnPost($match, currentUser()));
}
return ok();
}
Notification:
<?php
namespace App\Notifications;
use App\Models\Match;
use App\Models\User;
use Illuminate\Bus\Queueable;
use NotificationChannels\Apn\ApnChannel;
use NotificationChannels\Apn\ApnMessage;
use Illuminate\Notifications\Notification;
use Illuminate\Contracts\Queue\ShouldQueue;
class NewLikeOnPost extends Notification implements ShouldQueue
{
use Queueable;
/**
* The match instance.
*
* #var \App\Models\Match
*/
private $match;
/**
* The user instance.
*
* #var \App\Models\User
*/
private $user;
/**
* Create a new notification instance.
*
* #param \App\Models\Match $match
* #param \App\Models\User $user
*/
public function __construct(Match $match, User $user)
{
$this->user = $user;
$this->match = $match;
$this->onQueue('high');
}
/**
* Get the notification's delivery channels.
*
* #param \App\Models\User $notifiable
* #return array
*/
public function via($notifiable)
{
if ($notifiable->wantsPushNotification($this)) {
return ['database', ApnChannel::class];
}
return ['database'];
}
/**
* Get the mail representation of the notification.
*
* #param \App\Models\User $notifiable
* #return \NotificationChannels\Apn\ApnMessage
*/
public function toApn($notifiable)
{
return ApnMessage::create()
->badge($notifiable->unreadNotifications()->count())
->sound('success')
->body($this->user->username . ' flagged your match.');
}
/**
* Get the array representation of the notification.
*
* #param mixed $notifiable
* #return array
*/
public function toArray($notifiable)
{
return [
'user_id' => $this->user->id,
'body' => "<flag>Flagged</flag> your match.",
'link' => route('matches.show', $this->match),
'match_id' => $this->match->id,
];
}
/**
* Get the match attribute.
*
* #return \App\Models\Match
*/
public function getMatch()
{
return $this->match;
}
}
This is not a complete solution, but it will lower your chances of running into this error in the future.
Instead of passing in the whole Match model into the job, only pass the id of the model. You can then fetch that model in the constructor.
/**
* Like a match.
*
* #param \App\Models\Match $match
* #return \Illuminate\Http\JsonResponse
*/
public function show(Match $match)
{
$match->like();
$players = $match->players()->where('user_id', '!=', currentUser()->id)->get();
foreach ($players as $user) {
$user->notify(new NewLikeOnPost($match->id, currentUser()->id));
}
return ok();
}
Notification:
class NewLikeOnPost extends Notification implements ShouldQueue
{
use Queueable;
private const QUEUE_NAME = 'high';
/**
* The match instance.
*
* #var \App\Models\Match
*/
private $match;
/**
* The user instance.
*
* #var \App\Models\User
*/
private $user;
/**
* Create a new notification instance.
*
* #param int $match
* #param int $user
*/
public function __construct(int $matchId, int $userId)
{
$this->user = User::query()->where('id', $userId)->firstOrFail();
$this->match = Match::query()->where('id', $matchId)->firstOrFail();
$this->onQueue(self::QUEUE_NAME);
}
// Rest of the class is still the same...
}
You can use the SerializesModels trait, but it doesn't work well when you add a delay to a queued job. This is because it will try to reload the model on __wakeup() and sometimes it cannot find the class.
Hopefully this helps :)
Its probably because $user is not an object of User model, its an object of Match model. You need to do a User::findorfail or User::firstOrFail then notify the user.
public function show(Match $match)
{
$match->like();
$players = $match->players()->where('user_id', '!=', currentUser()->id)->get();
foreach ($players as $user) {
$someUser = User::findOrFail($user->user_id);
$someUser->notify(new NewLikeOnPost($match, currentUser()));
}
return ok();
}
Unless the notify trait is used in Match model. Or you could use eager loading which will cost way less queries!
Check your .env to be sure that u really use REDIS
BROADCAST_DRIVER=redis
CACHE_DRIVER=redis
SESSION_DRIVER=redis
SESSION_LIFETIME=120
QUEUE_DRIVER=redis
then clear cache ( php artisan cache:clear , php artisan view:clear ), that should clear the issue
EDIT
I had similar problems but now I use Docker only and before I had to check for cached configfiles, wrong file/folderpermissions and so on (REDIS for broadcast only, others were standard). I started using redis only - that`s a lot easier, faster and more debugfriendly for me ! And together with Docker really helpful to not use messed up nginx/apache/php/redis/ ...
So I'm using the Laravel model event observerables to fire custom event logic, but they only accept the model as a single argument. What I'd like to do is call a custom event that I can also pass some extra arguments to that would in turn get passed to the Observer method. Something like this:
$this->fireModelEvent('applied', $user, $type);
And then in the Observer
/**
* Listen to the applied event.
*
* #param Item $item
* #param User $user
* #param string $type
* #return void
*/
public function applied(Item $item, $user, string $type) {
Event::fire(new Applied($video, $user, $type));
}
As you can see i'm interested in passing a user that performed this action, which is not the one that necessarily created the item. I don't think temporary model attributes are the answer because my additional event logic gets queued off as jobs to keep response time as low as possible. Anyone have any ideas on how I could extend Laravel to let me do this?
My theory would be to do a custom trait that overrides one or more functions in the base laravel model class that handles this logic. Thought I'd see if anyone else has needed to do this while I look into it.
Also here's the docs reference
I've accomplished this task by implementing some custom model functionality using a trait.
/**
* Stores event key data
*
* #var array
*/
public $eventData = [];
/**
* Fire the given event for the model.
*
* #param string $event
* #param bool $halt
* #param array $data
* #return mixed
*/
protected function fireModelEvent($event, $halt = true, array $data = []) {
$this->eventData[$event] = $data;
return parent::fireModelEvent($event, $halt);
}
/**
* Get the event data by event
*
* #param string $event
* #return array|NULL
*/
public function getEventData(string $event) {
if (array_key_exists($event, $this->eventData)) {
return $this->eventData[$event];
}
return NULL;
}
I have two models with many to many relationship artists and songs.
then there are fields in my artists table called weekhits and week_date,
i want to increment the value of week-hits when ever a specific artist page is visited by the user
so made event listners
class ArtistEvent
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $artist;
/**
* Create a new event instance.
*
* #return void
*/
public function __construct(artist $artist)
{
//
$this->artist = $artist;
}
/**
* Get the channels the event should broadcast on.
*
* #return Channel|array
*/
public function broadcastOn()
{
return new PrivateChannel('channel-name');
}
}
ant this is the listener
class ArtistViewed
{
/**
* Create the event listener.
*
* #return void
*/
public function __construct()
{
//
}
/**
* Handle the event.
*
* #param ArtistEvent $event
* #return void
*/
public function handle(ArtistEvent $event)
{
$event->artist->increment('week_hits');
}
}
and here is where i fired the listener.
public function artist($id,$slug){
$artist = Artist::where('id', $id)->where('slug', $slug)->first();
Event::fire(new ArtistViewed($artist));
return view('front.artist', compact('artist'));
}
but this code isnot incrementing week-hits field in my artists table..
plz help. iam in the middle of learning laravel.
I have already added an answer that suggests using a job for this, but if you want to go with the existing event/listener setup then just fix the call to Event::fire() to actually fire your event, not your listener.
Event::fire(new ArtistEvent($artist))
Also ensure that the event and listener are registered in your EventServiceProvider's $listen array.
\App\Events\ArtistEvent::class => [
\App\Listeners\ArtistViewed::class
]
I think you're confusing events and listeners - you're firing off the listener to Event::fire() and you pass the $artist into the constructor of the listener, but instead try and take it off the $event.
Keep in mind if your queue driver isn't sync then you will actually need to run your queue from the command line, otherwise these jobs will never be run.
Below is an example of how you might do this with a job that you dispatch. You could fire this off by calling dispatch(new RecordArtistView($artist)).
<?php
namespace App\Jobs;
use App\Artist;
use App\Jobs\Job;
use Illuminate\Contracts\Queue\ShouldQueue;
class RecordArtistView extends Job implements ShouldQueue
{
protected $artist;
/**
* Create a new job instance.
*
* #param \App\Artist $artist
* #return void
*/
public function __construct(Artist $artist)
{
$this->artist = $artist;
}
/**
* Execute the job.
*
* #return void
*/
public function handle()
{
$this->artist->increment('week_hits');
}
}
I am doing ERP project in cloud base & I am using one source for multiple customers and using different database.
I want to run script every day at 00:00 with different time-zone
Here what i tried......
This is my Kernel class
protected function schedule(Schedule $schedule)
{
$schedule->command('attendance:null')->hourly();
}
This is my attendance:null command class
class AttendanceInsertNull extends Command{
protected $signature = 'attendance:null';
/**
* The console command description.
*
* #var string
*/
protected $description = 'Command description';
/**
* Create a new command instance.
*
* #return \App\Console\Commands\AttendanceInsertNull
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* #return mixed
*/
public function handle()
{
$locations = Location::get();
foreach ($locations as $location) {
$schedule1 = new Schedule();
$schedule1->call(function(){
$timeZone = $location->timezone;
$this->info('RUN');
})->at('00:00')->timezone($timeZone);
}
}
}
What i am doing is, Check with different timezone in location loop.
please anyone can help to solve this issue for me.