diff --git a/src/Illuminate/Events/Dispatcher.php b/src/Illuminate/Events/Dispatcher.php index 5a3c9702e198..bc142835f1b9 100755 --- a/src/Illuminate/Events/Dispatcher.php +++ b/src/Illuminate/Events/Dispatcher.php @@ -14,6 +14,7 @@ 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; @@ -21,9 +22,11 @@ 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. @@ -618,13 +621,21 @@ protected function queueHandler($class, $method, $arguments) { [$listener, $job] = $this->createListenerAndJob($class, $method, $arguments); + /** + * 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()) diff --git a/src/Illuminate/Foundation/Bus/PendingDispatch.php b/src/Illuminate/Foundation/Bus/PendingDispatch.php index f7f2d0ed71bc..ae96e63d05da 100644 --- a/src/Illuminate/Foundation/Bus/PendingDispatch.php +++ b/src/Illuminate/Foundation/Bus/PendingDispatch.php @@ -7,10 +7,12 @@ use Illuminate\Contracts\Bus\Dispatcher; use Illuminate\Contracts\Cache\Repository as Cache; use Illuminate\Contracts\Queue\ShouldBeUnique; +use Illuminate\Foundation\Queue\InteractsWithQueueAndConnection; use Illuminate\Foundation\Queue\InteractsWithUniqueJobs; class PendingDispatch { + use InteractsWithQueueAndConnection; use InteractsWithUniqueJobs; /** @@ -189,6 +191,36 @@ 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) { + if ($queue = $this->getQueueFromOnQueueAttribute($reflectionClass)) { + $this->onQueue($queue); + } + } + + if (! $hasConnectionSet) { + if ($connection = $this->getConnectionFromOnConnectionAttribute($reflectionClass)) { + $this->onConnection($connection); + } + } + } + /** * Dynamically proxy methods to the underlying job. * @@ -211,6 +243,7 @@ public function __call($method, $parameters) public function __destruct() { $this->addUniqueJobInformationToContext($this->job); + $this->setQueueAndConnectionFromAttributesIfNotSet(); if (! $this->shouldDispatch()) { $this->removeUniqueJobInformationFromContext($this->job); diff --git a/src/Illuminate/Foundation/Queue/InteractsWithQueueAndConnection.php b/src/Illuminate/Foundation/Queue/InteractsWithQueueAndConnection.php new file mode 100644 index 000000000000..9e4310facd3d --- /dev/null +++ b/src/Illuminate/Foundation/Queue/InteractsWithQueueAndConnection.php @@ -0,0 +1,40 @@ +getAttributes(OnConnection::class); + if ($onConnection === []) { + return null; + } + + return $onConnection[0]->newInstance()->connection; + } + + /** + * Extract the queue from OnQueue attribute if present. + * + * @param \ReflectionClass $reflectionClass + * @return string|\UnitEnum|null + */ + protected function getQueueFromOnQueueAttribute(ReflectionClass $reflectionClass) + { + $onQueue = $reflectionClass->getAttributes(OnQueue::class); + if ($onQueue === []) { + return null; + } + + return $onQueue[0]->newInstance()->queue; + } +} diff --git a/src/Illuminate/Foundation/Queue/OnConnection.php b/src/Illuminate/Foundation/Queue/OnConnection.php new file mode 100644 index 000000000000..913d065f23af --- /dev/null +++ b/src/Illuminate/Foundation/Queue/OnConnection.php @@ -0,0 +1,17 @@ +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 +244,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 +462,38 @@ protected function buildSubject($message) return $this; } + /** + * Get the queue specified on the class or from the OnQueue attribute. + * + * @return string|null + */ + protected function getQueue() + { + $queue = property_exists($this, 'queue') ? $this->queue : null; + + if ($queue === null) { + $queue = $this->getQueueFromOnQueueAttribute(new ReflectionClass($this)); + } + + return $queue !== null ? enum_value($queue) : null; + } + + /** + * Get the connection specified on the class or from the OnConnection attribute. + * + * @return string|null + */ + protected function getConnection() + { + $connection = property_exists($this, 'connection') ? $this->connection : null; + + if ($connection === null) { + $connection = $this->getConnectionFromOnConnectionAttribute(new ReflectionClass($this)); + } + + return $connection !== null ? enum_value($connection) : null; + } + /** * Add all of the attachments to the message. * diff --git a/src/Illuminate/Notifications/NotificationSender.php b/src/Illuminate/Notifications/NotificationSender.php index cea407f70b9a..f6ebd77a6182 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/Bus/BusPendingDispatchWithAttributesTest.php b/tests/Integration/Bus/BusPendingDispatchWithAttributesTest.php new file mode 100644 index 000000000000..08ea3471abef --- /dev/null +++ b/tests/Integration/Bus/BusPendingDispatchWithAttributesTest.php @@ -0,0 +1,165 @@ +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'; + }); + } + + 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' + ); + } + + 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')] +#[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) + { + } +} + +#[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'; +} diff --git a/tests/Integration/Notifications/NotificationQueueAndConnectionTest.php b/tests/Integration/Notifications/NotificationQueueAndConnectionTest.php new file mode 100644 index 000000000000..ff361ce5cedb --- /dev/null +++ b/tests/Integration/Notifications/NotificationQueueAndConnectionTest.php @@ -0,0 +1,127 @@ +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; +} diff --git a/tests/Integration/Queue/QueuedListenersTest.php b/tests/Integration/Queue/QueuedListenersTest.php index 6268ac040549..e2b234abad79 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,38 @@ 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 +87,58 @@ 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; +} diff --git a/tests/Support/SupportMailTest.php b/tests/Support/SupportMailTest.php index 146a3ca331a5..a2e21d3ed765 100644 --- a/tests/Support/SupportMailTest.php +++ b/tests/Support/SupportMailTest.php @@ -2,9 +2,16 @@ 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\Queue\Events\JobProcessing; use Illuminate\Support\Facades\Mail; +use Illuminate\Support\Facades\Queue; use Orchestra\Testbench\TestCase; +use PHPUnit\Framework\Attributes\DataProvider; class SupportMailTest extends TestCase { @@ -39,6 +46,101 @@ public function testEmailSent() Mail::assertSent(TestMail::class); } + + #[DataProvider('queueDataProvider')] + public function testQueuedMailableRespectsOnQueueAttribute(string $mailableClass, string $queueName) + { + Queue::fake(); + + Mail::send(new $mailableClass()); + + Queue::assertPushedOn( + $queueName, + SendQueuedMailable::class, + fn ($job) => is_a($job->mailable, $mailableClass, true) + ); + } + + #[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) { + 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 @@ -53,3 +155,49 @@ 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 +{ +} + +#[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 +{ + public $queue = 'some-other-queue'; + public $connection = 'my-connection'; +} + +enum SupportMailTestEnum: string +{ + case Queue = 'queue-from-enum'; + case Connection = 'connection-from-enum'; +}