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