mirror of
https://github.com/archtechx/tenancy.git
synced 2025-12-12 15:54:03 +00:00
Domain model & resolver test
This commit is contained in:
parent
08ed5084d5
commit
e1a4054743
11 changed files with 158 additions and 8 deletions
|
|
@ -2,10 +2,12 @@
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Stancl\Tenancy\Database\Models\Domain;
|
||||||
use Stancl\Tenancy\Database\Models\Tenant;
|
use Stancl\Tenancy\Database\Models\Tenant;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'tenant_model' => Tenant::class,
|
'tenant_model' => Tenant::class,
|
||||||
|
'domain_model' => Domain::class,
|
||||||
'internal_prefix' => 'tenancy_',
|
'internal_prefix' => 'tenancy_',
|
||||||
|
|
||||||
'central_connection' => 'central',
|
'central_connection' => 'central',
|
||||||
|
|
|
||||||
|
|
@ -16,9 +16,11 @@ class CreateDomainsTable extends Migration
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
Schema::create('domains', function (Blueprint $table) {
|
Schema::create('domains', function (Blueprint $table) {
|
||||||
$table->string('domain', 255)->primary();
|
$table->increments('id');
|
||||||
|
$table->string('domain', 255)->unique();
|
||||||
$table->string('tenant_id', 36);
|
$table->string('tenant_id', 36);
|
||||||
|
|
||||||
|
$table->timestamps();
|
||||||
$table->foreign('tenant_id')->references('id')->on('tenants')->onUpdate('cascade')->onDelete('cascade');
|
$table->foreign('tenant_id')->references('id')->on('tenants')->onUpdate('cascade')->onDelete('cascade');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
6
src/Contracts/Tenant.php
Normal file
6
src/Contracts/Tenant.php
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Stancl\Tenancy\Contracts;
|
||||||
|
|
||||||
|
interface Tenant
|
||||||
|
{}
|
||||||
9
src/Contracts/TenantCouldNotBeIdentifiedException.php
Normal file
9
src/Contracts/TenantCouldNotBeIdentifiedException.php
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Stancl\Tenancy\Contracts;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
abstract class TenantCouldNotBeIdentifiedException extends Exception
|
||||||
|
{
|
||||||
|
}
|
||||||
13
src/Contracts/TenantResolver.php
Normal file
13
src/Contracts/TenantResolver.php
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Stancl\Tenancy\Contracts;
|
||||||
|
|
||||||
|
interface TenantResolver
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Resolve a tenant using some value.
|
||||||
|
*
|
||||||
|
* @throws TenantCouldNotBeIdentifiedException
|
||||||
|
*/
|
||||||
|
public function resolve(...$args): Tenant;
|
||||||
|
}
|
||||||
|
|
@ -3,19 +3,42 @@
|
||||||
namespace Stancl\Tenancy\Database\Models;
|
namespace Stancl\Tenancy\Database\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Stancl\Tenancy\Contracts\Tenant;
|
||||||
use Stancl\Tenancy\Events\DomainCreated;
|
use Stancl\Tenancy\Events\DomainCreated;
|
||||||
use Stancl\Tenancy\Events\DomainDeleted;
|
use Stancl\Tenancy\Events\DomainDeleted;
|
||||||
use Stancl\Tenancy\Events\DomainSaved;
|
use Stancl\Tenancy\Events\DomainSaved;
|
||||||
use Stancl\Tenancy\Events\DomainUpdated;
|
use Stancl\Tenancy\Events\DomainUpdated;
|
||||||
|
use Stancl\Tenancy\Exceptions\DomainsOccupiedByOtherTenantException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property string $domain
|
||||||
|
* @property string $tenant_id
|
||||||
|
*
|
||||||
|
* @property-read Tenant $tenant
|
||||||
|
*/
|
||||||
class Domain extends Model
|
class Domain extends Model
|
||||||
{
|
{
|
||||||
public function tenant()
|
public $guarded = [];
|
||||||
|
|
||||||
|
public static function booted()
|
||||||
{
|
{
|
||||||
return $this->belongsTo(Tenant::class);
|
$ensureDomainIsNotOccupied = function (Domain $self) {
|
||||||
|
if ($domain = Domain::where('domain', $self->domain)->first()) {
|
||||||
|
if ($domain->getKey() !== $self->getKey()) {
|
||||||
|
throw new DomainsOccupiedByOtherTenantException;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static::saving($ensureDomainIsNotOccupied);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected $dispatchEvents = [
|
public function tenant()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(config('tenancy.tenant_model'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public $dispatchEvents = [
|
||||||
'saved' => DomainSaved::class,
|
'saved' => DomainSaved::class,
|
||||||
'created' => DomainCreated::class,
|
'created' => DomainCreated::class,
|
||||||
'updated' => DomainUpdated::class,
|
'updated' => DomainUpdated::class,
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,11 @@ namespace Stancl\Tenancy\Database\Models;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Stancl\Tenancy\DatabaseConfig;
|
use Stancl\Tenancy\DatabaseConfig;
|
||||||
use Stancl\Tenancy\Events;
|
use Stancl\Tenancy\Events;
|
||||||
|
use Stancl\Tenancy\Contracts;
|
||||||
|
|
||||||
// todo use a contract
|
// todo use a contract
|
||||||
class Tenant extends Model
|
// todo @property
|
||||||
|
class Tenant extends Model implements Contracts\Tenant
|
||||||
{
|
{
|
||||||
use Concerns\CentralConnection, Concerns\HasADataColumn, Concerns\GeneratesIds, Concerns\HasADataColumn {
|
use Concerns\CentralConnection, Concerns\HasADataColumn, Concerns\GeneratesIds, Concerns\HasADataColumn {
|
||||||
Concerns\HasADataColumn::getCasts as dataColumnCasts;
|
Concerns\HasADataColumn::getCasts as dataColumnCasts;
|
||||||
|
|
@ -80,7 +82,7 @@ class Tenant extends Model
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected $dispatchesEvents = [
|
public $dispatchesEvents = [
|
||||||
'saved' => Events\TenantSaved::class,
|
'saved' => Events\TenantSaved::class,
|
||||||
'created' => Events\TenantCreated::class,
|
'created' => Events\TenantCreated::class,
|
||||||
'updated' => Events\TenantUpdated::class,
|
'updated' => Events\TenantUpdated::class,
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,9 @@ namespace Stancl\Tenancy\Exceptions;
|
||||||
use Facade\IgnitionContracts\BaseSolution;
|
use Facade\IgnitionContracts\BaseSolution;
|
||||||
use Facade\IgnitionContracts\ProvidesSolution;
|
use Facade\IgnitionContracts\ProvidesSolution;
|
||||||
use Facade\IgnitionContracts\Solution;
|
use Facade\IgnitionContracts\Solution;
|
||||||
|
use Stancl\Tenancy\Contracts\TenantCouldNotBeIdentifiedException;
|
||||||
|
|
||||||
class TenantCouldNotBeIdentifiedException extends \Exception implements ProvidesSolution
|
class TenantCouldNotBeIdentifiedOnDomainException extends TenantCouldNotBeIdentifiedException implements ProvidesSolution
|
||||||
{
|
{
|
||||||
public function __construct($domain)
|
public function __construct($domain)
|
||||||
{
|
{
|
||||||
|
|
@ -20,7 +21,7 @@ class TenantCouldNotBeIdentifiedException extends \Exception implements Provides
|
||||||
return BaseSolution::create('Tenant could not be identified on this domain')
|
return BaseSolution::create('Tenant could not be identified on this domain')
|
||||||
->setSolutionDescription('Did you forget to create a tenant for this domain?')
|
->setSolutionDescription('Did you forget to create a tenant for this domain?')
|
||||||
->setDocumentationLinks([
|
->setDocumentationLinks([
|
||||||
'Creating Tenants' => 'https://tenancy.samuelstancl.me/docs/v2/creating-tenants/',
|
'Creating Tenants' => 'https://tenancyforlaravel.com/docs/v2/creating-tenants/', // todo update link for v3
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
22
src/Resolvers/DomainTenantResolver.php
Normal file
22
src/Resolvers/DomainTenantResolver.php
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Stancl\Tenancy\Resolvers;
|
||||||
|
|
||||||
|
use Stancl\Tenancy\Contracts\Tenant;
|
||||||
|
use Stancl\Tenancy\Contracts\TenantResolver;
|
||||||
|
use Stancl\Tenancy\Database\Models\Domain;
|
||||||
|
use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedOnDomainException;
|
||||||
|
|
||||||
|
class DomainTenantResolver implements TenantResolver
|
||||||
|
{
|
||||||
|
public function resolve(...$args): Tenant
|
||||||
|
{
|
||||||
|
$domain = app(config('tenancy.domain_model'))->where('domain', $args[0])->first();
|
||||||
|
|
||||||
|
if ($domain) {
|
||||||
|
return $domain->tenant;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new TenantCouldNotBeIdentifiedOnDomainException($domain);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Stancl\Tenancy\Tests\v3;
|
||||||
|
|
||||||
|
use Stancl\Tenancy\Database\Models;
|
||||||
|
use Stancl\Tenancy\Database\Models\Domain;
|
||||||
|
use Stancl\Tenancy\Exceptions\DomainsOccupiedByOtherTenantException;
|
||||||
|
use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedOnDomainException;
|
||||||
|
use Stancl\Tenancy\Resolvers\DomainTenantResolver;
|
||||||
|
use Stancl\Tenancy\Tests\TestCase;
|
||||||
|
|
||||||
|
class DomainTest extends TestCase
|
||||||
|
{
|
||||||
|
public function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
config(['tenancy.tenant_model' => Tenant::class]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function tenant_can_be_identified_using_hostname()
|
||||||
|
{
|
||||||
|
$tenant = Tenant::create();
|
||||||
|
|
||||||
|
$id = $tenant->id;
|
||||||
|
|
||||||
|
$tenant->domains()->create([
|
||||||
|
'domain' => 'foo.localhost',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$resolvedTenant = app(DomainTenantResolver::class)->resolve('foo.localhost');
|
||||||
|
|
||||||
|
$this->assertSame($id, $resolvedTenant->id);
|
||||||
|
$this->assertSame(['foo.localhost'], $resolvedTenant->domains->pluck('domain')->toArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function a_domain_can_belong_to_only_one_tenant()
|
||||||
|
{
|
||||||
|
$tenant = Tenant::create();
|
||||||
|
|
||||||
|
$tenant->domains()->create([
|
||||||
|
'domain' => 'foo.localhost',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$tenant2 = Tenant::create();
|
||||||
|
|
||||||
|
$this->expectException(DomainsOccupiedByOtherTenantException::class);
|
||||||
|
$tenant2->domains()->create([
|
||||||
|
'domain' => 'foo.localhost',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function an_exception_is_thrown_if_tenant_cannot_be_identified()
|
||||||
|
{
|
||||||
|
$this->expectException(TenantCouldNotBeIdentifiedOnDomainException::class);
|
||||||
|
|
||||||
|
app(DomainTenantResolver::class)->resolve('foo.localhost');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Tenant extends Models\Tenant
|
||||||
|
{
|
||||||
|
public function domains()
|
||||||
|
{
|
||||||
|
return $this->hasMany(Domain::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue