Add trait for Queue event handling
This commit is contained in:
parent
687ec80069
commit
c260fcb4e4
|
@ -7,6 +7,8 @@ use Illuminate\Broadcasting\InteractsWithSockets;
|
|||
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Ramsey\Uuid\Lazy\LazyUuidFromString;
|
||||
use Ramsey\Uuid\UuidInterface;
|
||||
|
||||
class JobEvent implements ShouldBroadcastNow
|
||||
{
|
||||
|
@ -15,13 +17,13 @@ class JobEvent implements ShouldBroadcastNow
|
|||
public bool $reload = false;
|
||||
public string $message = '';
|
||||
|
||||
final private function __construct(public string $channel)
|
||||
final private function __construct(public string $channel, public UuidInterface $jobId)
|
||||
{
|
||||
}
|
||||
|
||||
public static function on(string $channel): static
|
||||
public static function on(string $channel, UuidInterface $jobId): static
|
||||
{
|
||||
return new static($channel);
|
||||
return new static($channel, $jobId);
|
||||
}
|
||||
|
||||
public function withMessage(string $message): static
|
||||
|
@ -31,14 +33,22 @@ class JobEvent implements ShouldBroadcastNow
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function dispatch(): void
|
||||
{
|
||||
event($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the channels the event should broadcast on.
|
||||
*
|
||||
* @return \Illuminate\Broadcasting\Channel
|
||||
* @return array<int, Channel>
|
||||
*/
|
||||
public function broadcastOn()
|
||||
{
|
||||
return new Channel($this->channel);
|
||||
return [
|
||||
new Channel($this->channel),
|
||||
new Channel('jobs'),
|
||||
];
|
||||
}
|
||||
|
||||
public function shouldReload(): static
|
||||
|
|
|
@ -7,64 +7,72 @@ use App\Lib\Events\JobFinished;
|
|||
use App\Lib\Events\JobStarted;
|
||||
use Closure;
|
||||
use Lorisleiva\Actions\Decorators\JobDecorator;
|
||||
use Ramsey\Uuid\Lazy\LazyUuidFromString;
|
||||
use Ramsey\Uuid\UuidInterface;
|
||||
use Throwable;
|
||||
|
||||
class WithJobState
|
||||
{
|
||||
|
||||
public JobStarted $beforeMessage;
|
||||
public JobFinished $afterMessage;
|
||||
public JobFailed $failedMessage;
|
||||
public ?JobStarted $beforeMessage = null;
|
||||
public ?JobFinished $afterMessage = null;
|
||||
public ?JobFailed $failedMessage = null;
|
||||
|
||||
private function __construct(public string $channel)
|
||||
private function __construct(public string $channel, public UuidInterface $jobId)
|
||||
{
|
||||
}
|
||||
|
||||
public static function make(string $channel): self
|
||||
public static function make(string $channel, UuidInterface $jobId): self
|
||||
{
|
||||
return new self($channel);
|
||||
return new self($channel, $jobId);
|
||||
}
|
||||
|
||||
public function before(string $message): self
|
||||
{
|
||||
$this->beforeMessage = JobStarted::on($this->channel)->withMessage($message);
|
||||
$this->beforeMessage = JobStarted::on($this->channel, $this->jobId)->withMessage($message);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function after(string $message): self
|
||||
{
|
||||
$this->afterMessage = JobFinished::on($this->channel)->withMessage($message);
|
||||
$this->afterMessage = JobFinished::on($this->channel, $this->jobId)->withMessage($message);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function failed(string $message): self
|
||||
{
|
||||
$this->failedMessage = JobFailed::on($this->channel)->withMessage($message);
|
||||
$this->failedMessage = JobFailed::on($this->channel, $this->jobId)->withMessage($message);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function shouldReload(): self
|
||||
{
|
||||
$this->afterMessage->shouldReload();
|
||||
$this->failedMessage->shouldReload();
|
||||
$this->afterMessage?->shouldReload();
|
||||
$this->failedMessage?->shouldReload();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function handle(JobDecorator $job, Closure $next): void
|
||||
{
|
||||
event($this->beforeMessage);
|
||||
if ($this->beforeMessage) {
|
||||
event($this->beforeMessage);
|
||||
}
|
||||
|
||||
try {
|
||||
$next($job);
|
||||
} catch (Throwable $e) {
|
||||
event($this->failedMessage);
|
||||
if ($this->failedMessage) {
|
||||
event($this->failedMessage);
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
|
||||
event($this->afterMessage);
|
||||
if ($this->afterMessage) {
|
||||
event($this->afterMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
namespace App\Lib\Queue;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
use App\Lib\JobMiddleware\WithJobState;
|
||||
|
||||
trait TracksJob
|
||||
{
|
||||
abstract public function jobState(WithJobState $jobState, ...$parameters): WithJobState;
|
||||
abstract public function jobChannel(): string;
|
||||
|
||||
/**
|
||||
* @param mixed $parameters
|
||||
*/
|
||||
public function startJob(...$parameters): void
|
||||
{
|
||||
$jobId = Str::uuid();
|
||||
$jobState = WithJobState::make($this->jobChannel(), $jobId);
|
||||
$this->jobState(...[$jobState, ...$parameters])->beforeMessage->dispatch();
|
||||
$parameters[] = $jobId;
|
||||
static::dispatch(...$parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $parameters
|
||||
*
|
||||
* @return array<int, object>
|
||||
*/
|
||||
public function getJobMiddleware(...$parameters): array
|
||||
{
|
||||
$jobId = array_pop($parameters);
|
||||
$jobState = WithJobState::make($this->jobChannel(), $jobId);
|
||||
$jobState = $this->jobState(...[$jobState, ...$parameters]);
|
||||
$jobState->beforeMessage = null;
|
||||
|
||||
return [
|
||||
$jobState
|
||||
];
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@
|
|||
namespace App\Member\Actions;
|
||||
|
||||
use App\Lib\JobMiddleware\WithJobState;
|
||||
use App\Lib\Queue\TracksJob;
|
||||
use App\Maildispatcher\Actions\ResyncAction;
|
||||
use App\Member\Member;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
|
@ -12,6 +13,7 @@ class MemberDeleteAction
|
|||
{
|
||||
|
||||
use AsAction;
|
||||
use TracksJob;
|
||||
|
||||
public function handle(int $memberId): void
|
||||
{
|
||||
|
@ -27,24 +29,27 @@ class MemberDeleteAction
|
|||
|
||||
public function asController(Member $member): RedirectResponse
|
||||
{
|
||||
static::dispatch($member->id);
|
||||
$this->startJob($member->id);
|
||||
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, object>
|
||||
* @param mixed $parameters
|
||||
*/
|
||||
public function getJobMiddleware(int $memberId): array
|
||||
public function jobState(WithJobState $jobState, ...$parameters): WithJobState
|
||||
{
|
||||
$member = Member::findOrFail($memberId);
|
||||
$member = Member::findOrFail($parameters[0]);
|
||||
|
||||
return [
|
||||
WithJobState::make('member')
|
||||
->before('Lösche Mitglied ' . $member->fullname)
|
||||
->after('Mitglied ' . $member->fullname . ' gelöscht')
|
||||
->failed('Löschen von ' . $member->fullname . ' fehlgeschlagen.')
|
||||
->shouldReload(),
|
||||
];
|
||||
return $jobState
|
||||
->before('Mitglied ' . $member->fullname . ' wird gelöscht')
|
||||
->after('Mitglied ' . $member->fullname . ' gelöscht')
|
||||
->failed('Löschen von ' . $member->fullname . ' fehlgeschlagen.')
|
||||
->shouldReload();
|
||||
}
|
||||
|
||||
public function jobChannel(): string
|
||||
{
|
||||
return 'member';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,7 +55,7 @@ return [
|
|||
|
||||
'prefix' => env(
|
||||
'HORIZON_PREFIX',
|
||||
Str::slug(env('APP_NAME', 'laravel'), '_').'_horizon:'
|
||||
Str::slug(env('APP_NAME', 'laravel'), '_') . '_horizon:'
|
||||
),
|
||||
|
||||
/*
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Tests\Feature\Member;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
use App\Course\Models\Course;
|
||||
use App\Course\Models\CourseMember;
|
||||
use App\Lib\Events\JobFailed;
|
||||
|
@ -21,6 +22,12 @@ class DeleteTest extends TestCase
|
|||
{
|
||||
use DatabaseTransactions;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
Event::fake([JobStarted::class, JobFinished::class, JobFailed::class]);
|
||||
}
|
||||
|
||||
public function testItFiresJob(): void
|
||||
{
|
||||
Queue::fake();
|
||||
|
@ -31,6 +38,7 @@ class DeleteTest extends TestCase
|
|||
|
||||
$response->assertRedirect('/member');
|
||||
|
||||
Event::assertDispatched(JobStarted::class);
|
||||
MemberDeleteAction::assertPushed(fn ($action, $parameters) => $parameters[0] === $member->id);
|
||||
}
|
||||
|
||||
|
@ -64,7 +72,7 @@ class DeleteTest extends TestCase
|
|||
|
||||
MemberDeleteAction::run($member->id);
|
||||
|
||||
$this->assertDatabaseCount('members', 0);
|
||||
$this->assertDatabaseMissing('members', ['id' => $member->id]);
|
||||
}
|
||||
|
||||
public function testItFiresEventWhenFinished(): void
|
||||
|
@ -72,11 +80,12 @@ class DeleteTest extends TestCase
|
|||
Event::fake([JobStarted::class, JobFinished::class]);
|
||||
$this->withoutExceptionHandling()->login()->loginNami();
|
||||
$member = Member::factory()->defaults()->create(['firstname' => 'Max', 'lastname' => 'Muster']);
|
||||
$uuid = Str::uuid();
|
||||
|
||||
MemberDeleteAction::dispatch($member->id);
|
||||
MemberDeleteAction::dispatch($member->id, $uuid);
|
||||
|
||||
Event::assertDispatched(JobStarted::class, fn ($event) => $event->broadcastOn()->name === 'member' && $event->message === 'Lösche Mitglied Max Muster' && $event->reload === false);
|
||||
Event::assertDispatched(JobFinished::class, fn ($event) => $event->message === 'Mitglied Max Muster gelöscht' && $event->reload === true);
|
||||
Event::assertNotDispatched(JobStarted::class);
|
||||
Event::assertDispatched(JobFinished::class, fn ($event) => $event->message === 'Mitglied Max Muster gelöscht' && $event->reload === true && $event->jobId->serialize() === $uuid->serialize());
|
||||
}
|
||||
|
||||
public function testItFiresEventWhenDeletingFailed(): void
|
||||
|
@ -85,14 +94,15 @@ class DeleteTest extends TestCase
|
|||
$this->login()->loginNami();
|
||||
$member = Member::factory()->defaults()->create(['firstname' => 'Max', 'lastname' => 'Muster']);
|
||||
MemberDeleteAction::partialMock()->shouldReceive('handle')->andThrow(new Exception('sorry'));
|
||||
$uuid = Str::uuid();
|
||||
|
||||
try {
|
||||
MemberDeleteAction::dispatch($member->id);
|
||||
MemberDeleteAction::dispatch($member->id, $uuid);
|
||||
} catch (Throwable) {
|
||||
}
|
||||
|
||||
Event::assertDispatched(JobStarted::class, fn ($event) => $event->broadcastOn()->name === 'member' && $event->message === 'Lösche Mitglied Max Muster' && $event->reload === false);
|
||||
Event::assertDispatched(JobFailed::class, fn ($event) => $event->message === 'Löschen von Max Muster fehlgeschlagen.' && $event->reload === true);
|
||||
Event::assertNotDispatched(JobStarted::class);
|
||||
Event::assertDispatched(JobFailed::class, fn ($event) => $event->message === 'Löschen von Max Muster fehlgeschlagen.' && $event->reload === true && $event->jobId->serialize() === $uuid->serialize());
|
||||
Event::assertNotDispatched(JobFinished::class);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue