From 96bc2c5a07863741ae1e8cf3259b8ff25fff9419 Mon Sep 17 00:00:00 2001 From: nexxo Date: Mon, 3 Aug 2026 19:04:32 +0200 Subject: [PATCH] Fix-Runde 2: eine fremde Mailklasse faehrt im ruhigen Takt statt ungebremst MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gedrosselteSpur() las die Spur nur aus $this->mailable->queue. Die setzt aber allein RidesALane, und den tragen ausschliesslich unsere vierzehn Klassen. Eine Mailklasse aus einem Paket — Fortify, ein kuenftiges Abhaengigkeitspaket — bekam damit gar keine Drossel. Das ist die Gegenrichtung zu der Entscheidung aus Aufgabe 1: dort faellt eine unbekannte Klasse ausdruecklich in die ruhige Spur, weil eine zu langsam verschickte Mail ein kleinerer Fehler ist als ein ungedrosselter Schub, den niemand vorhergesehen hat. Steht keine Spur am Mailable, entscheidet jetzt MailLane::for() — dieselbe Quelle, die Aufgabe 1 dafuer gebaut hat. Schlange und Takt fallen fuer so eine Mail auseinander: sie bleibt auf default, weil die Schlange der Trait waehlt, und faehrt trotzdem im ruhigen Takt, weil den der Auftrag waehlt. Der Kommentar sagt, warum das kein Versehen ist. Co-Authored-By: Claude Opus 5 --- app/Jobs/PacedMail.php | 36 ++++++++++++++-------- tests/Feature/Mail/MailPaceTest.php | 47 +++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 12 deletions(-) diff --git a/app/Jobs/PacedMail.php b/app/Jobs/PacedMail.php index eaa459c..5571464 100644 --- a/app/Jobs/PacedMail.php +++ b/app/Jobs/PacedMail.php @@ -99,19 +99,31 @@ class PacedMail extends SendQueuedMailable return null; } - // Die Schlange am Mailable, nicht `MailLane::for(...)`: das hier ist - // der Name, den `Mailable::queue()` an `pushOn()` gegeben hat — die - // Spur, in der dieser Auftrag TATSÄCHLICH liegt. `MailLane::for(...)` - // läse die Einstellungen erst im Arbeiter: würde der Betreiber die - // Rechnung von ruhig auf wichtig schieben, während zweihundert davon - // in der ruhigen Schlange warten, drosselte sie ab diesem Moment das - // falsche Kontingent. + // Zuerst die Schlange am Mailable: das ist der Name, den + // `Mailable::queue()` an `pushOn()` gegeben hat — die Spur, in der + // dieser Auftrag TATSÄCHLICH liegt. `MailLane::for(...)` allein wäre + // hier falsch, weil es die Einstellungen erst im Arbeiter läse: würde + // der Betreiber die Rechnung von ruhig auf wichtig schieben, während + // zweihundert davon in der ruhigen Schlange warten, drosselte sie ab + // diesem Moment das falsche Kontingent. // - // `?? null` statt eines nackten Zugriffs, weil `$this->mailable` nur - // die Schnittstelle zusichert: eine Mailklasse, die den Trait nicht - // benutzt, hat die Eigenschaft womöglich gar nicht — und liegt dann - // auch in keiner Spur, gehört also nicht gedrosselt. - return match ($this->mailable->queue ?? null) { + // Steht dort nichts, kommt die Mail von auswärts — aus einem Paket, + // aus Fortify, aus irgendetwas, das jemand eingebunden hat. Sie kennt + // `RidesALane` nicht, hat also keine Spur und liegt auf `default`. + // Dann entscheidet `MailLane::for(...)`, und das ist Absicht: Aufgabe 1 + // lässt eine unbekannte Klasse ausdrücklich in die ruhige Spur fallen, + // weil eine zu langsam verschickte Mail ein kleinerer Fehler ist als + // ein ungedrosselter Schub, den niemand vorhergesehen hat. Ohne diesen + // Rückgriff fiele sie am Auftrag in die Gegenrichtung. + // + // Schlange und Takt fallen für so eine Mail auseinander — sie bleibt + // auf `default` und fährt trotzdem im ruhigen Takt. Das ist kein + // Versehen, sondern die Arbeitsteilung: die Schlange wählt der Trait, + // den Takt wählt dieser Auftrag, und für eine fremde Mailklasse greift + // nur das zweite. + $spur = $this->mailable->queue ?? MailLane::for($this->mailable::class); + + return match ($spur) { MailLane::URGENT => MailLane::URGENT, MailLane::CALM => MailLane::CALM, default => null, diff --git a/tests/Feature/Mail/MailPaceTest.php b/tests/Feature/Mail/MailPaceTest.php index f1b5d7e..9ea5d4e 100644 --- a/tests/Feature/Mail/MailPaceTest.php +++ b/tests/Feature/Mail/MailPaceTest.php @@ -105,6 +105,29 @@ class EigeneGrenzeMail extends Mailable implements ShouldQueue } } +/** + * Eine Mailklasse aus der Fremde: kein Trait, keine Spur am Mailable. + * + * So sieht jede Mail aus, die nicht aus `app/Mail` kommt — Fortify, ein + * künftiges Abhängigkeitspaket, irgendetwas, das jemand einbindet. Sie kennt + * `RidesALane` nicht, also steht in `$mailable->queue` nichts, und sie liegt + * auf `default`. + */ +class FremdeMail extends Mailable implements ShouldQueue +{ + use Queueable, SerializesModels; + + public function envelope(): Envelope + { + return new Envelope(from: 'takt@example.test', subject: 'Fremd'); + } + + public function content(): Content + { + return new Content(htmlString: '

Fremd

'); + } +} + /** * Der Auftrag, so wie ihn das Einreihen erzeugt. * @@ -323,6 +346,30 @@ it('hängt jede gedrosselte Spur an ihre eigene Drossel', function (string $spur ->and(drosselName($middleware[0]))->toBe($spur); })->with([MailLane::URGENT, MailLane::CALM]); +/** + * Eine fremde Mailklasse fährt im ruhigen Takt, nicht ungebremst. + * + * Aufgabe 1 hat sich für diese Richtung entschieden: eine unbekannte Klasse + * fällt in die ruhige Spur, weil eine zu langsam verschickte Mail ein kleinerer + * Fehler ist als ein ungedrosselter Schub, den niemand vorhergesehen hat. Am + * Auftrag fiel sie bis hierher in die Gegenrichtung — ohne Spur am Mailable + * griff gar keine Drossel. + * + * Schlange und Takt fallen dabei auseinander, und das ist kein Versehen: die + * Schlange wählt der Trait, den diese Klasse nicht hat, den Takt wählt der + * Auftrag. Sie liegt also weiter auf `default` und fährt trotzdem im ruhigen + * Takt. + */ +it('drosselt eine Mailklasse ohne den Trait wie die ruhige Spur', function () { + $auftrag = taktAuftrag(new FremdeMail); + $middleware = $auftrag->middleware(); + + expect($auftrag->mailable->queue)->toBeNull() + ->and($middleware)->toHaveCount(1) + ->and(drosselName($middleware[0]))->toBe(MailLane::CALM) + ->and($auftrag->retryUntil())->not->toBeNull(); +}); + /** * Auf der direkten Spur wartet gerade ein Mensch, der eben geklickt hat. * Drosseln nützt dort nichts — die Mails entstehen einzeln und können gar