I just thought of a quick and dirty way that you can use to achieve this. I would create an abstract Base class like so;
abstract class BaseTenancyJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(public ?int $tenancyId = null)
{
$this->initialize();
}
public function initialize(): void
{
//this could have been set prior at the initial instantiation of the app
//when the app still had context because I would assume if a job gets fired
//it would mostly be when there was context but in case there was no context
//the tenancyId would then be passed in like the example you gave.
$this->tenancyId = $this->tenancyId ?? config('app.tenancy_id');
}
public function middleware(): array
{
return [
new PreserveTenancyConfig($this->tenancyId),
];
}
}
Then I’ll create a middleware that runs before the job is processed. This middleware is what is passed as part of the middleware in the BaseJob class. What it will do is to set the tenancy config. In cases like this, I feel database is just one of many things that could be needed, so that method should take care of everything including database change.
class PreserveTenancyConfig
{
public function __construct(public ?int $tenancyId = null) {}
/**
* Handle the job and preserve the config context.
*
* @param mixed $job
* @param callable $next
* @return void
*/
public function handle(mixed $job, callable $next): void
{
if (! is_null($this->tenancyId)) {
//do some query with the Id to resolve tenancy
$tenancy = Tenancy::find($this->tenancyId);
//TenancyService facade
//I've put the suggestion of a facade here as you may need
//to do a lot like get config, set config in different places
//you don't want to bother about instantiating the service class
//from time to time.
\App\Tenancy\Tenancy::setTenancyToConfig($tenancy);
}
$next($job);
}
The setTenancyToConfig()
method could look like this;
/**
* @param Tenancy $tenancy
* @return void
*/
public function setTenancyToConfig(Tenancy $tenancy): void
{
config()->set('app.tenancy_id', $tenancy->id);
config()->set('app.name', $tenancy->name);
config()->set('database.connections.'.$tenancy->database, $tenancy->database_connection);
config()->set('database.default', $tenancy->database);
}
Now the actual job could look like so;
class TestJob extends BaseTenancyJob
{
/**
* Create a new job instance.
*
* @param string $someValue
* @param int|null $tenancyId
*/
public function __construct(public string $someValue, public ?int $tenancyId = null)
{
parent::__construct($this->tenancyId);
}
/**
* Execute the job.
*
* @return void
*/
public function handle(): void
{
//if everything works out this should dump the tenancyId you have set
//it would also mean everything you have set will now take precedence
dump(config('app.tenancy_id'));
dump($this->someValue);
dump(User::count()); // A way to track data change as DB would have changed.
}
}
I don’t particularly like this solution but it should solve your problem.
Modifying your example the way it’s currently without any big modification based on the suggestion I’ve here would look like this;
class YourJob extends BaseTenantJob
{
/**
* Create a new job instance.
*/
public function __construct(private array $tenantDetails)
{
parent::__construct($this->tenantDetails);
}
/**
* Execute the job.
*
* @return void
*/
public function handle(): void
{
//Do whatever in your job
}
}
abstract class BaseTenantJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(private array $tenantDetails)
{
}
public function middleware(): array
{
return [
new PreserveTenantConfig($this->tenantDetails),
];
}
}
class PreserveTenantConfig
{
public function __construct(private array $tenantDetails) {}
/**
* Handle the job and preserve the config context.
*
* @param mixed $job
* @param callable $next
* @return void
*/
public function handle(mixed $job, callable $next): void
{
config()->set('database.connections.'.$tenantDetails['database'], $tenantDetails);
config()->set('database.default', $tenantDetails['database']);
$next($job);
}
NB: I’ve made some assumptions with tenantDetails
, adjust accordingly and this will definitely work.