mirror of
https://github.com/archtechx/tenancy.git
synced 2025-12-12 17:24: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);
|
||||
|
||||
use Stancl\Tenancy\Database\Models\Domain;
|
||||
use Stancl\Tenancy\Database\Models\Tenant;
|
||||
|
||||
return [
|
||||
'tenant_model' => Tenant::class,
|
||||
'domain_model' => Domain::class,
|
||||
'internal_prefix' => 'tenancy_',
|
||||
|
||||
'central_connection' => 'central',
|
||||
|
|
|
|||
|
|
@ -16,9 +16,11 @@ class CreateDomainsTable extends Migration
|
|||
public function up(): void
|
||||
{
|
||||
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->timestamps();
|
||||
$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;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Stancl\Tenancy\Contracts\Tenant;
|
||||
use Stancl\Tenancy\Events\DomainCreated;
|
||||
use Stancl\Tenancy\Events\DomainDeleted;
|
||||
use Stancl\Tenancy\Events\DomainSaved;
|
||||
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
|
||||
{
|
||||
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,
|
||||
'created' => DomainCreated::class,
|
||||
'updated' => DomainUpdated::class,
|
||||
|
|
|
|||
|
|
@ -5,9 +5,11 @@ namespace Stancl\Tenancy\Database\Models;
|
|||
use Illuminate\Database\Eloquent\Model;
|
||||
use Stancl\Tenancy\DatabaseConfig;
|
||||
use Stancl\Tenancy\Events;
|
||||
use Stancl\Tenancy\Contracts;
|
||||
|
||||
// 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 {
|
||||
Concerns\HasADataColumn::getCasts as dataColumnCasts;
|
||||
|
|
@ -80,7 +82,7 @@ class Tenant extends Model
|
|||
return $result;
|
||||
}
|
||||
|
||||
protected $dispatchesEvents = [
|
||||
public $dispatchesEvents = [
|
||||
'saved' => Events\TenantSaved::class,
|
||||
'created' => Events\TenantCreated::class,
|
||||
'updated' => Events\TenantUpdated::class,
|
||||
|
|
|
|||
|
|
@ -7,8 +7,9 @@ namespace Stancl\Tenancy\Exceptions;
|
|||
use Facade\IgnitionContracts\BaseSolution;
|
||||
use Facade\IgnitionContracts\ProvidesSolution;
|
||||
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)
|
||||
{
|
||||
|
|
@ -20,7 +21,7 @@ class TenantCouldNotBeIdentifiedException extends \Exception implements Provides
|
|||
return BaseSolution::create('Tenant could not be identified on this domain')
|
||||
->setSolutionDescription('Did you forget to create a tenant for this domain?')
|
||||
->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