1
0
Fork 0
mirror of https://github.com/archtechx/tenancy.git synced 2026-02-06 05:04:03 +00:00

Refactor cached tenant resolvers with decorator pattern

This commit is contained in:
Erik Gaal 2022-09-24 20:15:47 +02:00
parent 8e3b74f9d1
commit 97e45ab9cc
No known key found for this signature in database
GPG key ID: 8733B288F439A599
15 changed files with 424 additions and 251 deletions

View file

@ -1,95 +0,0 @@
<?php
declare(strict_types=1);
use Illuminate\Support\Facades\DB;
use Stancl\Tenancy\Resolvers\DomainTenantResolver;
use Stancl\Tenancy\Tests\Etc\Tenant;
afterEach(function () {
DomainTenantResolver::$shouldCache = false;
});
test('tenants can be resolved using the cached resolver', function () {
$tenant = Tenant::create();
$tenant->domains()->create([
'domain' => 'acme',
]);
expect($tenant->is(app(DomainTenantResolver::class)->resolve('acme')))->toBeTrue()->toBeTrue();
});
test('the underlying resolver is not touched when using the cached resolver', function () {
$tenant = Tenant::create();
$tenant->domains()->create([
'domain' => 'acme',
]);
DB::enableQueryLog();
DomainTenantResolver::$shouldCache = false;
expect($tenant->is(app(DomainTenantResolver::class)->resolve('acme')))->toBeTrue();
DB::flushQueryLog();
expect($tenant->is(app(DomainTenantResolver::class)->resolve('acme')))->toBeTrue();
pest()->assertNotEmpty(DB::getQueryLog()); // not empty
DomainTenantResolver::$shouldCache = true;
expect($tenant->is(app(DomainTenantResolver::class)->resolve('acme')))->toBeTrue();
DB::flushQueryLog();
expect($tenant->is(app(DomainTenantResolver::class)->resolve('acme')))->toBeTrue();
expect(DB::getQueryLog())->toBeEmpty(); // empty
});
test('cache is invalidated when the tenant is updated', function () {
$tenant = Tenant::create();
$tenant->createDomain([
'domain' => 'acme',
]);
DB::enableQueryLog();
DomainTenantResolver::$shouldCache = true;
expect($tenant->is(app(DomainTenantResolver::class)->resolve('acme')))->toBeTrue();
DB::flushQueryLog();
expect($tenant->is(app(DomainTenantResolver::class)->resolve('acme')))->toBeTrue();
expect(DB::getQueryLog())->toBeEmpty(); // empty
$tenant->update([
'foo' => 'bar',
]);
DB::flushQueryLog();
expect($tenant->is(app(DomainTenantResolver::class)->resolve('acme')))->toBeTrue();
pest()->assertNotEmpty(DB::getQueryLog()); // not empty
});
test('cache is invalidated when a tenants domain is changed', function () {
$tenant = Tenant::create();
$tenant->createDomain([
'domain' => 'acme',
]);
DB::enableQueryLog();
DomainTenantResolver::$shouldCache = true;
expect($tenant->is(app(DomainTenantResolver::class)->resolve('acme')))->toBeTrue();
DB::flushQueryLog();
expect($tenant->is(app(DomainTenantResolver::class)->resolve('acme')))->toBeTrue();
expect(DB::getQueryLog())->toBeEmpty(); // empty
$tenant->createDomain([
'domain' => 'bar',
]);
DB::flushQueryLog();
expect($tenant->is(app(DomainTenantResolver::class)->resolve('acme')))->toBeTrue();
pest()->assertNotEmpty(DB::getQueryLog()); // not empty
DB::flushQueryLog();
expect($tenant->is(app(DomainTenantResolver::class)->resolve('bar')))->toBeTrue();
pest()->assertNotEmpty(DB::getQueryLog()); // not empty
});

View file

@ -0,0 +1,41 @@
<?php
namespace Stancl\Tenancy\Tests\Repository;
use Illuminate\Support\Collection;
use Stancl\Tenancy\Contracts\Tenant;
use Stancl\Tenancy\Repository\TenantRepository;
class InMemoryTenantRepository implements TenantRepository
{
public function __construct(
/** @var Collection<Tenant> */
private Collection $tenants = new Collection(),
) {
}
public function find(int|string $id): ?Tenant
{
return $this->tenants->first(fn (Tenant $tenant) => $tenant->getTenantKey() == $id);
}
public function findForDomain(string $domain): ?Tenant
{
return $this->tenants->firstWhere(fn (Tenant $tenant) => in_array($domain, $tenant->domains ?? []));
}
public function all(): iterable
{
return $this->tenants->lazy();
}
public function whereKeyIn(string|int ...$ids): iterable
{
return $this->tenants->filter(fn (Tenant $tenant) => in_array($tenant->getTenantKey(), $ids))->lazy();
}
public function store(Tenant $tenant): void
{
$this->tenants->push($tenant);
}
}

