From 10d0ea6e08c3ff44d84341d8338f823d6c2d855c Mon Sep 17 00:00:00 2001 From: Luke Kuzmish Date: Fri, 17 Jan 2025 08:36:44 -0500 Subject: [PATCH 01/16] introduce OnQueue and OnConnection --- .../Bus/Attributes/OnConnection.php | 17 +++ .../Foundation/Bus/Attributes/OnQueue.php | 18 +++ .../Foundation/Bus/PendingDispatch.php | 27 +++++ .../BusPendingDispatchWithAttributesTest.php | 105 ++++++++++++++++++ 4 files changed, 167 insertions(+) create mode 100644 src/Illuminate/Foundation/Bus/Attributes/OnConnection.php create mode 100644 src/Illuminate/Foundation/Bus/Attributes/OnQueue.php create mode 100644 tests/Bus/BusPendingDispatchWithAttributesTest.php diff --git a/src/Illuminate/Foundation/Bus/Attributes/OnConnection.php b/src/Illuminate/Foundation/Bus/Attributes/OnConnection.php new file mode 100644 index 000000000000..6969d57f48e8 --- /dev/null +++ b/src/Illuminate/Foundation/Bus/Attributes/OnConnection.php @@ -0,0 +1,17 @@ +job = $job; + + $this->setQueueAndConnectionFromAttributesIfNotSet(); + } + + protected function setQueueAndConnectionFromAttributesIfNotSet(): void + { + if (($hasQueueSet = isset($this->job->queue)) + && ($hasConnectionSet = isset($this->job->connection))) { + return; + } + + $reflectionClass = new \ReflectionClass($this->job); + if (!$hasQueueSet) { + $onQueue = $reflectionClass->getAttributes(OnQueue::class); + if ($onQueue !== []) { + $this->onQueue($onQueue[0]->newInstance()->queue); + } + } + + if (!$hasConnectionSet) { + $onConnection = $reflectionClass->getAttributes(OnConnection::class); + if ($onConnection !== []) { + $this->onConnection($onConnection[0]->newInstance()->connection); + } + } } /** diff --git a/tests/Bus/BusPendingDispatchWithAttributesTest.php b/tests/Bus/BusPendingDispatchWithAttributesTest.php new file mode 100644 index 000000000000..533f33501ac8 --- /dev/null +++ b/tests/Bus/BusPendingDispatchWithAttributesTest.php @@ -0,0 +1,105 @@ +connection === "connection_from_attribute" + && $job->queue === "queue-from-attribute" + && $job->value === 123; + }); + } + + public function testDispatchWhereQueueIsFromAttribute(): void + { + Queue::fake(); + + FakeJobWithOnQueueFromAttribute::dispatch(1234)->onConnection("not-from-attribute"); + + Queue::assertPushed(function (FakeJobWithOnQueueFromAttribute $job) { + return $job->connection === "not-from-attribute" + && $job->queue === "queue-from-attribute" + && $job->value === 1234; + }); + } + + public function testDispatchWhereConnectionIsFromAttribute(): void + { + Queue::fake(); + + FakeJobWithOnConnection::dispatch(999); + + Queue::assertPushed(function (FakeJobWithOnConnection $job) { + return $job->connection === "connection_from_attribute" + && !isset($job->queue) + && $job->value === 999; + }); + } + + public function testOverridingQueueAndConnectionDoesNotUseAttributeValues(): void + { + Queue::fake(); + + FakeJobWithOnQueueAndOnConnection::dispatch('abc') + ->onQueue('setViaMethod') + ->onConnection('setViaMethodToo'); + + Queue::assertPushed(function (FakeJobWithOnQueueAndOnConnection $job) { + return $job->queue === "setViaMethod" + && $job->connection === "setViaMethodToo" + && $job->value === 'abc'; + }); + } +} + +#[OnQueue('queue-from-attribute')] +#[OnConnection('connection_from_attribute')] +class FakeJobWithOnQueueAndOnConnection implements ShouldQueue +{ + use Dispatchable; + use Queueable; + use InteractsWithQueue; + + public function __construct(public $value) + { + } +} + +#[OnQueue('queue-from-attribute')] +class FakeJobWithOnQueueFromAttribute implements ShouldQueue +{ + use Dispatchable; + use Queueable; + use InteractsWithQueue; + + public function __construct(public $value) + { + } +} + +#[OnConnection('connection_from_attribute')] +class FakeJobWithOnConnection implements ShouldQueue +{ + use Dispatchable; + use Queueable; + use InteractsWithQueue; + + public function __construct(public $value) + { + } +} From b8f9b4afe1d5dd412215a120bf511f8e8234c829 Mon Sep 17 00:00:00 2001 From: Luke Kuzmish Date: Fri, 17 Jan 2025 09:01:39 -0500 Subject: [PATCH 02/16] ensure both variables are set --- src/Illuminate/Foundation/Bus/PendingDispatch.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Foundation/Bus/PendingDispatch.php b/src/Illuminate/Foundation/Bus/PendingDispatch.php index bac06ac441e2..cedd320df09d 100644 --- a/src/Illuminate/Foundation/Bus/PendingDispatch.php +++ b/src/Illuminate/Foundation/Bus/PendingDispatch.php @@ -39,10 +39,18 @@ public function __construct($job) $this->setQueueAndConnectionFromAttributesIfNotSet(); } + /** + * Set the job's queue and connection values based on the job's OnQueue/OnConnection attributes. + * + * @return void + * @throws \ReflectionException + */ protected function setQueueAndConnectionFromAttributesIfNotSet(): void { - if (($hasQueueSet = isset($this->job->queue)) - && ($hasConnectionSet = isset($this->job->connection))) { + $hasQueueSet = isset($this->job->queue); + $hasConnectionSet = isset($this->job->connection); + + if ($hasQueueSet && $hasConnectionSet) { return; } From 2d4d3f7ac0f82eb864b96b71a74d44e9b3f0a4e0 Mon Sep 17 00:00:00 2001 From: Luke Kuzmish Date: Fri, 17 Jan 2025 09:23:27 -0500 Subject: [PATCH 03/16] tests --- .../BusPendingDispatchWithAttributesTest.php | 48 ++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) rename tests/{ => Integration}/Bus/BusPendingDispatchWithAttributesTest.php (70%) diff --git a/tests/Bus/BusPendingDispatchWithAttributesTest.php b/tests/Integration/Bus/BusPendingDispatchWithAttributesTest.php similarity index 70% rename from tests/Bus/BusPendingDispatchWithAttributesTest.php rename to tests/Integration/Bus/BusPendingDispatchWithAttributesTest.php index 533f33501ac8..961830db8e73 100644 --- a/tests/Bus/BusPendingDispatchWithAttributesTest.php +++ b/tests/Integration/Bus/BusPendingDispatchWithAttributesTest.php @@ -1,6 +1,7 @@ value === 'abc'; }); } + + public function testAllowsEnumsInAttributes(): void + { + Queue::fake(); + + FakeJobWithAttributesUsingEnums::dispatch(1234); + + Queue::assertPushed( + fn (FakeJobWithAttributesUsingEnums $job) => $job->queue === 'my-value' + && $job->connection == 'other-value' + && $job->value === 1234 + ); + } + + public function testWorksWithDispatchFunction(): void + { + Queue::fake(); + + dispatch(new FakeJobWithAttributesUsingEnums('laravel'))->onConnection('zzz'); + + Queue::assertPushed( + fn (FakeJobWithAttributesUsingEnums $job) => $job->queue === 'my-value' + && $job->connection == 'zzz' + && $job->value === 'laravel' + ); + } } #[OnQueue('queue-from-attribute')] @@ -103,3 +130,22 @@ public function __construct(public $value) { } } + +#[OnQueue(PendingDispatchWithAttributesEnum::MyValue)] +#[OnConnection(PendingDispatchWithAttributesEnum::OtherValue)] +class FakeJobWithAttributesUsingEnums implements ShouldQueue +{ + use Dispatchable; + use Queueable; + use InteractsWithQueue; + + public function __construct(public $value) + { + } +} + +enum PendingDispatchWithAttributesEnum: string +{ + case MyValue = 'my-value'; + case OtherValue = 'other-value'; +} From 20c693665d7c68e2f26f739343c7cc4141d7358c Mon Sep 17 00:00:00 2001 From: Luke Kuzmish <42181698+cosmastech@users.noreply.github.com> Date: Fri, 17 Jan 2025 10:57:27 -0500 Subject: [PATCH 04/16] UnitEnum for connection --- src/Illuminate/Foundation/Bus/Attributes/OnConnection.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Foundation/Bus/Attributes/OnConnection.php b/src/Illuminate/Foundation/Bus/Attributes/OnConnection.php index 6969d57f48e8..80b47cedda0b 100644 --- a/src/Illuminate/Foundation/Bus/Attributes/OnConnection.php +++ b/src/Illuminate/Foundation/Bus/Attributes/OnConnection.php @@ -8,7 +8,7 @@ class OnConnection { /** - * @param string|\BackedEnum $connection + * @param string|\UnitEnum $connection * @return void */ public function __construct(public $connection) From fb9012f7be25fae85c5f2f8513911a1b5831871d Mon Sep 17 00:00:00 2001 From: Luke Kuzmish <42181698+cosmastech@users.noreply.github.com> Date: Fri, 17 Jan 2025 10:57:49 -0500 Subject: [PATCH 05/16] use UnitEnum --- src/Illuminate/Foundation/Bus/Attributes/OnQueue.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Foundation/Bus/Attributes/OnQueue.php b/src/Illuminate/Foundation/Bus/Attributes/OnQueue.php index 89357179dc4e..9cb14df65512 100644 --- a/src/Illuminate/Foundation/Bus/Attributes/OnQueue.php +++ b/src/Illuminate/Foundation/Bus/Attributes/OnQueue.php @@ -8,7 +8,7 @@ class OnQueue { /** - * @param string|\BackedEnum $queue + * @param string|\UnitEnum $queue * @return void */ public function __construct(public $queue) From 15a9de7792164f35f27660079aaf7c94f3a57af2 Mon Sep 17 00:00:00 2001 From: Luke Kuzmish Date: Mon, 20 Jan 2025 06:16:24 -0500 Subject: [PATCH 06/16] style --- .../Bus/Attributes/OnConnection.php | 2 +- .../Foundation/Bus/Attributes/OnQueue.php | 3 +-- .../Foundation/Bus/PendingDispatch.php | 5 +++-- .../BusPendingDispatchWithAttributesTest.php | 19 +++++++++---------- 4 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/Illuminate/Foundation/Bus/Attributes/OnConnection.php b/src/Illuminate/Foundation/Bus/Attributes/OnConnection.php index 80b47cedda0b..9a5f326f8d58 100644 --- a/src/Illuminate/Foundation/Bus/Attributes/OnConnection.php +++ b/src/Illuminate/Foundation/Bus/Attributes/OnConnection.php @@ -8,7 +8,7 @@ class OnConnection { /** - * @param string|\UnitEnum $connection + * @param string|\UnitEnum $connection * @return void */ public function __construct(public $connection) diff --git a/src/Illuminate/Foundation/Bus/Attributes/OnQueue.php b/src/Illuminate/Foundation/Bus/Attributes/OnQueue.php index 9cb14df65512..435c6267a8a0 100644 --- a/src/Illuminate/Foundation/Bus/Attributes/OnQueue.php +++ b/src/Illuminate/Foundation/Bus/Attributes/OnQueue.php @@ -8,11 +8,10 @@ class OnQueue { /** - * @param string|\UnitEnum $queue + * @param string|\UnitEnum $queue * @return void */ public function __construct(public $queue) { - } } diff --git a/src/Illuminate/Foundation/Bus/PendingDispatch.php b/src/Illuminate/Foundation/Bus/PendingDispatch.php index b985dc7f4c92..2eea86dfd685 100644 --- a/src/Illuminate/Foundation/Bus/PendingDispatch.php +++ b/src/Illuminate/Foundation/Bus/PendingDispatch.php @@ -46,6 +46,7 @@ public function __construct($job) * Set the job's queue and connection values based on the job's OnQueue/OnConnection attributes. * * @return void + * * @throws \ReflectionException */ protected function setQueueAndConnectionFromAttributesIfNotSet(): void @@ -58,14 +59,14 @@ protected function setQueueAndConnectionFromAttributesIfNotSet(): void } $reflectionClass = new \ReflectionClass($this->job); - if (!$hasQueueSet) { + if (! $hasQueueSet) { $onQueue = $reflectionClass->getAttributes(OnQueue::class); if ($onQueue !== []) { $this->onQueue($onQueue[0]->newInstance()->queue); } } - if (!$hasConnectionSet) { + if (! $hasConnectionSet) { $onConnection = $reflectionClass->getAttributes(OnConnection::class); if ($onConnection !== []) { $this->onConnection($onConnection[0]->newInstance()->connection); diff --git a/tests/Integration/Bus/BusPendingDispatchWithAttributesTest.php b/tests/Integration/Bus/BusPendingDispatchWithAttributesTest.php index 961830db8e73..50d4174d852e 100644 --- a/tests/Integration/Bus/BusPendingDispatchWithAttributesTest.php +++ b/tests/Integration/Bus/BusPendingDispatchWithAttributesTest.php @@ -2,7 +2,6 @@ namespace Illuminate\Tests\Integration\Bus; - use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Attributes\OnConnection; @@ -20,8 +19,8 @@ public function testDispatchWhereQueueAndConnectionAreFromAttributes(): void FakeJobWithOnQueueAndOnConnection::dispatch(123); Queue::assertPushed(function (FakeJobWithOnQueueAndOnConnection $job) { - return $job->connection === "connection_from_attribute" - && $job->queue === "queue-from-attribute" + return $job->connection === 'connection_from_attribute' + && $job->queue === 'queue-from-attribute' && $job->value === 123; }); } @@ -30,11 +29,11 @@ public function testDispatchWhereQueueIsFromAttribute(): void { Queue::fake(); - FakeJobWithOnQueueFromAttribute::dispatch(1234)->onConnection("not-from-attribute"); + FakeJobWithOnQueueFromAttribute::dispatch(1234)->onConnection('not-from-attribute'); Queue::assertPushed(function (FakeJobWithOnQueueFromAttribute $job) { - return $job->connection === "not-from-attribute" - && $job->queue === "queue-from-attribute" + return $job->connection === 'not-from-attribute' + && $job->queue === 'queue-from-attribute' && $job->value === 1234; }); } @@ -46,8 +45,8 @@ public function testDispatchWhereConnectionIsFromAttribute(): void FakeJobWithOnConnection::dispatch(999); Queue::assertPushed(function (FakeJobWithOnConnection $job) { - return $job->connection === "connection_from_attribute" - && !isset($job->queue) + return $job->connection === 'connection_from_attribute' + && ! isset($job->queue) && $job->value === 999; }); } @@ -61,8 +60,8 @@ public function testOverridingQueueAndConnectionDoesNotUseAttributeValues(): voi ->onConnection('setViaMethodToo'); Queue::assertPushed(function (FakeJobWithOnQueueAndOnConnection $job) { - return $job->queue === "setViaMethod" - && $job->connection === "setViaMethodToo" + return $job->queue === 'setViaMethod' + && $job->connection === 'setViaMethodToo' && $job->value === 'abc'; }); } From 25432a931bad118ad4334662d59de6e1d4c6dd6d Mon Sep 17 00:00:00 2001 From: Luke Kuzmish Date: Mon, 20 Jan 2025 06:18:31 -0500 Subject: [PATCH 07/16] test for overriding with onQueue() and onConnection() --- .../Bus/BusPendingDispatchWithAttributesTest.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/Integration/Bus/BusPendingDispatchWithAttributesTest.php b/tests/Integration/Bus/BusPendingDispatchWithAttributesTest.php index 50d4174d852e..51e1ff62b5ed 100644 --- a/tests/Integration/Bus/BusPendingDispatchWithAttributesTest.php +++ b/tests/Integration/Bus/BusPendingDispatchWithAttributesTest.php @@ -91,6 +91,21 @@ public function testWorksWithDispatchFunction(): void && $job->value === 'laravel' ); } + + public function testIgnoresAttributesWhenCallingOnQueueAndOnConnection(): void + { + Queue::fake(); + + FakeJobWithAttributesUsingEnums::dispatch(76543) + ->onQueue('called-onQueue') + ->onConnection('called-onConnection'); + + Queue::assertPushed( + fn (FakeJobWithAttributesUsingEnums $job) => $job->queue === 'called-onQueue' + && $job->connection == 'called-onConnection' + && $job->value === 76543 + ); + } } #[OnQueue('queue-from-attribute')] From 326a7b6b4aa5f3ea31134780fd373c80494b1a31 Mon Sep 17 00:00:00 2001 From: Luke Kuzmish Date: Tue, 21 Jan 2025 20:08:05 -0500 Subject: [PATCH 08/16] move setting the queue/connection to the destructor --- .../Foundation/Bus/PendingDispatch.php | 67 +++++++++---------- 1 file changed, 33 insertions(+), 34 deletions(-) diff --git a/src/Illuminate/Foundation/Bus/PendingDispatch.php b/src/Illuminate/Foundation/Bus/PendingDispatch.php index 2eea86dfd685..5801a07e81f2 100644 --- a/src/Illuminate/Foundation/Bus/PendingDispatch.php +++ b/src/Illuminate/Foundation/Bus/PendingDispatch.php @@ -38,40 +38,6 @@ class PendingDispatch public function __construct($job) { $this->job = $job; - - $this->setQueueAndConnectionFromAttributesIfNotSet(); - } - - /** - * Set the job's queue and connection values based on the job's OnQueue/OnConnection attributes. - * - * @return void - * - * @throws \ReflectionException - */ - protected function setQueueAndConnectionFromAttributesIfNotSet(): void - { - $hasQueueSet = isset($this->job->queue); - $hasConnectionSet = isset($this->job->connection); - - if ($hasQueueSet && $hasConnectionSet) { - return; - } - - $reflectionClass = new \ReflectionClass($this->job); - if (! $hasQueueSet) { - $onQueue = $reflectionClass->getAttributes(OnQueue::class); - if ($onQueue !== []) { - $this->onQueue($onQueue[0]->newInstance()->queue); - } - } - - if (! $hasConnectionSet) { - $onConnection = $reflectionClass->getAttributes(OnConnection::class); - if ($onConnection !== []) { - $this->onConnection($onConnection[0]->newInstance()->connection); - } - } } /** @@ -225,6 +191,38 @@ public function getJob() return $this->job; } + /** + * Set the job's queue and connection values based on the job's OnQueue/OnConnection attributes. + * + * @return void + * + * @throws \ReflectionException + */ + protected function setQueueAndConnectionFromAttributesIfNotSet(): void + { + $hasQueueSet = isset($this->job->queue); + $hasConnectionSet = isset($this->job->connection); + + if ($hasQueueSet && $hasConnectionSet) { + return; + } + + $reflectionClass = new \ReflectionClass($this->job); + if (! $hasQueueSet) { + $onQueue = $reflectionClass->getAttributes(OnQueue::class); + if ($onQueue !== []) { + $this->onQueue($onQueue[0]->newInstance()->queue); + } + } + + if (! $hasConnectionSet) { + $onConnection = $reflectionClass->getAttributes(OnConnection::class); + if ($onConnection !== []) { + $this->onConnection($onConnection[0]->newInstance()->connection); + } + } + } + /** * Dynamically proxy methods to the underlying job. * @@ -247,6 +245,7 @@ public function __call($method, $parameters) public function __destruct() { $this->addUniqueJobInformationToContext($this->job); + $this->setQueueAndConnectionFromAttributesIfNotSet(); if (! $this->shouldDispatch()) { $this->removeUniqueJobInformationFromContext($this->job); From d397e57f74ada4b80c3af92f5666ad6bcf016990 Mon Sep 17 00:00:00 2001 From: Luke Kuzmish Date: Wed, 22 Jan 2025 20:32:54 -0500 Subject: [PATCH 09/16] wip --- .../Foundation/Bus/PendingDispatch.php | 14 ++--- .../Queue/InteractsWithQueueAndConnection.php | 42 +++++++++++++ .../Attributes => Queue}/OnConnection.php | 2 +- .../{Bus/Attributes => Queue}/OnQueue.php | 2 +- src/Illuminate/Mail/Mailable.php | 51 ++++++++++++---- .../BusPendingDispatchWithAttributesTest.php | 4 +- tests/Support/SupportMailTest.php | 61 ++++++++++++++++++- 7 files changed, 149 insertions(+), 27 deletions(-) create mode 100644 src/Illuminate/Foundation/Queue/InteractsWithQueueAndConnection.php rename src/Illuminate/Foundation/{Bus/Attributes => Queue}/OnConnection.php (82%) rename src/Illuminate/Foundation/{Bus/Attributes => Queue}/OnQueue.php (81%) diff --git a/src/Illuminate/Foundation/Bus/PendingDispatch.php b/src/Illuminate/Foundation/Bus/PendingDispatch.php index 5801a07e81f2..54a831a8945f 100644 --- a/src/Illuminate/Foundation/Bus/PendingDispatch.php +++ b/src/Illuminate/Foundation/Bus/PendingDispatch.php @@ -7,12 +7,12 @@ use Illuminate\Contracts\Bus\Dispatcher; use Illuminate\Contracts\Cache\Repository as Cache; use Illuminate\Contracts\Queue\ShouldBeUnique; -use Illuminate\Foundation\Bus\Attributes\OnConnection; -use Illuminate\Foundation\Bus\Attributes\OnQueue; +use Illuminate\Foundation\Queue\InteractsWithQueueAndConnection; use Illuminate\Foundation\Queue\InteractsWithUniqueJobs; class PendingDispatch { + use InteractsWithQueueAndConnection; use InteractsWithUniqueJobs; /** @@ -209,16 +209,14 @@ protected function setQueueAndConnectionFromAttributesIfNotSet(): void $reflectionClass = new \ReflectionClass($this->job); if (! $hasQueueSet) { - $onQueue = $reflectionClass->getAttributes(OnQueue::class); - if ($onQueue !== []) { - $this->onQueue($onQueue[0]->newInstance()->queue); + if ($queue = $this->getQueueFromOnConnectionAttribute($reflectionClass)) { + $this->onQueue($queue); } } if (! $hasConnectionSet) { - $onConnection = $reflectionClass->getAttributes(OnConnection::class); - if ($onConnection !== []) { - $this->onConnection($onConnection[0]->newInstance()->connection); + if ($connection = $this->getConnectionFromOnConnectionAttribute($reflectionClass)) { + $this->onConnection($connection); } } } diff --git a/src/Illuminate/Foundation/Queue/InteractsWithQueueAndConnection.php b/src/Illuminate/Foundation/Queue/InteractsWithQueueAndConnection.php new file mode 100644 index 000000000000..fc1d70d9ee34 --- /dev/null +++ b/src/Illuminate/Foundation/Queue/InteractsWithQueueAndConnection.php @@ -0,0 +1,42 @@ +getAttributes(OnConnection::class); + if ($onConnection === []) { + return null; + } + + return enum_value($onConnection[0]->newInstance()->connection); + } + + /** + * Extract the queue from OnQueue attribute if present. + * + * @param \ReflectionClass $reflectionClass + * @return string|\UnitEnum|null + */ + protected function getQueueFromOnConnectionAttribute(ReflectionClass $reflectionClass) + { + $onQueue = $reflectionClass->getAttributes(OnQueue::class); + if ($onQueue === []) { + return null; + } + + return enum_value($onQueue[0]->newInstance()->queue); + } +} diff --git a/src/Illuminate/Foundation/Bus/Attributes/OnConnection.php b/src/Illuminate/Foundation/Queue/OnConnection.php similarity index 82% rename from src/Illuminate/Foundation/Bus/Attributes/OnConnection.php rename to src/Illuminate/Foundation/Queue/OnConnection.php index 9a5f326f8d58..913d065f23af 100644 --- a/src/Illuminate/Foundation/Bus/Attributes/OnConnection.php +++ b/src/Illuminate/Foundation/Queue/OnConnection.php @@ -1,6 +1,6 @@ later($this->delay, $queue); } - $connection = property_exists($this, 'connection') ? $this->connection : null; - - $queueName = property_exists($this, 'queue') ? $this->queue : null; - - return $queue->connection($connection)->pushOn( - $queueName ?: null, $this->newQueuedJob() + return $queue->connection($this->getConnection())->pushOn( + $this->getQueue(), $this->newQueuedJob() ); } @@ -245,12 +242,8 @@ public function queue(Queue $queue) */ public function later($delay, Queue $queue) { - $connection = property_exists($this, 'connection') ? $this->connection : null; - - $queueName = property_exists($this, 'queue') ? $this->queue : null; - - return $queue->connection($connection)->laterOn( - $queueName ?: null, $delay, $this->newQueuedJob() + return $queue->connection($this->getConnection())->laterOn( + $this->getQueue(), $delay, $this->newQueuedJob() ); } @@ -467,6 +460,38 @@ protected function buildSubject($message) return $this; } + /** + * Get the queue specified on the class or from the OnQueue attribute. + * + * @return string|\UnitEnum|null + */ + protected function getQueue() + { + $queue = property_exists($this, 'queue') ? $this->queue : null; + + if ($queue === null) { + $queue = $this->getQueueFromOnConnectionAttribute(new ReflectionClass($this)); + } + + return $queue; + } + + /** + * Get the connection specified on the class or from the OnConnection attribute. + * + * @return string|\UnitEnum|null + */ + protected function getConnection() + { + $connection = property_exists($this, 'connection') ? $this->connection : null; + + if ($connection === null) { + $connection = $this->getConnectionFromOnConnectionAttribute(new ReflectionClass($this)); + } + + return $connection; + } + /** * Add all of the attachments to the message. * diff --git a/tests/Integration/Bus/BusPendingDispatchWithAttributesTest.php b/tests/Integration/Bus/BusPendingDispatchWithAttributesTest.php index 51e1ff62b5ed..08ea3471abef 100644 --- a/tests/Integration/Bus/BusPendingDispatchWithAttributesTest.php +++ b/tests/Integration/Bus/BusPendingDispatchWithAttributesTest.php @@ -4,9 +4,9 @@ use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; -use Illuminate\Foundation\Bus\Attributes\OnConnection; -use Illuminate\Foundation\Bus\Attributes\OnQueue; use Illuminate\Foundation\Bus\Dispatchable; +use Illuminate\Foundation\Queue\OnConnection; +use Illuminate\Foundation\Queue\OnQueue; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Support\Facades\Queue; use Orchestra\Testbench\TestCase; diff --git a/tests/Support/SupportMailTest.php b/tests/Support/SupportMailTest.php index 146a3ca331a5..307c72c792ec 100644 --- a/tests/Support/SupportMailTest.php +++ b/tests/Support/SupportMailTest.php @@ -2,15 +2,21 @@ namespace Illuminate\Tests\Support; +use Illuminate\Contracts\Queue\ShouldQueue; +use Illuminate\Foundation\Queue\OnConnection; +use Illuminate\Foundation\Queue\OnQueue; use Illuminate\Mail\Mailable; +use Illuminate\Mail\SendQueuedMailable; use Illuminate\Support\Facades\Mail; +use Illuminate\Support\Facades\Queue; use Orchestra\Testbench\TestCase; +use PHPUnit\Framework\Attributes\TestWith; class SupportMailTest extends TestCase { public function testItRegisterAndCallMacros() { - Mail::macro('test', fn (string $str) => $str === 'foo' + Mail::macro('test', fn(string $str) => $str === 'foo' ? 'it works!' : 'it failed.', ); @@ -20,7 +26,7 @@ public function testItRegisterAndCallMacros() public function testItRegisterAndCallMacrosWhenFaked() { - Mail::macro('test', fn (string $str) => $str === 'foo' + Mail::macro('test', fn(string $str) => $str === 'foo' ? 'it works!' : 'it failed.', ); @@ -39,6 +45,33 @@ public function testEmailSent() Mail::assertSent(TestMail::class); } + + #[TestWith([TestMailWithOnQueue::class, 'my-queue'], 'string for queue')] + #[TestWith([TestMailWithEnumOnQueue::class, 'queue-from-enum'], 'enum for queue')] + public function testQueuedMailableRespectsStringOnQueueAttribute(string $mailableClass, string $queueName) + { + Queue::fake(); + + Mail::send(new $mailableClass()); + + Queue::assertPushedOn( + $queueName, + SendQueuedMailable::class, + fn ($job) => is_a($job->mailable, $mailableClass, true) + ); + } + + public function testQueueableMailableUsesQueueIfSetAsProperty() + { + Queue::fake(); + Mail::send(new TestMailWithOnQueueAndOnConnectionSetAndBothPropertiesSet()); + + Queue::assertPushed(function(SendQueuedMailable $sendQueuedMailable) { + return $sendQueuedMailable->mailable instanceof TestMailWithOnQueueAndOnConnectionSetAndBothPropertiesSet + && $sendQueuedMailable->connection === 'my-connection' + && $sendQueuedMailable->queue === 'some-other-queue'; + }); + } } class TestMail extends Mailable @@ -53,3 +86,27 @@ public function build() return $this->view('view'); } } + +#[OnQueue('my-queue')] +class TestMailWithOnQueue extends Mailable implements ShouldQueue +{ +} + +#[OnQueue(SupportMailTestEnum::Queue)] +class TestMailWithEnumOnQueue extends Mailable implements ShouldQueue +{ +} + +#[OnQueue(SupportMailTestEnum::Queue)] +#[OnConnection(SupportMailTestEnum::Connection)] +class TestMailWithOnQueueAndOnConnectionSetAndBothPropertiesSet extends Mailable implements ShouldQueue +{ + public $queue = 'some-other-queue'; + public $connection = 'my-connection'; +} + +enum SupportMailTestEnum: string +{ + case Queue = 'queue-from-enum'; + case Connection = 'connection-from-enum'; +} From 1e320eb585ca7f3b2af110bc24f41b153401057a Mon Sep 17 00:00:00 2001 From: Luke Kuzmish Date: Thu, 23 Jan 2025 15:30:33 -0500 Subject: [PATCH 10/16] mailable --- .../Foundation/Bus/PendingDispatch.php | 2 +- .../Queue/InteractsWithQueueAndConnection.php | 8 +- src/Illuminate/Mail/Mailable.php | 11 +- tests/Support/SupportMailTest.php | 108 ++++++++++++++++-- 4 files changed, 111 insertions(+), 18 deletions(-) diff --git a/src/Illuminate/Foundation/Bus/PendingDispatch.php b/src/Illuminate/Foundation/Bus/PendingDispatch.php index 54a831a8945f..4dcdf09702f6 100644 --- a/src/Illuminate/Foundation/Bus/PendingDispatch.php +++ b/src/Illuminate/Foundation/Bus/PendingDispatch.php @@ -209,7 +209,7 @@ protected function setQueueAndConnectionFromAttributesIfNotSet(): void $reflectionClass = new \ReflectionClass($this->job); if (! $hasQueueSet) { - if ($queue = $this->getQueueFromOnConnectionAttribute($reflectionClass)) { + if ($queue = $this->getQueueFromOnQueueAttribute($reflectionClass)) { $this->onQueue($queue); } } diff --git a/src/Illuminate/Foundation/Queue/InteractsWithQueueAndConnection.php b/src/Illuminate/Foundation/Queue/InteractsWithQueueAndConnection.php index fc1d70d9ee34..9e4310facd3d 100644 --- a/src/Illuminate/Foundation/Queue/InteractsWithQueueAndConnection.php +++ b/src/Illuminate/Foundation/Queue/InteractsWithQueueAndConnection.php @@ -4,8 +4,6 @@ use ReflectionClass; -use function Illuminate\Support\enum_value; - trait InteractsWithQueueAndConnection { /** @@ -21,7 +19,7 @@ protected function getConnectionFromOnConnectionAttribute(ReflectionClass $refle return null; } - return enum_value($onConnection[0]->newInstance()->connection); + return $onConnection[0]->newInstance()->connection; } /** @@ -30,13 +28,13 @@ protected function getConnectionFromOnConnectionAttribute(ReflectionClass $refle * @param \ReflectionClass $reflectionClass * @return string|\UnitEnum|null */ - protected function getQueueFromOnConnectionAttribute(ReflectionClass $reflectionClass) + protected function getQueueFromOnQueueAttribute(ReflectionClass $reflectionClass) { $onQueue = $reflectionClass->getAttributes(OnQueue::class); if ($onQueue === []) { return null; } - return enum_value($onQueue[0]->newInstance()->queue); + return $onQueue[0]->newInstance()->queue; } } diff --git a/src/Illuminate/Mail/Mailable.php b/src/Illuminate/Mail/Mailable.php index ad62c51c2551..6c1681a489c2 100644 --- a/src/Illuminate/Mail/Mailable.php +++ b/src/Illuminate/Mail/Mailable.php @@ -28,6 +28,7 @@ use Symfony\Component\Mailer\Header\MetadataHeader; use Symfony\Component\Mailer\Header\TagHeader; use Symfony\Component\Mime\Address; +use function Illuminate\Support\enum_value; class Mailable implements MailableContract, Renderable { @@ -463,23 +464,23 @@ protected function buildSubject($message) /** * Get the queue specified on the class or from the OnQueue attribute. * - * @return string|\UnitEnum|null + * @return string|null */ protected function getQueue() { $queue = property_exists($this, 'queue') ? $this->queue : null; if ($queue === null) { - $queue = $this->getQueueFromOnConnectionAttribute(new ReflectionClass($this)); + $queue = $this->getQueueFromOnQueueAttribute(new ReflectionClass($this)); } - return $queue; + return $queue !== null ? enum_value($queue) : null; } /** * Get the connection specified on the class or from the OnConnection attribute. * - * @return string|\UnitEnum|null + * @return string|null */ protected function getConnection() { @@ -489,7 +490,7 @@ protected function getConnection() $connection = $this->getConnectionFromOnConnectionAttribute(new ReflectionClass($this)); } - return $connection; + return $connection !== null ? enum_value($connection) : null; } /** diff --git a/tests/Support/SupportMailTest.php b/tests/Support/SupportMailTest.php index 307c72c792ec..c0efdc52b132 100644 --- a/tests/Support/SupportMailTest.php +++ b/tests/Support/SupportMailTest.php @@ -7,16 +7,18 @@ use Illuminate\Foundation\Queue\OnQueue; use Illuminate\Mail\Mailable; use Illuminate\Mail\SendQueuedMailable; +use Illuminate\Queue\Events\JobProcessing; use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\Queue; use Orchestra\Testbench\TestCase; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\TestWith; class SupportMailTest extends TestCase { public function testItRegisterAndCallMacros() { - Mail::macro('test', fn(string $str) => $str === 'foo' + Mail::macro('test', fn (string $str) => $str === 'foo' ? 'it works!' : 'it failed.', ); @@ -26,7 +28,7 @@ public function testItRegisterAndCallMacros() public function testItRegisterAndCallMacrosWhenFaked() { - Mail::macro('test', fn(string $str) => $str === 'foo' + Mail::macro('test', fn (string $str) => $str === 'foo' ? 'it works!' : 'it failed.', ); @@ -46,9 +48,8 @@ public function testEmailSent() Mail::assertSent(TestMail::class); } - #[TestWith([TestMailWithOnQueue::class, 'my-queue'], 'string for queue')] - #[TestWith([TestMailWithEnumOnQueue::class, 'queue-from-enum'], 'enum for queue')] - public function testQueuedMailableRespectsStringOnQueueAttribute(string $mailableClass, string $queueName) + #[DataProvider('queueDataProvider')] + public function testQueuedMailableRespectsOnQueueAttribute(string $mailableClass, string $queueName) { Queue::fake(); @@ -61,17 +62,87 @@ public function testQueuedMailableRespectsStringOnQueueAttribute(string $mailabl ); } - public function testQueueableMailableUsesQueueIfSetAsProperty() + #[DataProvider('queueDataProvider')] + public function testQueuedMailableDispatchedLaterRespectsOnQueueAttribute(string $mailableClass, string $queueName) + { + Queue::fake(); + + Mail::later(100, new $mailableClass); + + Queue::assertPushedOn( + $queueName, + SendQueuedMailable::class, + fn ($job) => is_a($job->mailable, $mailableClass, true) + ); + } + + #[DataProvider('connectionDataProvider')] + public function testQueuedMailableRespectsOnConnectionAttribute(string $mailableClass, string $connectionName) + { + Queue::fake(); + + Queue::before(function (JobProcessing $jobProcessing) use ($connectionName) { + $this->assertEquals($connectionName, $jobProcessing->connectionName); + }); + + Mail::send(new $mailableClass()); + + } + + #[DataProvider('connectionDataProvider')] + public function testLaterQueuedMailableRespectsOnConnectionAttribute(string $mailableClass, string $connectionName) + { + Queue::fake(); + + Queue::before(function (JobProcessing $jobProcessing) use ($connectionName) { + $this->assertEquals($connectionName, $jobProcessing->connectionName); + }); + + Mail::later(100, new $mailableClass()); + } + + public function testQueueableMailableUsesQueueAndConnectionFromClassProperties() { Queue::fake(); Mail::send(new TestMailWithOnQueueAndOnConnectionSetAndBothPropertiesSet()); - Queue::assertPushed(function(SendQueuedMailable $sendQueuedMailable) { + Queue::assertPushed(function (SendQueuedMailable $sendQueuedMailable) { + return $sendQueuedMailable->mailable instanceof TestMailWithOnQueueAndOnConnectionSetAndBothPropertiesSet + && $sendQueuedMailable->connection === 'my-connection' + && $sendQueuedMailable->queue === 'some-other-queue'; + }); + } + + public function testQueueableMailableDispatchedLaterUsesQueueAndConnectionFromClassProperties() + { + Queue::fake(); + Mail::later(100, new TestMailWithOnQueueAndOnConnectionSetAndBothPropertiesSet()); + + Queue::assertPushed(function (SendQueuedMailable $sendQueuedMailable) { return $sendQueuedMailable->mailable instanceof TestMailWithOnQueueAndOnConnectionSetAndBothPropertiesSet && $sendQueuedMailable->connection === 'my-connection' && $sendQueuedMailable->queue === 'some-other-queue'; }); } + + /** + * @return array, string}> + */ + public static function queueDataProvider(): array + { + return [ + 'string for queue' => [TestMailWithOnQueue::class, 'my-queue'], + 'enum for queue' => [TestMailWithEnumOnQueue::class, 'queue-from-enum'], + ]; + } + + public static function connectionDataProvider(): array + { + return [ + 'string for connection' => [TestMailWithOnConnection::class, 'connection-string'], + 'enum for connection' => [TestMailWithEnumOnConnection::class, 'connection-from-enum'], + ]; + } } class TestMail extends Mailable @@ -97,6 +168,29 @@ class TestMailWithEnumOnQueue extends Mailable implements ShouldQueue { } +#[OnConnection('connection-string')] +class TestMailWithOnConnection extends Mailable implements ShouldQueue +{ + public $queue = 'queue'; + + public function build() + { + return $this->view('view'); + } +} + +#[OnConnection(SupportMailTestEnum::Connection)] +class TestMailWithEnumOnConnection extends Mailable implements ShouldQueue +{ + public $queue = 'queue'; + + public function build() + { + return $this->view('view'); + } +} + + #[OnQueue(SupportMailTestEnum::Queue)] #[OnConnection(SupportMailTestEnum::Connection)] class TestMailWithOnQueueAndOnConnectionSetAndBothPropertiesSet extends Mailable implements ShouldQueue From 8a40a1a7e56ca838ab46a74bdd04921ebf08e535 Mon Sep 17 00:00:00 2001 From: Luke Kuzmish Date: Sat, 25 Jan 2025 07:34:26 -0500 Subject: [PATCH 11/16] queued listeners --- src/Illuminate/Events/Dispatcher.php | 61 +++++++++----- .../Integration/Queue/QueuedListenersTest.php | 83 +++++++++++++++++++ 2 files changed, 121 insertions(+), 23 deletions(-) diff --git a/src/Illuminate/Events/Dispatcher.php b/src/Illuminate/Events/Dispatcher.php index 5a3c9702e198..90c2459ad9ba 100755 --- a/src/Illuminate/Events/Dispatcher.php +++ b/src/Illuminate/Events/Dispatcher.php @@ -14,16 +14,18 @@ use Illuminate\Contracts\Queue\ShouldBeEncrypted; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueueAfterCommit; +use Illuminate\Foundation\Queue\InteractsWithQueueAndConnection; use Illuminate\Support\Arr; use Illuminate\Support\Collection; use Illuminate\Support\Str; use Illuminate\Support\Traits\Macroable; use Illuminate\Support\Traits\ReflectsClosures; use ReflectionClass; +use function Illuminate\Support\enum_value; class Dispatcher implements DispatcherContract { - use Macroable, ReflectsClosures; + use InteractsWithQueueAndConnection, Macroable, ReflectsClosures; /** * The IoC container instance. @@ -133,8 +135,8 @@ protected function setupWildcardListen($event, $listener) public function hasListeners($eventName) { return isset($this->listeners[$eventName]) || - isset($this->wildcards[$eventName]) || - $this->hasWildcardListeners($eventName); + isset($this->wildcards[$eventName]) || + $this->hasWildcardListeners($eventName); } /** @@ -256,9 +258,9 @@ public function dispatch($event, $payload = [], $halt = false) // dispatching this event on the next successful DB transaction commit. if ($isEventObject && $payload[0] instanceof ShouldDispatchAfterCommit && - ! is_null($transactions = $this->resolveTransactionManager())) { + !is_null($transactions = $this->resolveTransactionManager())) { $transactions->addCallback( - fn () => $this->invokeListeners($event, $payload, $halt) + fn() => $this->invokeListeners($event, $payload, $halt) ); return null; @@ -289,7 +291,7 @@ protected function invokeListeners($event, $payload, $halt = false) // If a response is returned from the listener and event halting is enabled // we will just return this response, and not call the rest of the event // listeners. Otherwise we will add the response on the response list. - if ($halt && ! is_null($response)) { + if ($halt && !is_null($response)) { return $response; } @@ -331,8 +333,8 @@ protected function parseEventAndPayload($event, $payload) protected function shouldBroadcast(array $payload) { return isset($payload[0]) && - $payload[0] instanceof ShouldBroadcast && - $this->broadcastWhen($payload[0]); + $payload[0] instanceof ShouldBroadcast && + $this->broadcastWhen($payload[0]); } /** @@ -344,7 +346,7 @@ protected function shouldBroadcast(array $payload) protected function broadcastWhen($event) { return method_exists($event, 'broadcastWhen') - ? $event->broadcastWhen() : true; + ? $event->broadcastWhen() : true; } /** @@ -372,8 +374,8 @@ public function getListeners($eventName) ); return class_exists($eventName, false) - ? $this->addInterfaceListeners($eventName, $listeners) - : $listeners; + ? $this->addInterfaceListeners($eventName, $listeners) + : $listeners; } /** @@ -489,10 +491,10 @@ public function createClassListener($listener, $wildcard = false) protected function createClassCallable($listener) { [$class, $method] = is_array($listener) - ? $listener - : $this->parseClassCallable($listener); + ? $listener + : $this->parseClassCallable($listener); - if (! method_exists($class, $method)) { + if (!method_exists($class, $method)) { $method = '__invoke'; } @@ -503,8 +505,8 @@ protected function createClassCallable($listener) $listener = $this->container->make($class); return $this->handlerShouldBeDispatchedAfterDatabaseTransactions($listener) - ? $this->createCallbackForListenerRunningAfterCommits($listener, $method) - : [$listener, $method]; + ? $this->createCallbackForListenerRunningAfterCommits($listener, $method) + : [$listener, $method]; } /** @@ -565,7 +567,7 @@ protected function handlerShouldBeDispatchedAfterDatabaseTransactions($listener) { return (($listener->afterCommit ?? null) || $listener instanceof ShouldHandleEventsAfterCommit) && - $this->resolveTransactionManager(); + $this->resolveTransactionManager(); } /** @@ -618,13 +620,23 @@ protected function queueHandler($class, $method, $arguments) { [$listener, $job] = $this->createListenerAndJob($class, $method, $arguments); + $listenerReflectionClass = null; + + /** + * We determine connection and queue based on the ways the user may specify it for the listener, + * stopping once we find the first value set. In order of precedence: via* methods, + * property, and finally On* attributes. + */ $connection = $this->resolveQueue()->connection(method_exists($listener, 'viaConnection') ? (isset($arguments[0]) ? $listener->viaConnection($arguments[0]) : $listener->viaConnection()) - : $listener->connection ?? null); + : $listener->connection + ?? enum_value($this->getConnectionFromOnConnectionAttribute($listenerReflectionClass ??= new ReflectionClass($listener))) + ); $queue = method_exists($listener, 'viaQueue') ? (isset($arguments[0]) ? $listener->viaQueue($arguments[0]) : $listener->viaQueue()) - : $listener->queue ?? null; + : $listener->queue + ?? enum_value($this->getQueueFromOnQueueAttribute($listenerReflectionClass ?? new ReflectionClass($listener))); $delay = method_exists($listener, 'withDelay') ? (isset($arguments[0]) ? $listener->withDelay($arguments[0]) : $listener->withDelay()) @@ -647,9 +659,11 @@ protected function createListenerAndJob($class, $method, $arguments) { $listener = (new ReflectionClass($class))->newInstanceWithoutConstructor(); - return [$listener, $this->propagateListenerOptions( - $listener, new CallQueuedListener($class, $method, $arguments) - )]; + return [ + $listener, $this->propagateListenerOptions( + $listener, new CallQueuedListener($class, $method, $arguments) + ) + ]; } /** @@ -670,7 +684,8 @@ protected function propagateListenerOptions($listener, $job) $job->afterCommit = property_exists($listener, 'afterCommit') ? $listener->afterCommit : null; } - $job->backoff = method_exists($listener, 'backoff') ? $listener->backoff(...$data) : ($listener->backoff ?? null); + $job->backoff = method_exists($listener, 'backoff') ? $listener->backoff(... + $data) : ($listener->backoff ?? null); $job->maxExceptions = $listener->maxExceptions ?? null; $job->retryUntil = method_exists($listener, 'retryUntil') ? $listener->retryUntil(...$data) : null; $job->shouldBeEncrypted = $listener instanceof ShouldBeEncrypted; diff --git a/tests/Integration/Queue/QueuedListenersTest.php b/tests/Integration/Queue/QueuedListenersTest.php index 6268ac040549..e548b48d817f 100644 --- a/tests/Integration/Queue/QueuedListenersTest.php +++ b/tests/Integration/Queue/QueuedListenersTest.php @@ -5,7 +5,11 @@ use Event; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Events\CallQueuedListener; +use Illuminate\Foundation\Queue\OnConnection; +use Illuminate\Foundation\Queue\OnQueue; +use Illuminate\Queue\Events\JobProcessing; use Orchestra\Testbench\TestCase; +use PHPUnit\Framework\Attributes\DataProvider; use Queue; class QueuedListenersTest extends TestCase @@ -29,6 +33,39 @@ public function testListenersCanBeQueuedOptionally() return $job->class == QueuedListenersTestListenerShouldNotQueue::class; }); } + + #[DataProvider('queueAndConnectionFromAttributesDataProvider')] + public function testListenerCanSetQueueAndConnectionFromAttributes( + string $className, + string $connectionName, + string $queueName + ) + { + config(["queue.connections.{$connectionName}" => ['driver' => 'sync']]); + + Event::listen(QueuedListenersTestEvent::class, $className); + + Queue::before(function(JobProcessing $jobProcessing) use ($connectionName, $queueName) { + $this->assertSame($connectionName, $jobProcessing->connectionName); + $actualQueue = (new \ReflectionProperty($jobProcessing->job::class, 'queue'))->getValue($jobProcessing->job); + $this->assertSame($queueName, $actualQueue); + }); + + Event::dispatch(new QueuedListenersTestEvent); + } + + /** + * @return array + */ + public static function queueAndConnectionFromAttributesDataProvider(): array + { + return [ + 'connection and queue are string attributes' => [QueuedListenerWithStringAttributes::class, 'connection-string', 'queue-string'], + 'connection and queue are enum attributes' => [QueuedListenerWithEnumAttributes::class, 'connection_name_from_enum', 'queue_name_from_enum'], + 'connection and queue are from via methods' => [QueuedListenerWithOnQueueAndOnConnectionMethods::class, 'connection-from-method', 'queue-from-method'], + 'connection and queue are from properties' => [QueuedListenerWithQueueAndConnectionProperties::class, 'connection-from-property', 'queue-from-property'], + ]; + } } class QueuedListenersTestEvent @@ -51,3 +88,49 @@ public function shouldQueue() return false; } } + +#[OnConnection('connection-string')] +#[OnQueue('queue-string')] +class QueuedListenerWithStringAttributes implements ShouldQueue +{ + public function __invoke() {} +} + +#[OnConnection(QueuedListenersTestEnum::connection_name_from_enum)] +#[OnQueue(QueuedListenersTestEnum::queue_name_from_enum)] +class QueuedListenerWithEnumAttributes implements ShouldQueue { + public function __invoke() {} +} + +#[OnConnection(QueuedListenersTestEnum::connection_name_from_enum)] +#[OnQueue('should-not-see-this')] +class QueuedListenerWithOnQueueAndOnConnectionMethods implements ShouldQueue +{ + public function __invoke() {} + + public function viaQueue(): string + { + return 'queue-from-method'; + } + + public function viaConnection(): string + { + return 'connection-from-method'; + } +} + +#[OnConnection('should-not-see-this')] +#[OnQueue(QueuedListenersTestEnum::queue_name_from_enum)] +class QueuedListenerWithQueueAndConnectionProperties implements ShouldQueue +{ + public $queue = 'queue-from-property'; + public $connection = 'connection-from-property'; + + public function __invoke() {} +} + +enum QueuedListenersTestEnum +{ + case queue_name_from_enum; + case connection_name_from_enum; +} From 9932a59ac21d8e265c286f547dea97c2bcd85bcb Mon Sep 17 00:00:00 2001 From: Luke Kuzmish Date: Sat, 25 Jan 2025 07:39:59 -0500 Subject: [PATCH 12/16] revert style --- src/Illuminate/Events/Dispatcher.php | 43 +++++++++++++--------------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/src/Illuminate/Events/Dispatcher.php b/src/Illuminate/Events/Dispatcher.php index 90c2459ad9ba..813cfc35e377 100755 --- a/src/Illuminate/Events/Dispatcher.php +++ b/src/Illuminate/Events/Dispatcher.php @@ -135,8 +135,8 @@ protected function setupWildcardListen($event, $listener) public function hasListeners($eventName) { return isset($this->listeners[$eventName]) || - isset($this->wildcards[$eventName]) || - $this->hasWildcardListeners($eventName); + isset($this->wildcards[$eventName]) || + $this->hasWildcardListeners($eventName); } /** @@ -258,9 +258,9 @@ public function dispatch($event, $payload = [], $halt = false) // dispatching this event on the next successful DB transaction commit. if ($isEventObject && $payload[0] instanceof ShouldDispatchAfterCommit && - !is_null($transactions = $this->resolveTransactionManager())) { + ! is_null($transactions = $this->resolveTransactionManager())) { $transactions->addCallback( - fn() => $this->invokeListeners($event, $payload, $halt) + fn () => $this->invokeListeners($event, $payload, $halt) ); return null; @@ -291,7 +291,7 @@ protected function invokeListeners($event, $payload, $halt = false) // If a response is returned from the listener and event halting is enabled // we will just return this response, and not call the rest of the event // listeners. Otherwise we will add the response on the response list. - if ($halt && !is_null($response)) { + if ($halt && ! is_null($response)) { return $response; } @@ -333,8 +333,8 @@ protected function parseEventAndPayload($event, $payload) protected function shouldBroadcast(array $payload) { return isset($payload[0]) && - $payload[0] instanceof ShouldBroadcast && - $this->broadcastWhen($payload[0]); + $payload[0] instanceof ShouldBroadcast && + $this->broadcastWhen($payload[0]); } /** @@ -346,7 +346,7 @@ protected function shouldBroadcast(array $payload) protected function broadcastWhen($event) { return method_exists($event, 'broadcastWhen') - ? $event->broadcastWhen() : true; + ? $event->broadcastWhen() : true; } /** @@ -374,8 +374,8 @@ public function getListeners($eventName) ); return class_exists($eventName, false) - ? $this->addInterfaceListeners($eventName, $listeners) - : $listeners; + ? $this->addInterfaceListeners($eventName, $listeners) + : $listeners; } /** @@ -491,10 +491,10 @@ public function createClassListener($listener, $wildcard = false) protected function createClassCallable($listener) { [$class, $method] = is_array($listener) - ? $listener - : $this->parseClassCallable($listener); + ? $listener + : $this->parseClassCallable($listener); - if (!method_exists($class, $method)) { + if (! method_exists($class, $method)) { $method = '__invoke'; } @@ -505,8 +505,8 @@ protected function createClassCallable($listener) $listener = $this->container->make($class); return $this->handlerShouldBeDispatchedAfterDatabaseTransactions($listener) - ? $this->createCallbackForListenerRunningAfterCommits($listener, $method) - : [$listener, $method]; + ? $this->createCallbackForListenerRunningAfterCommits($listener, $method) + : [$listener, $method]; } /** @@ -567,7 +567,7 @@ protected function handlerShouldBeDispatchedAfterDatabaseTransactions($listener) { return (($listener->afterCommit ?? null) || $listener instanceof ShouldHandleEventsAfterCommit) && - $this->resolveTransactionManager(); + $this->resolveTransactionManager(); } /** @@ -659,11 +659,9 @@ protected function createListenerAndJob($class, $method, $arguments) { $listener = (new ReflectionClass($class))->newInstanceWithoutConstructor(); - return [ - $listener, $this->propagateListenerOptions( - $listener, new CallQueuedListener($class, $method, $arguments) - ) - ]; + return [$listener, $this->propagateListenerOptions( + $listener, new CallQueuedListener($class, $method, $arguments) + )]; } /** @@ -684,8 +682,7 @@ protected function propagateListenerOptions($listener, $job) $job->afterCommit = property_exists($listener, 'afterCommit') ? $listener->afterCommit : null; } - $job->backoff = method_exists($listener, 'backoff') ? $listener->backoff(... - $data) : ($listener->backoff ?? null); + $job->backoff = method_exists($listener, 'backoff') ? $listener->backoff(...$data) : ($listener->backoff ?? null); $job->maxExceptions = $listener->maxExceptions ?? null; $job->retryUntil = method_exists($listener, 'retryUntil') ? $listener->retryUntil(...$data) : null; $job->shouldBeEncrypted = $listener instanceof ShouldBeEncrypted; From be610f9603ff34601e1cac5228d044ab41544313 Mon Sep 17 00:00:00 2001 From: Luke Kuzmish Date: Sat, 25 Jan 2025 07:41:11 -0500 Subject: [PATCH 13/16] keeping it real with StyleCI --- src/Illuminate/Events/Dispatcher.php | 1 + src/Illuminate/Mail/Mailable.php | 1 + .../Integration/Queue/QueuedListenersTest.php | 24 ++++++++++++------- tests/Support/SupportMailTest.php | 3 --- 4 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/Illuminate/Events/Dispatcher.php b/src/Illuminate/Events/Dispatcher.php index 813cfc35e377..34e8384cd21e 100755 --- a/src/Illuminate/Events/Dispatcher.php +++ b/src/Illuminate/Events/Dispatcher.php @@ -21,6 +21,7 @@ use Illuminate\Support\Traits\Macroable; use Illuminate\Support\Traits\ReflectsClosures; use ReflectionClass; + use function Illuminate\Support\enum_value; class Dispatcher implements DispatcherContract diff --git a/src/Illuminate/Mail/Mailable.php b/src/Illuminate/Mail/Mailable.php index 0d976ec70135..50da3ec1e0b6 100644 --- a/src/Illuminate/Mail/Mailable.php +++ b/src/Illuminate/Mail/Mailable.php @@ -28,6 +28,7 @@ use Symfony\Component\Mailer\Header\MetadataHeader; use Symfony\Component\Mailer\Header\TagHeader; use Symfony\Component\Mime\Address; + use function Illuminate\Support\enum_value; class Mailable implements MailableContract, Renderable diff --git a/tests/Integration/Queue/QueuedListenersTest.php b/tests/Integration/Queue/QueuedListenersTest.php index e548b48d817f..e2b234abad79 100644 --- a/tests/Integration/Queue/QueuedListenersTest.php +++ b/tests/Integration/Queue/QueuedListenersTest.php @@ -39,13 +39,12 @@ public function testListenerCanSetQueueAndConnectionFromAttributes( string $className, string $connectionName, string $queueName - ) - { + ) { config(["queue.connections.{$connectionName}" => ['driver' => 'sync']]); Event::listen(QueuedListenersTestEvent::class, $className); - Queue::before(function(JobProcessing $jobProcessing) use ($connectionName, $queueName) { + Queue::before(function (JobProcessing $jobProcessing) use ($connectionName, $queueName) { $this->assertSame($connectionName, $jobProcessing->connectionName); $actualQueue = (new \ReflectionProperty($jobProcessing->job::class, 'queue'))->getValue($jobProcessing->job); $this->assertSame($queueName, $actualQueue); @@ -93,20 +92,27 @@ public function shouldQueue() #[OnQueue('queue-string')] class QueuedListenerWithStringAttributes implements ShouldQueue { - public function __invoke() {} + public function __invoke() + { + } } #[OnConnection(QueuedListenersTestEnum::connection_name_from_enum)] #[OnQueue(QueuedListenersTestEnum::queue_name_from_enum)] -class QueuedListenerWithEnumAttributes implements ShouldQueue { - public function __invoke() {} +class QueuedListenerWithEnumAttributes implements ShouldQueue +{ + public function __invoke() + { + } } #[OnConnection(QueuedListenersTestEnum::connection_name_from_enum)] #[OnQueue('should-not-see-this')] class QueuedListenerWithOnQueueAndOnConnectionMethods implements ShouldQueue { - public function __invoke() {} + public function __invoke() + { + } public function viaQueue(): string { @@ -126,7 +132,9 @@ class QueuedListenerWithQueueAndConnectionProperties implements ShouldQueue public $queue = 'queue-from-property'; public $connection = 'connection-from-property'; - public function __invoke() {} + public function __invoke() + { + } } enum QueuedListenersTestEnum diff --git a/tests/Support/SupportMailTest.php b/tests/Support/SupportMailTest.php index c0efdc52b132..a2e21d3ed765 100644 --- a/tests/Support/SupportMailTest.php +++ b/tests/Support/SupportMailTest.php @@ -12,7 +12,6 @@ use Illuminate\Support\Facades\Queue; use Orchestra\Testbench\TestCase; use PHPUnit\Framework\Attributes\DataProvider; -use PHPUnit\Framework\Attributes\TestWith; class SupportMailTest extends TestCase { @@ -86,7 +85,6 @@ public function testQueuedMailableRespectsOnConnectionAttribute(string $mailable }); Mail::send(new $mailableClass()); - } #[DataProvider('connectionDataProvider')] @@ -190,7 +188,6 @@ public function build() } } - #[OnQueue(SupportMailTestEnum::Queue)] #[OnConnection(SupportMailTestEnum::Connection)] class TestMailWithOnQueueAndOnConnectionSetAndBothPropertiesSet extends Mailable implements ShouldQueue From ab52e43cb919d85c55608f625d736f9b3f64d8aa Mon Sep 17 00:00:00 2001 From: Luke Kuzmish Date: Sat, 25 Jan 2025 07:47:03 -0500 Subject: [PATCH 14/16] happy static analysis --- src/Illuminate/Events/Dispatcher.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Illuminate/Events/Dispatcher.php b/src/Illuminate/Events/Dispatcher.php index 34e8384cd21e..bc142835f1b9 100755 --- a/src/Illuminate/Events/Dispatcher.php +++ b/src/Illuminate/Events/Dispatcher.php @@ -621,8 +621,6 @@ protected function queueHandler($class, $method, $arguments) { [$listener, $job] = $this->createListenerAndJob($class, $method, $arguments); - $listenerReflectionClass = null; - /** * We determine connection and queue based on the ways the user may specify it for the listener, * stopping once we find the first value set. In order of precedence: via* methods, @@ -631,7 +629,7 @@ protected function queueHandler($class, $method, $arguments) $connection = $this->resolveQueue()->connection(method_exists($listener, 'viaConnection') ? (isset($arguments[0]) ? $listener->viaConnection($arguments[0]) : $listener->viaConnection()) : $listener->connection - ?? enum_value($this->getConnectionFromOnConnectionAttribute($listenerReflectionClass ??= new ReflectionClass($listener))) + ?? enum_value($this->getConnectionFromOnConnectionAttribute($listenerReflectionClass = new ReflectionClass($listener))) ); $queue = method_exists($listener, 'viaQueue') From bce38673aff089d17137511bfbdea321baf020b6 Mon Sep 17 00:00:00 2001 From: Luke Kuzmish Date: Sat, 25 Jan 2025 14:19:48 -0500 Subject: [PATCH 15/16] notifications --- .../Notifications/NotificationSender.php | 19 ++- .../NotificationQueueAndConnectionTest.php | 128 ++++++++++++++++++ 2 files changed, 144 insertions(+), 3 deletions(-) create mode 100644 tests/Integration/Notifications/NotificationQueueAndConnectionTest.php diff --git a/src/Illuminate/Notifications/NotificationSender.php b/src/Illuminate/Notifications/NotificationSender.php index cea407f70b9a..3e2f34a559ee 100644 --- a/src/Illuminate/Notifications/NotificationSender.php +++ b/src/Illuminate/Notifications/NotificationSender.php @@ -6,15 +6,18 @@ use Illuminate\Contracts\Translation\HasLocalePreference; use Illuminate\Database\Eloquent\Collection as EloquentCollection; use Illuminate\Database\Eloquent\Model; +use Illuminate\Foundation\Queue\InteractsWithQueueAndConnection; use Illuminate\Notifications\Events\NotificationSending; use Illuminate\Notifications\Events\NotificationSent; use Illuminate\Support\Collection; use Illuminate\Support\Str; use Illuminate\Support\Traits\Localizable; +use function Illuminate\Support\enum_value; + class NotificationSender { - use Localizable; + use InteractsWithQueueAndConnection, Localizable; /** * The notification manager instance. @@ -199,13 +202,23 @@ protected function queueNotification($notifiables, $notification) $notification->locale = $this->locale; } - $connection = $notification->connection; + $connection = enum_value( + $notification->connection + ?? $this->getConnectionFromOnConnectionAttribute( + $notifiableReflectionClass = new \ReflectionClass($original) + ) + ); if (method_exists($notification, 'viaConnections')) { $connection = $notification->viaConnections()[$channel] ?? null; } - $queue = $notification->queue; + $queue = enum_value( + $notification->queue + ?? $this->getQueueFromOnQueueAttribute( + $notifiableReflectionClass ?? new \ReflectionClass($original) + ) + ); if (method_exists($notification, 'viaQueues')) { $queue = $notification->viaQueues()[$channel] ?? null; diff --git a/tests/Integration/Notifications/NotificationQueueAndConnectionTest.php b/tests/Integration/Notifications/NotificationQueueAndConnectionTest.php new file mode 100644 index 000000000000..6191e1b8d0f3 --- /dev/null +++ b/tests/Integration/Notifications/NotificationQueueAndConnectionTest.php @@ -0,0 +1,128 @@ +id(); + }); + } + + #[DataProvider('notificationWithAttributesDataProvider')] + public function testNotificationWithAttributesRespectsAttributes( + string $className, + string $expectedConnection, + string $expectedQueue + ) { + config(["queue.connections.{$expectedConnection}" => ['driver' => 'sync']]); + + Queue::before(function (JobProcessing $jobProcessing) use ($expectedConnection, $expectedQueue) { + $this->assertSame($expectedConnection, $jobProcessing->connectionName); + $actualQueue = (new \ReflectionProperty($jobProcessing->job::class, + 'queue'))->getValue($jobProcessing->job); + $this->assertSame($expectedQueue, $actualQueue); + }); + + (new UserStub())->notify(new $className); + } + + /** + * @return array + */ + public static function notificationWithAttributesDataProvider(): array + { + return [ + 'string attribute values' => [ + NotificationQueueAndConnectionTestWithStringAttributesNotification::class, 'connection-string', 'queue-string' + ], + 'enum attribute values' => [ + NotificationQueueAndConnectionTestWithEnumAttributesNotification::class, 'my_connection_name', 'my_queue_name', + ], + 'prefers class properties over attributes' => [ + NotificationQueueAndConnectionTestPrefersPropertiesNotification::class, 'connection_from_constructor', 'queue_from_constructor', + ], + 'can set queue and connection properties as enums' => [ + NotificationQueueAndConnectionTestCanSetConnectionAndQueueAsEnums::class, 'my_connection_name', 'my_queue_name', + ], + ]; + } +} + + +class NotificationStub extends Notification implements ShouldQueue +{ + public function via($notifiable) + { + return ['mail']; + } +} + +#[OnConnection('connection-string')] +#[OnQueue('queue-string')] +class NotificationQueueAndConnectionTestWithStringAttributesNotification extends NotificationStub +{ + use Queueable; +} +#[OnConnection(NotificationQueueAndConnectionTestEnum::my_connection_name)] +#[OnQueue(NotificationQueueAndConnectionTestEnum::my_queue_name)] +class NotificationQueueAndConnectionTestWithEnumAttributesNotification extends NotificationStub +{ + use Queueable; +} + +#[OnConnection(NotificationQueueAndConnectionTestEnum::my_connection_name)] +#[OnQueue(NotificationQueueAndConnectionTestEnum::my_queue_name)] +class NotificationQueueAndConnectionTestPrefersPropertiesNotification extends NotificationStub +{ + use Queueable; + + public function __construct() + { + $this->onConnection('connection_from_constructor'); + $this->onQueue('queue_from_constructor'); + } +} + +class NotificationQueueAndConnectionTestCanSetConnectionAndQueueAsEnums extends NotificationStub +{ + use Queueable; + + public function __construct() { + $this->queue = NotificationQueueAndConnectionTestEnum::my_queue_name; + $this->connection = NotificationQueueAndConnectionTestEnum::my_connection_name; + } +} + + +class UserStub extends Model +{ + use Notifiable; + + protected $table = 'users'; +} + +enum NotificationQueueAndConnectionTestEnum +{ + case my_queue_name; + case my_connection_name; +} From a6f49bd697df6067bf040bc1b65b4653519796e8 Mon Sep 17 00:00:00 2001 From: Luke Kuzmish Date: Sat, 25 Jan 2025 14:21:07 -0500 Subject: [PATCH 16/16] style --- src/Illuminate/Notifications/NotificationSender.php | 4 ++-- .../Notifications/NotificationQueueAndConnectionTest.php | 7 +++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Illuminate/Notifications/NotificationSender.php b/src/Illuminate/Notifications/NotificationSender.php index 3e2f34a559ee..f6ebd77a6182 100644 --- a/src/Illuminate/Notifications/NotificationSender.php +++ b/src/Illuminate/Notifications/NotificationSender.php @@ -206,7 +206,7 @@ protected function queueNotification($notifiables, $notification) $notification->connection ?? $this->getConnectionFromOnConnectionAttribute( $notifiableReflectionClass = new \ReflectionClass($original) - ) + ) ); if (method_exists($notification, 'viaConnections')) { @@ -217,7 +217,7 @@ protected function queueNotification($notifiables, $notification) $notification->queue ?? $this->getQueueFromOnQueueAttribute( $notifiableReflectionClass ?? new \ReflectionClass($original) - ) + ) ); if (method_exists($notification, 'viaQueues')) { diff --git a/tests/Integration/Notifications/NotificationQueueAndConnectionTest.php b/tests/Integration/Notifications/NotificationQueueAndConnectionTest.php index 6191e1b8d0f3..ff361ce5cedb 100644 --- a/tests/Integration/Notifications/NotificationQueueAndConnectionTest.php +++ b/tests/Integration/Notifications/NotificationQueueAndConnectionTest.php @@ -53,7 +53,7 @@ public static function notificationWithAttributesDataProvider(): array { return [ 'string attribute values' => [ - NotificationQueueAndConnectionTestWithStringAttributesNotification::class, 'connection-string', 'queue-string' + NotificationQueueAndConnectionTestWithStringAttributesNotification::class, 'connection-string', 'queue-string', ], 'enum attribute values' => [ NotificationQueueAndConnectionTestWithEnumAttributesNotification::class, 'my_connection_name', 'my_queue_name', @@ -68,7 +68,6 @@ public static function notificationWithAttributesDataProvider(): array } } - class NotificationStub extends Notification implements ShouldQueue { public function via($notifiable) @@ -107,13 +106,13 @@ class NotificationQueueAndConnectionTestCanSetConnectionAndQueueAsEnums extends { use Queueable; - public function __construct() { + public function __construct() + { $this->queue = NotificationQueueAndConnectionTestEnum::my_queue_name; $this->connection = NotificationQueueAndConnectionTestEnum::my_connection_name; } } - class UserStub extends Model { use Notifiable;