diff --git a/assets/TenancyServiceProvider.stub.php b/assets/TenancyServiceProvider.stub.php index f1b00c88..46f35515 100644 --- a/assets/TenancyServiceProvider.stub.php +++ b/assets/TenancyServiceProvider.stub.php @@ -81,8 +81,6 @@ class TenancyServiceProvider extends ServiceProvider ])->send(function (Events\TenantDeleted $event) { return $event->tenant; })->shouldBeQueued(false), - - // ResourceSyncing\Listeners\DeleteAllTenantMappings::class, ], Events\TenantMaintenanceModeEnabled::class => [], @@ -131,9 +129,6 @@ class TenancyServiceProvider extends ServiceProvider ResourceSyncing\Events\SyncedResourceSaved::class => [ ResourceSyncing\Listeners\UpdateOrCreateSyncedResource::class, ], - ResourceSyncing\Events\SyncedResourceDeleted::class => [ - ResourceSyncing\Listeners\DeleteResourceMapping::class, - ], ResourceSyncing\Events\SyncMasterDeleted::class => [ ResourceSyncing\Listeners\DeleteResourcesInTenants::class, ], @@ -146,9 +141,7 @@ class TenancyServiceProvider extends ServiceProvider ResourceSyncing\Events\CentralResourceDetachedFromTenant::class => [ ResourceSyncing\Listeners\DeleteResourceInTenant::class, ], - - // Fired only when a synced resource is changed (as a result of syncing) - // in a different DB than DB from which the change originates (to avoid infinite loops) + // Fired only when a synced resource is changed in a different DB than the origin DB (to avoid infinite loops) ResourceSyncing\Events\SyncedResourceSavedInForeignDatabase::class => [], // Storage symlinks diff --git a/src/ResourceSyncing/CentralResourceNotAvailableInPivotException.php b/src/ResourceSyncing/CentralResourceNotAvailableInPivotException.php index fbb918dd..d20415be 100644 --- a/src/ResourceSyncing/CentralResourceNotAvailableInPivotException.php +++ b/src/ResourceSyncing/CentralResourceNotAvailableInPivotException.php @@ -13,7 +13,7 @@ class CentralResourceNotAvailableInPivotException extends Exception parent::__construct( 'Central resource is not accessible in pivot model. To attach a resource to a tenant, use $centralResource->tenants()->attach($tenant) instead of $tenant->resources()->attach($centralResource) (same for detaching). - To make this work both ways, you can make your pivot implement PivotWithCentralResource and return the related model in getCentralResourceClass() or extend MorphPivot.' + To make this work both ways, you can make your pivot implement PivotWithRelation and return the related model in getRelatedModel() or extend MorphPivot.' ); } } diff --git a/src/ResourceSyncing/Events/SyncedResourceDeleted.php b/src/ResourceSyncing/Events/SyncedResourceDeleted.php deleted file mode 100644 index 941e1841..00000000 --- a/src/ResourceSyncing/Events/SyncedResourceDeleted.php +++ /dev/null @@ -1,18 +0,0 @@ - 'tenant_key_column'] format. - * - * Since we cannot automatically detect which pivot tables - * are being used, they have to be specified here manually. - * - * The default value follows the polymorphic table used by default. - */ - public static array $pivotTables = ['tenant_resources' => 'tenant_id']; - - public function handle(TenantDeleted $event): void - { - foreach (static::$pivotTables as $table => $tenantKeyColumn) { - DB::table($table)->where($tenantKeyColumn, $event->tenant->getKey())->delete(); - } - } -} diff --git a/src/ResourceSyncing/Listeners/DeleteResourceMapping.php b/src/ResourceSyncing/Listeners/DeleteResourceMapping.php deleted file mode 100644 index 53754324..00000000 --- a/src/ResourceSyncing/Listeners/DeleteResourceMapping.php +++ /dev/null @@ -1,60 +0,0 @@ -getCentralResource($event->model); - - if (! $centralResource) { - return; - } - - // Delete pivot records if the central resource doesn't use soft deletes - // or the central resource was deleted using forceDelete() - if ($event->forceDelete || ! in_array(SoftDeletes::class, class_uses_recursive($centralResource::class), true)) { - Pivot::withoutEvents(function () use ($centralResource, $event) { - // If detach() is called with null -- if $event->tenant is null -- this means a central resource was deleted and detaches all tenants. - // If detach() is called with a specific tenant, it means the resource was deleted in that tenant, and we only delete that single mapping. - $centralResource->tenants()->detach($event->tenant); - }); - } - } - - public function getCentralResource(Syncable&Model $resource): SyncMaster|null - { - if ($resource instanceof SyncMaster) { - return $resource; - } - - $centralResourceClass = $resource->getCentralModelName(); - - /** @var (SyncMaster&Model)|null $centralResource */ - $centralResource = $centralResourceClass::firstWhere( - $resource->getGlobalIdentifierKeyName(), - $resource->getGlobalIdentifierKey() - ); - - return $centralResource; - } -} diff --git a/src/ResourceSyncing/Listeners/DeleteResourcesInTenants.php b/src/ResourceSyncing/Listeners/DeleteResourcesInTenants.php index 7b071a27..6876f476 100644 --- a/src/ResourceSyncing/Listeners/DeleteResourcesInTenants.php +++ b/src/ResourceSyncing/Listeners/DeleteResourcesInTenants.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Stancl\Tenancy\ResourceSyncing\Listeners; +use Illuminate\Database\Eloquent\SoftDeletes; use Stancl\Tenancy\Listeners\QueueableListener; use Stancl\Tenancy\ResourceSyncing\Events\SyncMasterDeleted; @@ -20,6 +21,12 @@ class DeleteResourcesInTenants extends QueueableListener tenancy()->runForMultiple($centralResource->tenants()->cursor(), function () use ($centralResource, $forceDelete) { $this->deleteSyncedResource($centralResource, $forceDelete); + + // Delete pivot records if the central resource doesn't use soft deletes + // or the central resource was deleted using forceDelete() + if ($forceDelete || ! in_array(SoftDeletes::class, class_uses_recursive($centralResource::class), true)) { + $centralResource->tenants()->detach(tenant()); + } }); } } diff --git a/src/ResourceSyncing/PivotWithCentralResource.php b/src/ResourceSyncing/PivotWithCentralResource.php deleted file mode 100644 index 07efcc2e..00000000 --- a/src/ResourceSyncing/PivotWithCentralResource.php +++ /dev/null @@ -1,11 +0,0 @@ - */ - public function getCentralResourceClass(): string; -} diff --git a/src/ResourceSyncing/PivotWithRelation.php b/src/ResourceSyncing/PivotWithRelation.php new file mode 100644 index 00000000..4936d1fe --- /dev/null +++ b/src/ResourceSyncing/PivotWithRelation.php @@ -0,0 +1,15 @@ +users()->getModel(). + */ + public function getRelatedModel(): Model; +} diff --git a/src/ResourceSyncing/ResourceSyncing.php b/src/ResourceSyncing/ResourceSyncing.php index 272b7bd7..f0d8cc12 100644 --- a/src/ResourceSyncing/ResourceSyncing.php +++ b/src/ResourceSyncing/ResourceSyncing.php @@ -11,7 +11,6 @@ use Stancl\Tenancy\Contracts\UniqueIdentifierGenerator; use Stancl\Tenancy\Database\Contracts\TenantWithDatabase; use Stancl\Tenancy\ResourceSyncing\Events\CentralResourceAttachedToTenant; use Stancl\Tenancy\ResourceSyncing\Events\CentralResourceDetachedFromTenant; -use Stancl\Tenancy\ResourceSyncing\Events\SyncedResourceDeleted; use Stancl\Tenancy\ResourceSyncing\Events\SyncedResourceSaved; use Stancl\Tenancy\ResourceSyncing\Events\SyncMasterDeleted; use Stancl\Tenancy\ResourceSyncing\Events\SyncMasterRestored; @@ -20,34 +19,37 @@ trait ResourceSyncing { public static function bootResourceSyncing(): void { - static::saved(static function (Syncable&Model $model) { + static::saved(function (Syncable&Model $model) { if ($model->shouldSync() && ($model->wasRecentlyCreated || $model->wasChanged($model->getSyncedAttributeNames()))) { $model->triggerSyncEvent(); } }); - static::deleted(static function (Syncable&Model $model) { - if ($model->shouldSync()) { + static::deleting(function (Syncable&Model $model) { + if ($model->shouldSync() && $model instanceof SyncMaster) { $model->triggerDeleteEvent(); } }); - static::creating(static function (Syncable&Model $model) { - if (! $model->getAttribute($model->getGlobalIdentifierKeyName())) { - $model->generateGlobalIdentifierKey(); + static::creating(function (Syncable&Model $model) { + if (! $model->getAttribute($model->getGlobalIdentifierKeyName()) && app()->bound(UniqueIdentifierGenerator::class)) { + $model->setAttribute( + $model->getGlobalIdentifierKeyName(), + app(UniqueIdentifierGenerator::class)->generate($model) + ); } }); if (in_array(SoftDeletes::class, class_uses_recursive(static::class), true)) { - static::forceDeleting(static function (Syncable&Model $model) { - if ($model->shouldSync()) { + static::forceDeleting(function (Syncable&Model $model) { + if ($model->shouldSync() && $model instanceof SyncMaster) { $model->triggerDeleteEvent(true); } }); - static::restoring(static function (Syncable&Model $model) { - if ($model instanceof SyncMaster && $model->shouldSync()) { - $model->triggerRestoreEvent(); + static::restoring(function (Syncable&Model $model) { + if ($model->shouldSync() && $model instanceof SyncMaster) { + $model->triggerRestoredEvent(); } }); } @@ -65,11 +67,9 @@ trait ResourceSyncing /** @var SyncMaster&Model $this */ event(new SyncMasterDeleted($this, $forceDelete)); } - - event(new SyncedResourceDeleted($this, tenant(), $forceDelete)); } - public function triggerRestoreEvent(): void + public function triggerRestoredEvent(): void { if ($this instanceof SyncMaster && in_array(SoftDeletes::class, class_uses_recursive($this), true)) { /** @var SyncMaster&Model $this */ @@ -116,18 +116,8 @@ trait ResourceSyncing return 'global_id'; } - public function getGlobalIdentifierKey(): string|int + public function getGlobalIdentifierKey(): string { return $this->getAttribute($this->getGlobalIdentifierKeyName()); } - - protected function generateGlobalIdentifierKey(): void - { - if (! app()->bound(UniqueIdentifierGenerator::class)) return; - - $this->setAttribute( - $this->getGlobalIdentifierKeyName(), - app(UniqueIdentifierGenerator::class)->generate($this), - ); - } } diff --git a/src/ResourceSyncing/SyncMaster.php b/src/ResourceSyncing/SyncMaster.php index 290546cb..882aeb54 100644 --- a/src/ResourceSyncing/SyncMaster.php +++ b/src/ResourceSyncing/SyncMaster.php @@ -25,5 +25,7 @@ interface SyncMaster extends Syncable public function triggerAttachEvent(TenantWithDatabase&Model $tenant): void; - public function triggerRestoreEvent(): void; + public function triggerDeleteEvent(bool $forceDelete = false): void; + + public function triggerRestoredEvent(): void; } diff --git a/src/ResourceSyncing/Syncable.php b/src/ResourceSyncing/Syncable.php index c38b02ea..3d5288f1 100644 --- a/src/ResourceSyncing/Syncable.php +++ b/src/ResourceSyncing/Syncable.php @@ -16,8 +16,6 @@ interface Syncable public function triggerSyncEvent(): void; - public function triggerDeleteEvent(bool $forceDelete = false): void; - /** * Get the attributes used for creating the *other* model (i.e. tenant if this is the central one, and central if this is the tenant one). * diff --git a/src/ResourceSyncing/TriggerSyncingEvents.php b/src/ResourceSyncing/TriggerSyncingEvents.php index 059eb579..eec1b13d 100644 --- a/src/ResourceSyncing/TriggerSyncingEvents.php +++ b/src/ResourceSyncing/TriggerSyncingEvents.php @@ -7,7 +7,6 @@ namespace Stancl\Tenancy\ResourceSyncing; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\MorphPivot; use Illuminate\Database\Eloquent\Relations\Pivot; -use Illuminate\Database\Eloquent\Relations\Relation; use Stancl\Tenancy\Contracts\Tenant; use Stancl\Tenancy\Database\Contracts\TenantWithDatabase; @@ -21,14 +20,14 @@ trait TriggerSyncingEvents { public static function bootTriggerSyncingEvents(): void { - static::saving(static function (self $pivot) { + static::saving(function (self $pivot) { // Try getting the central resource to see if it is available // If it is not available, throw an exception to interrupt the saving process // And prevent creating a pivot record without a central resource $pivot->getCentralResourceAndTenant(); }); - static::saved(static function (self $pivot) { + static::saved(function (self $pivot) { /** * @var static&Pivot $pivot * @var SyncMaster|null $centralResource @@ -41,7 +40,7 @@ trait TriggerSyncingEvents } }); - static::deleting(static function (self $pivot) { + static::deleting(function (self $pivot) { /** * @var static&Pivot $pivot * @var SyncMaster|null $centralResource @@ -80,13 +79,13 @@ trait TriggerSyncingEvents */ protected function getResourceClass(): string { - /** @var $this&(Pivot|MorphPivot|((Pivot|MorphPivot)&PivotWithCentralResource)) $this */ - if ($this instanceof PivotWithCentralResource) { - return $this->getCentralResourceClass(); + /** @var $this&(Pivot|MorphPivot|((Pivot|MorphPivot)&PivotWithRelation)) $this */ + if ($this instanceof PivotWithRelation) { + return $this->getRelatedModel()::class; } if ($this instanceof MorphPivot) { - return Relation::getMorphedModel($this->morphClass) ?? $this->morphClass; + return $this->morphClass; } throw new CentralResourceNotAvailableInPivotException; diff --git a/tests/Etc/ResourceSyncing/CentralUser.php b/tests/Etc/ResourceSyncing/CentralUser.php index ece09550..1533bd21 100644 --- a/tests/Etc/ResourceSyncing/CentralUser.php +++ b/tests/Etc/ResourceSyncing/CentralUser.php @@ -12,7 +12,6 @@ use Stancl\Tenancy\ResourceSyncing\SyncMaster; class CentralUser extends Model implements SyncMaster { use ResourceSyncing, CentralConnection; - protected $guarded = []; public $timestamps = false; diff --git a/tests/Etc/ResourceSyncing/CustomPivot.php b/tests/Etc/ResourceSyncing/CustomPivot.php index 2ffca4c0..00a019c9 100644 --- a/tests/Etc/ResourceSyncing/CustomPivot.php +++ b/tests/Etc/ResourceSyncing/CustomPivot.php @@ -4,13 +4,20 @@ declare(strict_types=1); namespace Stancl\Tenancy\Tests\Etc\ResourceSyncing; -use Stancl\Tenancy\ResourceSyncing\PivotWithCentralResource; +use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\BelongsToMany; +use Stancl\Tenancy\ResourceSyncing\PivotWithRelation; use Stancl\Tenancy\ResourceSyncing\TenantPivot; -class CustomPivot extends TenantPivot implements PivotWithCentralResource +class CustomPivot extends TenantPivot implements PivotWithRelation { - public function getCentralResourceClass(): string + public function users(): BelongsToMany { - return CentralUser::class; + return $this->belongsToMany(CentralUser::class); + } + + public function getRelatedModel(): Model + { + return $this->users()->getModel(); } } diff --git a/tests/Etc/synced_resource_migrations/2020_05_11_000002_create_tenant_users_table.php b/tests/Etc/synced_resource_migrations/2020_05_11_000002_create_tenant_users_table.php index dcd667a6..0aafd23c 100644 --- a/tests/Etc/synced_resource_migrations/2020_05_11_000002_create_tenant_users_table.php +++ b/tests/Etc/synced_resource_migrations/2020_05_11_000002_create_tenant_users_table.php @@ -16,6 +16,9 @@ class CreateTenantUsersTable extends Migration $table->string('global_user_id'); $table->unique(['tenant_id', 'global_user_id']); + + $table->foreign('tenant_id')->references('id')->on('tenants')->onUpdate('cascade')->onDelete('cascade'); + $table->foreign('global_user_id')->references('global_id')->on('users')->onUpdate('cascade')->onDelete('cascade'); }); } diff --git a/tests/ResourceSyncingTest.php b/tests/ResourceSyncingTest.php index 11a172c5..3250c37a 100644 --- a/tests/ResourceSyncingTest.php +++ b/tests/ResourceSyncingTest.php @@ -46,13 +46,6 @@ use Stancl\Tenancy\ResourceSyncing\Events\SyncedResourceSavedInForeignDatabase; use Illuminate\Database\Eloquent\Scope; use Illuminate\Database\QueryException; use function Stancl\Tenancy\Tests\pest; -use Illuminate\Support\Facades\Schema; -use Illuminate\Database\Schema\Blueprint; -use Stancl\Tenancy\Events\TenantDeleted; -use Stancl\Tenancy\ResourceSyncing\Events\SyncedResourceDeleted; -use Stancl\Tenancy\ResourceSyncing\Listeners\DeleteAllTenantMappings; -use Stancl\Tenancy\ResourceSyncing\Listeners\DeleteResourceMapping; -use Illuminate\Database\Eloquent\Relations\Relation; beforeEach(function () { config(['tenancy.bootstrappers' => [ @@ -76,7 +69,6 @@ beforeEach(function () { CreateTenantResource::$shouldQueue = false; DeleteResourceInTenant::$shouldQueue = false; UpdateOrCreateSyncedResource::$scopeGetModelQuery = null; - DeleteAllTenantMappings::$pivotTables = ['tenant_resources' => 'tenant_id']; // Reset global scopes on models (should happen automatically but to make this more explicit) Model::clearBootedModels(); @@ -100,7 +92,6 @@ beforeEach(function () { CentralUser::$creationAttributes = $creationAttributes; Event::listen(SyncedResourceSaved::class, UpdateOrCreateSyncedResource::class); - Event::listen(SyncedResourceDeleted::class, DeleteResourceMapping::class); Event::listen(SyncMasterDeleted::class, DeleteResourcesInTenants::class); Event::listen(SyncMasterRestored::class, RestoreResourcesInTenants::class); Event::listen(CentralResourceAttachedToTenant::class, CreateTenantResource::class); @@ -264,7 +255,7 @@ test('attaching central resources to tenants or vice versa creates synced tenant expect(TenantUser::all())->toHaveCount(0); }); - // Attaching resources to tenants requires using a pivot that implements the PivotWithCentralResource interface + // Attaching resources to tenants requires using a pivot that implements the PivotWithRelation interface $tenant->customPivotUsers()->attach($createCentralUser()); $createCentralUser()->tenants()->attach($tenant); @@ -288,7 +279,7 @@ test('detaching central users from tenants or vice versa force deletes the synce migrateUsersTableForTenants(); if ($attachUserToTenant) { - // Attaching resources to tenants requires using a pivot that implements the PivotWithCentralResource interface + // Attaching resources to tenants requires using a pivot that implements the PivotWithRelation interface $tenant->customPivotUsers()->attach($centralUser); } else { $centralUser->tenants()->attach($tenant); @@ -299,7 +290,7 @@ test('detaching central users from tenants or vice versa force deletes the synce }); if ($attachUserToTenant) { - // Detaching resources from tenants requires using a pivot that implements the PivotWithCentralResource interface + // Detaching resources from tenants requires using a pivot that implements the PivotWithRelation interface $tenant->customPivotUsers()->detach($centralUser); } else { $centralUser->tenants()->detach($tenant); @@ -334,7 +325,7 @@ test('detaching central users from tenants or vice versa force deletes the synce }); if ($attachUserToTenant) { - // Detaching resources from tenants requires using a pivot that implements the PivotWithCentralResource interface + // Detaching resources from tenants requires using a pivot that implements the PivotWithRelation interface $tenant->customPivotUsers()->detach($centralUserWithSoftDeletes); } else { $centralUserWithSoftDeletes->tenants()->detach($tenant); @@ -899,54 +890,7 @@ test('deleting SyncMaster automatically deletes its Syncables', function (bool $ 'basic pivot' => false, ]); -test('tenant pivot records are deleted along with the tenants to which they belong', function (bool $dbLevelOnCascadeDelete, bool $morphPivot) { - [$tenant] = createTenantsAndRunMigrations(); - - if ($morphPivot) { - config(['tenancy.models.tenant' => MorphTenant::class]); - $centralUserModel = BaseCentralUser::class; - - // The default pivot table, no need to configure the listener - $pivotTable = 'tenant_resources'; - } else { - $centralUserModel = CentralUser::class; - - // Custom pivot table - $pivotTable = 'tenant_users'; - } - - if ($dbLevelOnCascadeDelete) { - addTenantIdConstraintToPivot($pivotTable); - } else { - // Event-based cleanup - Event::listen(TenantDeleted::class, DeleteAllTenantMappings::class); - - DeleteAllTenantMappings::$pivotTables = [$pivotTable => 'tenant_id']; - } - - $syncMaster = $centralUserModel::create([ - 'global_id' => 'user', - 'name' => 'Central user', - 'email' => 'central@localhost', - 'password' => 'password', - 'role' => 'user', - ]); - - $syncMaster->tenants()->attach($tenant); - - // Pivot records should be deleted along with the tenant - $tenant->delete(); - - expect(DB::select("SELECT * FROM {$pivotTable} WHERE tenant_id = ?", [$tenant->getTenantKey()]))->toHaveCount(0); -})->with([ - 'db level on cascade delete' => true, - 'event-based on cascade delete' => false, -])->with([ - 'polymorphic pivot' => true, - 'basic pivot' => false, -]); - -test('pivot record is automatically deleted with the tenant resource', function() { +test('tenant pivot records are deleted along with the tenants to which they belong to', function() { [$tenant] = createTenantsAndRunMigrations(); $syncMaster = CentralUser::create([ @@ -959,54 +903,10 @@ test('pivot record is automatically deleted with the tenant resource', function( $syncMaster->tenants()->attach($tenant); - expect(DB::select("SELECT * FROM tenant_users WHERE tenant_id = ?", [$tenant->id]))->toHaveCount(1); + $tenant->delete(); - $tenant->run(function () { - TenantUser::firstWhere('global_id', 'cascade_user')->delete(); - }); - - // Deleting tenant resource deletes its pivot record - expect(DB::select("SELECT * FROM tenant_users WHERE tenant_id = ?", [$tenant->id]))->toHaveCount(0); - - // The same works with forceDelete - addExtraColumns(true); - - $syncMaster = CentralUserWithSoftDeletes::create([ - 'global_id' => 'force_cascade_user', - 'name' => 'Central user', - 'email' => 'central2@localhost', - 'password' => 'password', - 'role' => 'force_cascade_user', - 'foo' => 'bar', - ]); - - $syncMaster->tenants()->attach($tenant); - - expect(DB::select("SELECT * FROM tenant_users WHERE tenant_id = ?", [$tenant->id]))->toHaveCount(1); - - $tenant->run(function () { - TenantUserWithSoftDeletes::firstWhere('global_id', 'force_cascade_user')->forceDelete(); - }); - - expect(DB::select("SELECT * FROM tenant_users WHERE tenant_id = ?", [$tenant->id]))->toHaveCount(0); -}); - -test('DeleteAllTenantMappings handles incorrect configuration correctly', function() { - Event::listen(TenantDeleted::class, DeleteAllTenantMappings::class); - - [$tenant1, $tenant2] = createTenantsAndRunMigrations(); - - // Existing table, non-existent tenant key column - // The listener should throw an 'unknown column' exception - DeleteAllTenantMappings::$pivotTables = ['tenant_users' => 'non_existent_column']; - - // Should throw an exception when tenant is deleted - expect(fn() => $tenant1->delete())->toThrow(QueryException::class, "Unknown column 'non_existent_column' in 'where clause'"); - - // Non-existent table - DeleteAllTenantMappings::$pivotTables = ['nonexistent_pivot' => 'non_existent_column']; - - expect(fn() => $tenant2->delete())->toThrow(QueryException::class, "Table 'main.nonexistent_pivot' doesn't exist"); + // Deleting tenant deletes its pivot records + expect(DB::select("SELECT * FROM tenant_users WHERE tenant_id = ?", [$tenant->getTenantKey()]))->toHaveCount(0); }); test('trashed resources are synced correctly', function () { @@ -1365,60 +1265,6 @@ test('global scopes on syncable models can break resource syncing', function () expect($tenant1->run(fn () => TenantUser::first()->name))->toBe('tenant2 user'); }); -test('attach and detach events are handled correctly when using morph maps', function() { - config(['tenancy.models.tenant' => MorphTenant::class]); - [$tenant] = createTenantsAndRunMigrations(); - migrateCompaniesTableForTenants(); - - Relation::morphMap([ - 'users' => BaseCentralUser::class, - 'companies' => CentralCompany::class, - ]); - - $centralUser = BaseCentralUser::create([ - 'global_id' => 'user', - 'name' => 'Central user', - 'email' => 'central@localhost', - 'password' => 'password', - 'role' => 'user', - ]); - - $centralCompany = CentralCompany::create([ - 'global_id' => 'company', - 'name' => 'Central company', - 'email' => 'company@localhost', - ]); - - $tenant->users()->attach($centralUser); - $tenant->companies()->attach($centralCompany); - - // Assert all tenant_resources mappings actually use the configured morph map - expect(DB::table('tenant_resources')->count()) - ->toBe(DB::table('tenant_resources')->whereIn('tenant_resources_type', ['users', 'companies'])->count()); - - tenancy()->initialize($tenant); - - expect(BaseTenantUser::whereGlobalId('user')->first())->not()->toBeNull(); - expect(TenantCompany::whereGlobalId('company')->first())->not()->toBeNull(); - - tenancy()->end(); - - $tenant->users()->detach($centralUser); - $tenant->companies()->detach($centralCompany); - - tenancy()->initialize($tenant); - - expect(BaseTenantUser::whereGlobalId('user')->first())->toBeNull(); - expect(TenantCompany::whereGlobalId('company')->first())->toBeNull(); -}); - -function addTenantIdConstraintToPivot(string $pivotTable): void -{ - Schema::table($pivotTable, function (Blueprint $table) { - $table->foreign('tenant_id')->references('id')->on('tenants')->onDelete('cascade'); - }); -} - /** * Create two tenants and run migrations for those tenants. *