View file

@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
use Illuminate\Cache\Repository;
use Illuminate\Contracts\Cache\Store;
use Stancl\Tenancy\Contracts\TenantResolver;
use Stancl\Tenancy\Resolvers\CachedTenantResolver;
use Stancl\Tenancy\Tests\Etc\Tenant;
it('uses the underlying resolver if cache is stale', function () {
$underlying = Mockery::mock(TenantResolver::class);
$cache = new Repository($store = Mockery::mock(Store::class));
$args = [
'id' => 1,
];
$resolver = new CachedTenantResolver(
tenantResolver: $underlying,
cache: $cache,
prefix: '_tenant_resolver',
);
$store->expects('get')->withAnyArgs()->andReturnNull();
$underlying->expects('resolve')->andReturn($tenant = new Tenant());
$store->expects('put')->withSomeOfArgs($tenant);
expect($resolver->resolve($args))->toBe($tenant);
});
it('skips the underlying resolver if cache is valid', function () {
$underlying = Mockery::mock(TenantResolver::class);
$cache = new Repository($store = Mockery::mock(Store::class));
$args = [
'id' => 1,
];
$resolver = new CachedTenantResolver(
tenantResolver: $underlying,
cache: $cache,
prefix: '_tenant_resolver',
);
$cache->expects('get')->withAnyArgs()->andReturn($tenant = new Tenant());
$underlying->expects('resolve')->never();
expect($resolver->resolve($args))->toBe($tenant);
});

View file

@ -0,0 +1,33 @@
<?php
use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedOnDomainException;
use Stancl\Tenancy\Resolvers\DomainTenantResolver;
use Stancl\Tenancy\Tests\Etc\Tenant;
use Stancl\Tenancy\Tests\Repository\InMemoryTenantRepository;
beforeEach(function () {
$this->repository = new InMemoryTenantRepository();
$this->tenant = new Tenant();
$this->tenant->domains = ['acme'];
$this->repository->store($this->tenant);
});
it('resolves the tenant with domain', function () {
$resolver = new DomainTenantResolver(
tenantRepository: $this->repository,
);
$result = $resolver->resolve('acme');
expect($result)->toBe($this->tenant);
});
it('throws when unable to find tenant', function () {
$resolver = new DomainTenantResolver(
tenantRepository: $this->repository,
);
$resolver->resolve('foo');
})->throws(TenantCouldNotBeIdentifiedOnDomainException::class);

View file

@ -0,0 +1,40 @@
<?php
use Illuminate\Http\Request;
use Illuminate\Routing\Route;
use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedByPathException;
use Stancl\Tenancy\Resolvers\PathTenantResolver;
use Stancl\Tenancy\Tests\Repository\InMemoryTenantRepository;
beforeEach(function () {
$this->repository = new InMemoryTenantRepository();
$this->tenant = new Tenant();
$this->tenant->id = 1;
$this->repository->store($this->tenant);
});
it('resolves the tenant from path', function () {
$resolver = new PathTenantResolver(
tenantRepository: $this->repository,
);
$route = (new Route('get', '/{tenant}/foo', fn () => null))
->bind(Request::create('/1/foo'));
$result = $resolver->resolve($route);
expect($result)->toBe($this->tenant);
});
it('throws when unable to find tenant', function () {
$resolver = new PathTenantResolver(
tenantRepository: new InMemoryTenantRepository(),
);
$route = (new Route('GET', '/{tenant}/foo', fn () => null))
->bind(Request::create('/2/foo'));
$resolver->resolve($route);
})->throws(TenantCouldNotBeIdentifiedByPathException::class);

View file

@ -0,0 +1,33 @@
<?php
use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedOnDomainException;
use Stancl\Tenancy\Resolvers\DomainTenantResolver;
use Stancl\Tenancy\Tests\Etc\Tenant;
use Stancl\Tenancy\Tests\Repository\InMemoryTenantRepository;
beforeEach(function () {
$this->repository = new InMemoryTenantRepository();
$this->tenant = new Tenant();
$this->tenant->id = 1;
$this->repository->store($this->tenant);
});
it('resolves the tenant', function () {
$resolver = new DomainTenantResolver(
tenantRepository: $this->repository,
);
$result = $resolver->resolve(id: 1);
expect($result)->toBe($this->tenant);
});
it('throws when unable to find tenant', function () {
$resolver = new DomainTenantResolver(
tenantRepository: $this->repository,
);
$resolver->resolve('foo');
})->throws(TenantCouldNotBeIdentifiedOnDomainException::class);