Comments (4)
My previous version had issues with batched and chained jobs (since the events don't fire in order but rather "nested"), so I rewrote it to this version, which seems to work at least locally (I'll test it in deployment next week):
<?php
namespace App\MultiTenancy\Actions;
use Illuminate\Bus\ChainedBatch;
use Illuminate\Queue\Events\JobExceptionOccurred;
use Illuminate\Queue\Events\JobProcessed;
use Illuminate\Queue\Events\JobProcessing;
use Illuminate\Queue\Events\JobRetryRequested;
use Illuminate\Queue\Queue;
use Laravel\Horizon\Events\JobFailed;
use Spatie\Multitenancy\Exceptions\CurrentTenantCouldNotBeDeterminedInTenantAwareJob;
use Spatie\Multitenancy\Models\Tenant;
class MakeQueueTenantAwareAction extends \Spatie\Multitenancy\Actions\MakeQueueTenantAwareAction
{
public function execute(): void
{
$this
->listenForJobsBeingQueued()
->listenForBeforeJobsEvents()
->listenForAfterJobsEvents();
}
protected function listenForBeforeJobsEvents(): static
{
app('events')->listen(JobProcessing::class, function (JobProcessing $event) {
$this->bindOrForgetCurrentTenant($event);
});
app('events')->listen(JobRetryRequested::class, function (JobRetryRequested $event) {
$this->bindOrForgetCurrentTenant($event);
});
return $this;
}
protected function listenForAfterJobsEvents(): static
{
app('events')->listen(JobProcessed::class, [$this, 'restoreContextTenant']);
app('events')->listen(JobExceptionOccurred::class, [$this, 'restoreContextTenant']);
app('events')->listen(JobFailed::class, [$this, 'restoreContextTenant']);
return $this;
}
protected function getEventPayload($event): ?array
{
return match (true) {
$event instanceof JobRetryRequested => $event->payload(),
$event instanceof JobProcessing, $event instanceof JobProcessed, $event instanceof JobExceptionOccurred, $event instanceof JobFailed => $event->job->payload(),
default => null,
};
}
protected function findTenant(
JobProcessing|JobRetryRequested|JobProcessed|JobExceptionOccurred|JobFailed $event
): Tenant {
$tenantId = $this->getEventPayload($event)['tenantId'] ?? null;
if ( ! $tenantId) {
$event->job->delete();
throw CurrentTenantCouldNotBeDeterminedInTenantAwareJob::noIdSet($event);
}
/** @var \Spatie\Multitenancy\Models\Tenant $tenant */
if ( ! $tenant = $this->getTenantModel()::find($tenantId)) {
$event->job->delete();
throw CurrentTenantCouldNotBeDeterminedInTenantAwareJob::noTenantFound($event);
}
return $tenant;
}
protected static array $tenantContexts = [];
protected function bindOrForgetCurrentTenant(
JobProcessing|JobRetryRequested|JobProcessed|JobExceptionOccurred|JobFailed $event
): void {
$payload = $this->getEventPayload($event);
static::$tenantContexts[] = $this->getTenantModel()::current()?->getKey();
if (array_key_exists('tenantId', $payload)) {
$this->bindAsCurrentTenant($this->findTenant($event)->makeCurrent());
return;
}
$this->getTenantModel()::forgetCurrent();
}
public function restoreContextTenant(
JobProcessing|JobRetryRequested|JobProcessed|JobExceptionOccurred|JobFailed $event
): void {
$contextTenant = array_pop(static::$tenantContexts);
if ($contextTenant === null) {
$this->getTenantModel()::forgetCurrent();
} else {
$this->getTenantModel()::find($contextTenant)?->makeCurrent();
}
}
}
from laravel-multitenancy.
The following is my first attempt to fix it by extending MakeQueueTenantAwareAction with my custom version:
<?php
namespace App\MultiTenancy\Actions;
use Illuminate\Queue\Events\JobExceptionOccurred;
use Illuminate\Queue\Events\JobProcessed;
use Illuminate\Queue\Events\JobProcessing;
use Illuminate\Queue\Events\JobRetryRequested;
use Spatie\Multitenancy\Exceptions\CurrentTenantCouldNotBeDeterminedInTenantAwareJob;
use Spatie\Multitenancy\Models\Tenant;
class MakeQueueTenantAwareAction extends \Spatie\Multitenancy\Actions\MakeQueueTenantAwareAction
{
public function execute(): void
{
$this
->listenForJobsBeingQueued()
->listenForBeforeJobsEvents()
->listenForAfterJobsEvents();
}
protected function listenForBeforeJobsEvents(): static
{
app('events')->listen(JobProcessing::class, function (JobProcessing $event) {
$this->bindOrForgetCurrentTenant($event);
});
app('events')->listen(JobRetryRequested::class, function (JobRetryRequested $event) {
$this->bindOrForgetCurrentTenant($event);
});
return $this;
}
protected function listenForAfterJobsEvents(): static
{
app('events')->listen(JobProcessed::class, function (JobProcessed $event) {
$this->restoreContextTenant();
});
app('events')->listen(JobExceptionOccurred::class, function (JobExceptionOccurred $event) {
$this->restoreContextTenant();
});
return $this;
}
protected function getEventPayload($event): ?array
{
return match (true) {
$event instanceof JobRetryRequested => $event->payload(),
$event instanceof JobProcessing, $event instanceof JobProcessed, $event instanceof JobExceptionOccurred => $event->job->payload(),
default => null,
};
}
protected function findTenant(JobProcessing|JobRetryRequested|JobProcessed|JobExceptionOccurred $event): Tenant
{
$tenantId = $this->getEventPayload($event)['tenantId'] ?? null;
if ( ! $tenantId) {
$event->job->delete();
throw CurrentTenantCouldNotBeDeterminedInTenantAwareJob::noIdSet($event);
}
/** @var \Spatie\Multitenancy\Models\Tenant $tenant */
if ( ! $tenant = $this->getTenantModel()::find($tenantId)) {
$event->job->delete();
throw CurrentTenantCouldNotBeDeterminedInTenantAwareJob::noTenantFound($event);
}
return $tenant;
}
private Tenant|null $contextTenant = null;
protected function bindOrForgetCurrentTenant(
JobProcessing|JobRetryRequested|JobProcessed|JobExceptionOccurred $event
): void {
$this->contextTenant = $this->getTenantModel()::current();
if (array_key_exists('tenantId', $this->getEventPayload($event))) {
$this->bindAsCurrentTenant($this->findTenant($event)->makeCurrent());
return;
}
$this->getTenantModel()::forgetCurrent();
}
protected function restoreContextTenant(): void
{
if ($this->contextTenant === null) {
$this->getTenantModel()::forgetCurrent();
} else {
$this->contextTenant->makeCurrent();
}
}
}
If I understand all the laravel job events correctly this should also isolate jobs from one another (if a job sets a tenant and does not call forget)
from laravel-multitenancy.
I'm closing here due to the inactivity.
from laravel-multitenancy.
Hi @masterix21 pls not close the issue, is still currently present for the referenced case (i have the same problem with sync queue connection).
from laravel-multitenancy.
Related Issues (20)
- Support for custom primary key on Tenant model HOT 1
- How can I access all tenant data at once?
- Tenant Not Set (Vapor + Octane) HOT 1
- Current tenant not determined when dispatching queued closures HOT 6
- When dispatching a Tenant Aware Job in sync mode from a test, any prior/current DB transaction is cleared HOT 2
- Model ignore tenant when add Observer HOT 2
- Tenant password reset token is being stored on landlord password_reset_tokens table HOT 1
- Prefixing cache doesn't work fine with file-store HOT 3
- Conflict with Rabbitmq package from vyuldashev HOT 1
- Scheduler : Large (longer) jobs, multiple tenants, stops.
- Tenant-Aware Job Scheduling with Horizon HOT 4
- base table or view not found with AWS SQS queue for job TenantAware HOT 12
- randomly receive Invalid catalog name: 1046 No database selected (Connection: mysql, SQL: insert into failed_jobs (uuid, connection, queue, payload, exception, failed_at) on queue sqs HOT 5
- Tenant Aware Queue Fails No Database Selected when running from supervisor in a multi tenant environment using AWS SQS FIFO HOT 2
- Update documentation at https://spatie.be to `v3` HOT 3
- in_array(): Argument #2 ($haystack) must be of type array, null given {"exception":"[object] (TypeError(code: 0): in_array(): Argument #2 ($haystack) must be of type array, null given at /spatie/laravel-multitenancy/src/Actions/MakeQueueTenantAwareAction.php:79) HOT 1
- Cache issue with spatie/laravel-permission in TenantAware Command HOT 5
- Issue with the has function on different connections HOT 1
- I want to Connect to mongo DB for some tables but it is giving "Database connection [mongodb] not configured.", exception: "InvalidArgumentException",…} error
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from laravel-multitenancy.