diff --git a/config/multitenancy.php b/config/multitenancy.php index 5e4faa8..59d239f 100644 --- a/config/multitenancy.php +++ b/config/multitenancy.php @@ -1,5 +1,10 @@ MigrateTenantAction::class, ], + /* + * You can customize the way in which the package resolves the queueable to a job. + * + * For example, using the package laravel-actions (by Loris Leiva), you can + * resolve JobDecorator to getAction() like so: JobDecorator::class => 'getAction' + */ + 'queueable_to_job' => [ + SendQueuedMailable::class => 'mailable', + SendQueuedNotifications::class => 'notification', + CallQueuedClosure::class => 'closure', + CallQueuedListener::class => 'class', + BroadcastEvent::class => 'event', + ], + /* * Jobs tenant aware even if these don't implement the TenantAware interface. */ diff --git a/phpunit.xml.dist b/phpunit.xml.dist index d277999..fea497a 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -9,7 +9,7 @@ - + diff --git a/src/Actions/MakeQueueTenantAwareAction.php b/src/Actions/MakeQueueTenantAwareAction.php index 1d22229..b940dd8 100644 --- a/src/Actions/MakeQueueTenantAwareAction.php +++ b/src/Actions/MakeQueueTenantAwareAction.php @@ -2,8 +2,12 @@ namespace Spatie\Multitenancy\Actions; +use Illuminate\Mail\SendQueuedMailable; +use Illuminate\Notifications\SendQueuedNotifications; +use Illuminate\Queue\CallQueuedClosure; use Illuminate\Queue\Events\JobProcessing; use Illuminate\Queue\Events\JobRetryRequested; +use Illuminate\Support\Arr; use Illuminate\Support\Facades\Context; use Spatie\Multitenancy\Concerns\BindAsCurrentTenant; use Spatie\Multitenancy\Concerns\UsesMultitenancyConfig; @@ -44,9 +48,13 @@ protected function listenForJobsRetryRequested(): static protected function isTenantAware(JobProcessing|JobRetryRequested $event): bool { - $jobName = $this->getEventPayload($event)['data']['commandName']; + $payload = $this->getEventPayload($event); - $reflection = new \ReflectionClass($jobName); + $command = unserialize($payload['data']['command']); + + $job = $this->getJobFromQueueable($command); + + $reflection = new \ReflectionClass($job); if ($reflection->implementsInterface(TenantAware::class)) { return true; @@ -67,6 +75,21 @@ protected function isTenantAware(JobProcessing|JobRetryRequested $event): bool return config('multitenancy.queues_are_tenant_aware_by_default') === true; } + protected function getJobFromQueueable(object $queueable) + { + $job = Arr::get(config('multitenancy.queueable_to_job'), $queueable::class); + + if (! $job) { + return $queueable; + } + + if (method_exists($queueable, $job)) { + return $queueable->{$job}(); + } + + return $queueable->$job; + } + protected function getEventPayload($event): ?array { return match (true) { diff --git a/tests/Feature/TenantAwareJobs/QueuedBroadcastEventTest.php b/tests/Feature/TenantAwareJobs/QueuedBroadcastEventTest.php new file mode 100644 index 0000000..e890535 --- /dev/null +++ b/tests/Feature/TenantAwareJobs/QueuedBroadcastEventTest.php @@ -0,0 +1,55 @@ +set('multitenancy.queues_are_tenant_aware_by_default', true); + config()->set('queue.default', 'sync'); + config()->set('mail.default', 'log'); + + $this->tenant = Tenant::factory()->create(); +}); + +it('will fail when no tenant is present and listeners are tenant aware by default', function () { + config()->set('multitenancy.queues_are_tenant_aware_by_default', true); + + Event::listen(TestEvent::class, ListenerTenantAware::class); + + Broadcast::event(new BroadcastTenantAware("Hello world!")); +})->throws(CurrentTenantCouldNotBeDeterminedInTenantAwareJob::class); + +it('will not fail when no tenant is present and listeners are tenant aware by default', function () { + config()->set('multitenancy.queues_are_tenant_aware_by_default', true); + + Event::listen(TestEvent::class, ListenerNotTenantAware::class); + Broadcast::event(new BroadcastNotTenantAware("Hello world!")); + + $this->expectExceptionMessage("Method Illuminate\Events\Dispatcher::assertDispatchedTimes does not exist."); + + Event::assertDispatchedTimes(TestEvent::class); +}); + +it('will inject the current tenant id', function () { + config()->set('multitenancy.queues_are_tenant_aware_by_default', true); + + $this->tenant->makeCurrent(); + + Event::listen(TestEvent::class, ListenerNotTenantAware::class); + + expect( + Broadcast::event(new BroadcastTenantAware("Hello world!")) + )->toBeInstanceOf(\Illuminate\Broadcasting\PendingBroadcast::class); +}); diff --git a/tests/Feature/TenantAwareJobs/QueuedListenerTest.php b/tests/Feature/TenantAwareJobs/QueuedListenerTest.php new file mode 100644 index 0000000..9b1652e --- /dev/null +++ b/tests/Feature/TenantAwareJobs/QueuedListenerTest.php @@ -0,0 +1,52 @@ +set('multitenancy.queues_are_tenant_aware_by_default', true); + config()->set('queue.default', 'sync'); + config()->set('mail.default', 'log'); + + $this->tenant = Tenant::factory()->create(); +}); + +it('will fail when no tenant is present and listeners are tenant aware by default', function () { + config()->set('multitenancy.queues_are_tenant_aware_by_default', true); + + Event::listen(TestEvent::class, ListenerTenantAware::class); + + Event::dispatch(new TestEvent("Hello world!")); +})->throws(CurrentTenantCouldNotBeDeterminedInTenantAwareJob::class); + +it('will not fail when no tenant is present and listeners are tenant aware by default', function () { + config()->set('multitenancy.queues_are_tenant_aware_by_default', true); + + Event::listen(TestEvent::class, ListenerNotTenantAware::class); + Event::dispatch(new TestEvent("Hello world!")); + + $this->expectExceptionMessage("Method Illuminate\Events\Dispatcher::assertDispatchedTimes does not exist."); + + Event::assertDispatchedTimes(TestEvent::class); +}); + +it('will inject the current tenant id', function () { + config()->set('multitenancy.queues_are_tenant_aware_by_default', true); + + $this->tenant->makeCurrent(); + + Event::listen(TestEvent::class, ListenerNotTenantAware::class); + + expect( + Event::dispatch(new TestEvent("Hello world!")) + )->toEqual([0 => null]); +}); diff --git a/tests/Feature/TenantAwareJobs/QueuedMailableTest.php b/tests/Feature/TenantAwareJobs/QueuedMailableTest.php index d7eb69a..a94223f 100644 --- a/tests/Feature/TenantAwareJobs/QueuedMailableTest.php +++ b/tests/Feature/TenantAwareJobs/QueuedMailableTest.php @@ -3,6 +3,7 @@ use Illuminate\Support\Facades\Mail; use Spatie\Multitenancy\Exceptions\CurrentTenantCouldNotBeDeterminedInTenantAwareJob; use Spatie\Multitenancy\Models\Tenant; +use Spatie\Multitenancy\Tests\Feature\TenantAwareJobs\TestClasses\MailableNotTenantAware; use Spatie\Multitenancy\Tests\Feature\TenantAwareJobs\TestClasses\MailableTenantAware; beforeEach(function () { @@ -19,6 +20,16 @@ Mail::to('test@spatie.be')->queue(new MailableTenantAware()); })->throws(CurrentTenantCouldNotBeDeterminedInTenantAwareJob::class); +it('will not fail when no tenant is present and mailables are tenant aware by default', function () { + config()->set('multitenancy.queues_are_tenant_aware_by_default', true); + + Mail::to('test@spatie.be')->queue(new MailableNotTenantAware()); + + $this->expectExceptionMessage("Method Illuminate\Mail\Mailer::assertSentCount does not exist."); + + Mail::assertSentCount(1); +}); + it('will inject the current tenant id', function () { config()->set('multitenancy.queues_are_tenant_aware_by_default', true); diff --git a/tests/Feature/TenantAwareJobs/QueuedNotificationsTest.php b/tests/Feature/TenantAwareJobs/QueuedNotificationsTest.php index 1cfba24..0b07f0a 100644 --- a/tests/Feature/TenantAwareJobs/QueuedNotificationsTest.php +++ b/tests/Feature/TenantAwareJobs/QueuedNotificationsTest.php @@ -3,6 +3,7 @@ use Illuminate\Support\Facades\Notification; use Spatie\Multitenancy\Exceptions\CurrentTenantCouldNotBeDeterminedInTenantAwareJob; use Spatie\Multitenancy\Tests\Feature\Models\TenantNotifiable; +use Spatie\Multitenancy\Tests\Feature\TenantAwareJobs\TestClasses\NotificationNotTenantAware; use Spatie\Multitenancy\Tests\Feature\TenantAwareJobs\TestClasses\NotificationTenantAware; beforeEach(function () { @@ -21,6 +22,16 @@ Notification::assertNothingSent(); })->throws(CurrentTenantCouldNotBeDeterminedInTenantAwareJob::class); +it('will not fail when no tenant is present and mailables are tenant aware by default', function () { + config()->set('multitenancy.queues_are_tenant_aware_by_default', true); + + $this->tenant->notify((new NotificationNotTenantAware())); + + $this->expectExceptionMessage("Call to undefined method Illuminate\Notifications\Channels\MailChannel::assertCount()"); + + Notification::assertCount(1); +}); + it('will inject the current tenant id', function () { config()->set('multitenancy.queues_are_tenant_aware_by_default', true); diff --git a/tests/Feature/TenantAwareJobs/TestClasses/BroadcastNotTenantAware.php b/tests/Feature/TenantAwareJobs/TestClasses/BroadcastNotTenantAware.php new file mode 100644 index 0000000..36b587f --- /dev/null +++ b/tests/Feature/TenantAwareJobs/TestClasses/BroadcastNotTenantAware.php @@ -0,0 +1,24 @@ +view('mailable'); + } +} diff --git a/tests/Feature/TenantAwareJobs/TestClasses/NotificationNotTenantAware.php b/tests/Feature/TenantAwareJobs/TestClasses/NotificationNotTenantAware.php new file mode 100644 index 0000000..4383a37 --- /dev/null +++ b/tests/Feature/TenantAwareJobs/TestClasses/NotificationNotTenantAware.php @@ -0,0 +1,33 @@ +subject('Message') + ->greeting('Hello!') + ->line('Say goodbye!'); + } + + public function toArray($notifiable) + { + return [ ]; + } +} diff --git a/tests/Feature/TenantAwareJobs/TestClasses/TestEvent.php b/tests/Feature/TenantAwareJobs/TestClasses/TestEvent.php new file mode 100644 index 0000000..77e743c --- /dev/null +++ b/tests/Feature/TenantAwareJobs/TestClasses/TestEvent.php @@ -0,0 +1,16 @@ +