Giter VIP home page Giter VIP logo

Comments (4)

rhurling avatar rhurling commented on August 16, 2024 1

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.

rhurling avatar rhurling commented on August 16, 2024

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.

masterix21 avatar masterix21 commented on August 16, 2024

I'm closing here due to the inactivity.

from laravel-multitenancy.

illambo avatar illambo commented on August 16, 2024

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)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.