mirror of
https://github.com/archtechx/tenancy.git
synced 2026-05-06 19:24:04 +00:00
Merge branch 'master' into boilerplate-dev
This commit is contained in:
commit
5d047089ea
8 changed files with 95 additions and 7 deletions
|
|
@ -32,7 +32,7 @@
|
||||||
"league/flysystem-aws-s3-v3": "^3.12.2",
|
"league/flysystem-aws-s3-v3": "^3.12.2",
|
||||||
"doctrine/dbal": "^3.6.0",
|
"doctrine/dbal": "^3.6.0",
|
||||||
"spatie/valuestore": "^1.2.5",
|
"spatie/valuestore": "^1.2.5",
|
||||||
"pestphp/pest": "^3.0",
|
"pestphp/pest": "^4.0",
|
||||||
"larastan/larastan": "^3.0",
|
"larastan/larastan": "^3.0",
|
||||||
"league/flysystem-path-prefixing": "^3.0"
|
"league/flysystem-path-prefixing": "^3.0"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace Stancl\Tenancy\Bootstrappers;
|
namespace Stancl\Tenancy\Bootstrappers;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
use Illuminate\Foundation\Application;
|
use Illuminate\Foundation\Application;
|
||||||
use Illuminate\Session\FileSessionHandler;
|
use Illuminate\Session\FileSessionHandler;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
@ -75,8 +76,13 @@ class FilesystemTenancyBootstrapper implements TenancyBootstrapper
|
||||||
: $this->originalStoragePath . '/framework/cache';
|
: $this->originalStoragePath . '/framework/cache';
|
||||||
|
|
||||||
if (! is_dir($path)) {
|
if (! is_dir($path)) {
|
||||||
// Create tenant framework/cache directory if it does not exist
|
// Create tenant framework/cache directory if it does not exist.
|
||||||
mkdir($path, 0750, true);
|
// We ignore errors due to TOCTOU race conditions, instead we check for success below.
|
||||||
|
@mkdir($path, 0750, true);
|
||||||
|
|
||||||
|
if (! is_dir($path)) {
|
||||||
|
throw new Exception("Unable to create tenant storage directory [{$path}].");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($suffix === false) {
|
if ($suffix === false) {
|
||||||
|
|
@ -222,8 +228,13 @@ class FilesystemTenancyBootstrapper implements TenancyBootstrapper
|
||||||
: $this->originalStoragePath . '/framework/sessions';
|
: $this->originalStoragePath . '/framework/sessions';
|
||||||
|
|
||||||
if (! is_dir($path)) {
|
if (! is_dir($path)) {
|
||||||
// Create tenant framework/sessions directory if it does not exist
|
// Create tenant framework/sessions directory if it does not exist.
|
||||||
mkdir($path, 0750, true);
|
// We ignore errors due to TOCTOU race conditions, instead we check for success below.
|
||||||
|
@mkdir($path, 0750, true);
|
||||||
|
|
||||||
|
if (! is_dir($path)) {
|
||||||
|
throw new Exception("Unable to create tenant session directory [{$path}].");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->app['config']['session.files'] = $path;
|
$this->app['config']['session.files'] = $path;
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Scope;
|
use Illuminate\Database\Eloquent\Scope;
|
||||||
|
|
||||||
|
/** @implements Scope<Model> */
|
||||||
class PendingScope implements Scope
|
class PendingScope implements Scope
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
|
@ -57,8 +58,10 @@ class PendingScope implements Scope
|
||||||
{
|
{
|
||||||
$builder->macro('withoutPending', function (Builder $builder) {
|
$builder->macro('withoutPending', function (Builder $builder) {
|
||||||
$builder->withoutGlobalScope(static::class)
|
$builder->withoutGlobalScope(static::class)
|
||||||
->whereNull($builder->getModel()->getColumnForQuery('pending_since'))
|
->where(function (Builder $query) {
|
||||||
->orWhereNull($builder->getModel()->getDataColumn());
|
$query->whereNull($query->getModel()->getColumnForQuery('pending_since'))
|
||||||
|
->orWhereNull($query->getModel()->getDataColumn());
|
||||||
|
});
|
||||||
|
|
||||||
return $builder;
|
return $builder;
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Scope;
|
use Illuminate\Database\Eloquent\Scope;
|
||||||
|
|
||||||
|
/** @implements Scope<Model> */
|
||||||
class ParentModelScope implements Scope
|
class ParentModelScope implements Scope
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Scope;
|
use Illuminate\Database\Eloquent\Scope;
|
||||||
use Stancl\Tenancy\Tenancy;
|
use Stancl\Tenancy\Tenancy;
|
||||||
|
|
||||||
|
/** @implements Scope<Model> */
|
||||||
class TenantScope implements Scope
|
class TenantScope implements Scope
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -152,6 +152,26 @@ class Tenancy
|
||||||
$this->initialized = false;
|
$this->initialized = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* End tenancy and initialize it again for the current tenant.
|
||||||
|
*
|
||||||
|
* This can be helpful when changing "dependencies" of bootstrappers such as
|
||||||
|
* attributes of the current tenant that are only read once, during bootstrap().
|
||||||
|
*
|
||||||
|
* If tenancy is not initialized, this method is a no-op.
|
||||||
|
*/
|
||||||
|
public function reinitialize(): void
|
||||||
|
{
|
||||||
|
if ($this->tenant === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$tenant = $this->tenant;
|
||||||
|
$this->end();
|
||||||
|
|
||||||
|
$this->initialize($tenant);
|
||||||
|
}
|
||||||
|
|
||||||
/** @return TenancyBootstrapper[] */
|
/** @return TenancyBootstrapper[] */
|
||||||
public function getBootstrappers(): array
|
public function getBootstrappers(): array
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -103,6 +103,33 @@ test('central helper doesnt change tenancy state when called in central context'
|
||||||
expect(tenant())->toBeNull();
|
expect(tenant())->toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('reinitialize method does nothing in the central context', function () {
|
||||||
|
expect(tenancy()->initialized)->toBe(false);
|
||||||
|
expect(fn () => tenancy()->reinitialize())->not()->toThrow(\Throwable::class);
|
||||||
|
expect(tenancy()->initialized)->toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('reinitialize method runs bootstrappers again for the current tenant', function () {
|
||||||
|
config(['tenancy.bootstrappers' => [
|
||||||
|
ReinitBootstrapper::class,
|
||||||
|
]]);
|
||||||
|
|
||||||
|
tenancy()->initialize($tenant = Tenant::create(['reinit_bootstrapper_key' => 'foo']));
|
||||||
|
|
||||||
|
expect(tenant()->getKey())->toBe($tenant->getKey());
|
||||||
|
expect(app('tenancy_reinit_bootstrapper_key'))->toBe('foo');
|
||||||
|
|
||||||
|
$tenant->update(['reinit_bootstrapper_key' => 'bar']);
|
||||||
|
|
||||||
|
// Unchanged until we reinitialize...
|
||||||
|
expect(app('tenancy_reinit_bootstrapper_key'))->toBe('foo');
|
||||||
|
|
||||||
|
tenancy()->reinitialize();
|
||||||
|
|
||||||
|
expect(tenant()->getKey())->toBe($tenant->getKey());
|
||||||
|
expect(app('tenancy_reinit_bootstrapper_key'))->toBe('bar');
|
||||||
|
});
|
||||||
|
|
||||||
class MyBootstrapper implements TenancyBootstrapper
|
class MyBootstrapper implements TenancyBootstrapper
|
||||||
{
|
{
|
||||||
public function bootstrap(\Stancl\Tenancy\Contracts\Tenant $tenant): void
|
public function bootstrap(\Stancl\Tenancy\Contracts\Tenant $tenant): void
|
||||||
|
|
@ -115,3 +142,16 @@ class MyBootstrapper implements TenancyBootstrapper
|
||||||
app()->instance('tenancy_ended', true);
|
app()->instance('tenancy_ended', true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ReinitBootstrapper implements TenancyBootstrapper
|
||||||
|
{
|
||||||
|
public function bootstrap(\Stancl\Tenancy\Contracts\Tenant $tenant): void
|
||||||
|
{
|
||||||
|
app()->instance('tenancy_reinit_bootstrapper_key', $tenant->getAttribute('reinit_bootstrapper_key'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function revert(): void
|
||||||
|
{
|
||||||
|
app()->instance('tenancy_reinit_bootstrapper_key', null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -111,6 +111,18 @@ test('a new tenant gets created while pulling a pending tenant if the pending po
|
||||||
expect(Tenant::withPending()->get()->count())->toBe(1); // All tenants
|
expect(Tenant::withPending()->get()->count())->toBe(1); // All tenants
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('withoutPending chained with where clauses returns correct results', function () {
|
||||||
|
$tenant = Tenant::create();
|
||||||
|
$pendingTenant = Tenant::createPending();
|
||||||
|
|
||||||
|
// The query returned the correct tenant
|
||||||
|
expect(Tenant::withoutPending()->where('id', $tenant->id)->first()->id)->toBe($tenant->id);
|
||||||
|
// No tenant with this ID exists, the query returns null
|
||||||
|
expect(Tenant::withoutPending()->where('id', Str::random(8) . 'nonexistent-id')->first())->toBeNull();
|
||||||
|
// withoutPending() correctly excludes the pending tenant from the query
|
||||||
|
expect(Tenant::withoutPending()->where('id', $pendingTenant->id)->first())->toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
test('pending tenants are included in all queries based on the include_in_queries config', function () {
|
test('pending tenants are included in all queries based on the include_in_queries config', function () {
|
||||||
Tenant::createPending();
|
Tenant::createPending();
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue