From 77c5ae1f32e8c54510a37206f8d96786fd766031 Mon Sep 17 00:00:00 2001 From: Abrar Ahmad Date: Thu, 3 Nov 2022 21:51:29 +0500 Subject: [PATCH] [4.x] Configure attributes for synced resources when creating models (#915) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * configure attributes for creating resource * Update ResourceSyncingTest.php * Update ci.yml * Update ResourceSyncingTest.php * Update ci.yml * cs * comments * Update tests/ResourceSyncingTest.php Co-authored-by: Samuel Štancl * improve comments, move method to `SyncMaster` interface * Revert "improve comments, move method to `SyncMaster` interface" This reverts commit 5ddd50deb9f5aa2ad0ebadec5bd5afcbf7e1257e. * Update ResourceSyncingTest.php * Update ResourceSyncingTest.php * update comment * Update ResourceSyncingTest.php * Update ResourceSyncingTest.php * wip * wip * wip * add a todo * assert that creation attributes returns null * classes at the end * rename method to `getAttributesForCreation` * Update ResourceSyncingTest.php * update comments * Fix little grammer * merge default values with sync attributes and tests * Update ResourceSyncingTest.php * method rename * method rename * Update ResourceSyncingTest.php * comments * Update ResourceSyncingTest.php * allow defining a mix of attribute names and default values * add test * code improvements * Fix code style (php-cs-fixer) * remove unused import * fix all phpstan issues in resource syncing code * Fix code style (php-cs-fixer) * wip * improve tests * Update ResourceSyncingTest.php * better names * Update UpdateSyncedResource.php * code style * Update UpdateSyncedResource.php * add comments above new tests * methods dockblocks and correct names * Update ResourceSyncingTest.php * update comments * remove different schema setup * delete custom migrations * self review * grammar, code style * refactor helpers for creating tenants Co-authored-by: Samuel Štancl Co-authored-by: Samuel Štancl Co-authored-by: PHP CS Fixer --- src/Contracts/Syncable.php | 3 + src/Database/Concerns/ResourceSyncing.php | 5 + src/Events/SyncedResourceSaved.php | 13 +- src/Listeners/UpdateSyncedResource.php | 86 +++- ...dd_extra_column_to_central_users_table.php | 26 ++ tests/ResourceSyncingTest.php | 373 +++++++++++++++++- 6 files changed, 469 insertions(+), 37 deletions(-) create mode 100644 tests/Etc/synced_resource_migrations/users_extra/2019_08_08_000009_add_extra_column_to_central_users_table.php diff --git a/src/Contracts/Syncable.php b/src/Contracts/Syncable.php index e09f4f7e..a481f318 100644 --- a/src/Contracts/Syncable.php +++ b/src/Contracts/Syncable.php @@ -15,4 +15,7 @@ interface Syncable public function getSyncedAttributeNames(): array; public function triggerSyncEvent(): 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). */ + public function getSyncedCreationAttributes(): array|null; // todo come up with a better name } diff --git a/src/Database/Concerns/ResourceSyncing.php b/src/Database/Concerns/ResourceSyncing.php index df5b0766..fd63738d 100644 --- a/src/Database/Concerns/ResourceSyncing.php +++ b/src/Database/Concerns/ResourceSyncing.php @@ -32,4 +32,9 @@ trait ResourceSyncing /** @var Syncable $this */ event(new SyncedResourceSaved($this, tenant())); } + + public function getSyncedCreationAttributes(): array|null + { + return null; + } } diff --git a/src/Events/SyncedResourceSaved.php b/src/Events/SyncedResourceSaved.php index 72d34d16..5c3b1334 100644 --- a/src/Events/SyncedResourceSaved.php +++ b/src/Events/SyncedResourceSaved.php @@ -10,14 +10,9 @@ use Stancl\Tenancy\Database\Contracts\TenantWithDatabase; class SyncedResourceSaved { - public Syncable&Model $model; - - /** @var (TenantWithDatabase&Model)|null */ - public TenantWithDatabase|null $tenant; - - public function __construct(Syncable $model, TenantWithDatabase|null $tenant) - { - $this->model = $model; - $this->tenant = $tenant; + public function __construct( + public Syncable&Model $model, + public TenantWithDatabase|null $tenant, + ) { } } diff --git a/src/Listeners/UpdateSyncedResource.php b/src/Listeners/UpdateSyncedResource.php index 45f73516..39391eac 100644 --- a/src/Listeners/UpdateSyncedResource.php +++ b/src/Listeners/UpdateSyncedResource.php @@ -4,14 +4,19 @@ declare(strict_types=1); namespace Stancl\Tenancy\Listeners; -use Illuminate\Database\Eloquent\Collection as EloquentCollection; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\Pivot; +use Illuminate\Support\Arr; +use Stancl\Tenancy\Contracts\Syncable; use Stancl\Tenancy\Contracts\SyncMaster; +use Stancl\Tenancy\Contracts\Tenant; +use Stancl\Tenancy\Database\TenantCollection; use Stancl\Tenancy\Events\SyncedResourceChangedInForeignDatabase; use Stancl\Tenancy\Events\SyncedResourceSaved; use Stancl\Tenancy\Exceptions\ModelNotSyncMasterException; +// todo@v4 review all code related to resource syncing + class UpdateSyncedResource extends QueueableListener { public static bool $shouldQueue = false; @@ -30,25 +35,28 @@ class UpdateSyncedResource extends QueueableListener $this->updateResourceInTenantDatabases($tenants, $event, $syncedAttributes); } - protected function getTenantsForCentralModel($centralModel): EloquentCollection + protected function getTenantsForCentralModel(Syncable $centralModel): TenantCollection { if (! $centralModel instanceof SyncMaster) { // If we're trying to use a tenant User model instead of the central User model, for example. throw new ModelNotSyncMasterException(get_class($centralModel)); } - /** @var SyncMaster|Model $centralModel */ + /** @var Tenant&Model&SyncMaster $centralModel */ // Since this model is "dirty" (taken by reference from the event), it might have the tenants // relationship already loaded and cached. For this reason, we refresh the relationship. $centralModel->load('tenants'); - return $centralModel->tenants; + /** @var TenantCollection $tenants */ + $tenants = $centralModel->tenants; + + return $tenants; } - protected function updateResourceInCentralDatabaseAndGetTenants($event, $syncedAttributes): EloquentCollection + protected function updateResourceInCentralDatabaseAndGetTenants(SyncedResourceSaved $event, array $syncedAttributes): TenantCollection { - /** @var Model|SyncMaster $centralModel */ + /** @var (Model&SyncMaster)|null $centralModel */ $centralModel = $event->model->getCentralModelName()::where($event->model->getGlobalIdentifierKeyName(), $event->model->getGlobalIdentifierKey()) ->first(); @@ -59,15 +67,17 @@ class UpdateSyncedResource extends QueueableListener event(new SyncedResourceChangedInForeignDatabase($event->model, null)); } else { // If the resource doesn't exist at all in the central DB,we create - // the record with all attributes, not just the synced ones. - $centralModel = $event->model->getCentralModelName()::create($event->model->getAttributes()); + $centralModel = $event->model->getCentralModelName()::create($this->getAttributesForCreation($event->model)); event(new SyncedResourceChangedInForeignDatabase($event->model, null)); } }); // If the model was just created, the mapping of the tenant to the user likely doesn't exist, so we create it. $currentTenantMapping = function ($model) use ($event) { - return ((string) $model->pivot->tenant_id) === ((string) $event->tenant->getTenantKey()); + /** @var Tenant */ + $tenant = $event->tenant; + + return ((string) $model->pivot->tenant_id) === ((string) $tenant->getTenantKey()); }; $mappingExists = $centralModel->tenants->contains($currentTenantMapping); @@ -76,22 +86,29 @@ class UpdateSyncedResource extends QueueableListener // Here we should call TenantPivot, but we call general Pivot, so that this works // even if people use their own pivot model that is not based on our TenantPivot Pivot::withoutEvents(function () use ($centralModel, $event) { - $centralModel->tenants()->attach($event->tenant->getTenantKey()); + /** @var Tenant */ + $tenant = $event->tenant; + + $centralModel->tenants()->attach($tenant->getTenantKey()); }); } - return $centralModel->tenants->filter(function ($model) use ($currentTenantMapping) { + /** @var TenantCollection $tenants */ + $tenants = $centralModel->tenants->filter(function ($model) use ($currentTenantMapping) { // Remove the mapping for the current tenant. return ! $currentTenantMapping($model); }); + + return $tenants; } - protected function updateResourceInTenantDatabases($tenants, $event, $syncedAttributes): void + protected function updateResourceInTenantDatabases(TenantCollection $tenants, SyncedResourceSaved $event, array $syncedAttributes): void { tenancy()->runForMultiple($tenants, function ($tenant) use ($event, $syncedAttributes) { // Forget instance state and find the model, // again in the current tenant's context. + /** @var Model&Syncable $eventModel */ $eventModel = $event->model; if ($eventModel instanceof SyncMaster) { @@ -112,12 +129,53 @@ class UpdateSyncedResource extends QueueableListener if ($localModel) { $localModel->update($syncedAttributes); } else { - // When creating, we use all columns, not just the synced ones. - $localModel = $localModelClass::create($eventModel->getAttributes()); + $localModel = $localModelClass::create($this->getAttributesForCreation($eventModel)); } event(new SyncedResourceChangedInForeignDatabase($localModel, $tenant)); }); }); } + + protected function getAttributesForCreation(Model&Syncable $model): array + { + if (! $model->getSyncedCreationAttributes()) { + // Creation attributes are not specified so create the model as 1:1 copy + // exclude the "primary key" because we want primary key to handle by the target model to avoid duplication errors + $attributes = $model->getAttributes(); + unset($attributes[$model->getKeyName()]); + + return $attributes; + } + + if (Arr::isAssoc($model->getSyncedCreationAttributes())) { + // Developer provided the default values (key => value) or mix of default values and attribute names (values only) + // We will merge the default values with provided attributes and sync attributes + [$attributeNames, $defaultValues] = $this->getAttributeNamesAndDefaultValues($model); + $attributes = $model->only(array_merge($model->getSyncedAttributeNames(), $attributeNames)); + + return array_merge($attributes, $defaultValues); + } + + // Developer provided the attribute names, so we'll use them to pick model attributes + return $model->only($model->getSyncedCreationAttributes()); + } + + /** + * Split the attribute names (sequential index items) and default values (key => values). + */ + protected function getAttributeNamesAndDefaultValues(Model&Syncable $model): array + { + $syncedCreationAttributes = $model->getSyncedCreationAttributes() ?? []; + + $attributes = Arr::where($syncedCreationAttributes, function ($value, $key) { + return is_numeric($key); + }); + + $defaultValues = Arr::where($syncedCreationAttributes, function ($value, $key) { + return is_string($key); + }); + + return [$attributes, $defaultValues]; + } } diff --git a/tests/Etc/synced_resource_migrations/users_extra/2019_08_08_000009_add_extra_column_to_central_users_table.php b/tests/Etc/synced_resource_migrations/users_extra/2019_08_08_000009_add_extra_column_to_central_users_table.php new file mode 100644 index 00000000..bfa13cc1 --- /dev/null +++ b/tests/Etc/synced_resource_migrations/users_extra/2019_08_08_000009_add_extra_column_to_central_users_table.php @@ -0,0 +1,26 @@ +string('foo'); + }); + } + + public function down() + { + } +} diff --git a/tests/ResourceSyncingTest.php b/tests/ResourceSyncingTest.php index 214a9f47..430c52ef 100644 --- a/tests/ResourceSyncingTest.php +++ b/tests/ResourceSyncingTest.php @@ -44,9 +44,10 @@ beforeEach(function () { Event::listen(TenancyInitialized::class, BootstrapTenancy::class); Event::listen(TenancyEnded::class, RevertToCentralContext::class); - UpdateSyncedResource::$shouldQueue = false; // global state cleanup + UpdateSyncedResource::$shouldQueue = false; // Global state cleanup Event::listen(SyncedResourceSaved::class, UpdateSyncedResource::class); + // Run migrations on central connection pest()->artisan('migrate', [ '--path' => [ __DIR__ . '/Etc/synced_resource_migrations', @@ -83,7 +84,7 @@ test('only the synced columns are updated in the central db', function () { ]); $tenant = ResourceTenant::create(); - migrateTenantsResource(); + migrateUsersTableForTenants(); tenancy()->initialize($tenant); @@ -126,6 +127,231 @@ test('only the synced columns are updated in the central db', function () { ], ResourceUser::first()->getAttributes()); }); +// This tests attribute list on the central side, and default values on the tenant side +// Those two don't depend on each other, we're just testing having each option on each side +// using tests that combine the two, to avoid having an excessively long and complex test suite +test('sync resource creation works when central model provides attributes and resource model provides default values', function () { + [$tenant1, $tenant2] = createTenantsAndRunMigrations(); + + addExtraColumnToCentralDB(); + + $centralUser = CentralUserProvidingAttributeNames::create([ + 'global_id' => 'acme', + 'name' => 'John Doe', + 'email' => 'john@localhost', + 'password' => 'secret', + 'role' => 'commenter', + 'foo' => 'bar', // foo does not exist in resource model + ]); + + $tenant1->run(function () { + expect(ResourceUserProvidingDefaultValues::all())->toHaveCount(0); + }); + + // When central model provides the list of attributes, resource model will be created from the provided list of attributes' values + $centralUser->tenants()->attach('t1'); + + $tenant1->run(function () { + $resourceUser = ResourceUserProvidingDefaultValues::all(); + expect($resourceUser)->toHaveCount(1); + expect($resourceUser->first()->global_id)->toBe('acme'); + expect($resourceUser->first()->email)->toBe('john@localhost'); + // 'foo' attribute is not provided by central model + expect($resourceUser->first()->foo)->toBeNull(); + }); + + tenancy()->initialize($tenant2); + + // When resource model provides the list of default values, central model will be created from the provided list of default values + ResourceUserProvidingDefaultValues::create([ + 'global_id' => 'asdf', + 'name' => 'John Doe', + 'email' => 'john@localhost', + 'password' => 'secret', + 'role' => 'commenter', + ]); + + tenancy()->end(); + + // Assert central user was created using the list of default values + $centralUser = CentralUserProvidingAttributeNames::whereGlobalId('asdf')->first(); + expect($centralUser)->not()->toBeNull(); + expect($centralUser->name)->toBe('Default Name'); + expect($centralUser->email)->toBe('default@localhost'); + expect($centralUser->password)->toBe('password'); + expect($centralUser->role)->toBe('admin'); + expect($centralUser->foo)->toBe('bar'); +}); + +// This tests default values on the central side, and attribute list on the tenant side +// Those two don't depend on each other, we're just testing having each option on each side +// using tests that combine the two, to avoid having an excessively long and complex test suite +test('sync resource creation works when central model provides default values and resource model provides attributes', function () { + [$tenant1, $tenant2] = createTenantsAndRunMigrations(); + + addExtraColumnToCentralDB(); + + $centralUser = CentralUserProvidingDefaultValues::create([ + 'global_id' => 'acme', + 'name' => 'John Doe', + 'email' => 'john@localhost', + 'password' => 'secret', + 'role' => 'commenter', + 'foo' => 'bar', // foo does not exist in resource model + ]); + + $tenant1->run(function () { + expect(ResourceUserProvidingDefaultValues::all())->toHaveCount(0); + }); + + // When central model provides the list of default values, resource model will be created from the provided list of default values + $centralUser->tenants()->attach('t1'); + + $tenant1->run(function () { + // Assert resource user was created using the list of default values + $resourceUser = ResourceUserProvidingDefaultValues::first(); + expect($resourceUser)->not()->toBeNull(); + expect($resourceUser->global_id)->toBe('acme'); + expect($resourceUser->email)->toBe('default@localhost'); + expect($resourceUser->password)->toBe('password'); + expect($resourceUser->role)->toBe('admin'); + }); + + tenancy()->initialize($tenant2); + + // When resource model provides the list of attributes, central model will be created from the provided list of attributes' values + ResourceUserProvidingAttributeNames::create([ + 'global_id' => 'asdf', + 'name' => 'John Doe', + 'email' => 'john@localhost', + 'password' => 'secret', + 'role' => 'commenter', + ]); + + tenancy()->end(); + + // Assert central user was created using the list of provided attributes + $centralUser = CentralUserProvidingAttributeNames::whereGlobalId('asdf')->first(); + expect($centralUser)->not()->toBeNull(); + expect($centralUser->email)->toBe('john@localhost'); + expect($centralUser->password)->toBe('secret'); + expect($centralUser->role)->toBe('commenter'); +}); + +// This tests mixed attribute list/defaults on the central side, and no specified attributes on the tenant side +// Those two don't depend on each other, we're just testing having each option on each side +// using tests that combine the two, to avoid having an excessively long and complex test suite +test('sync resource creation works when central model provides mixture and resource model provides nothing', function () { + [$tenant1, $tenant2] = createTenantsAndRunMigrations(); + + $centralUser = CentralUserProvidingMixture::create([ + 'global_id' => 'acme', + 'name' => 'John Doe', + 'email' => 'john@localhost', + 'password' => 'password', + 'role' => 'commentator' + ]); + + $tenant1->run(function () { + expect(ResourceUser::all())->toHaveCount(0); + }); + + // When central model provides the list of a mixture (attributes and default values), resource model will be created from the provided list of mixture (attributes and default values) + $centralUser->tenants()->attach('t1'); + + $tenant1->run(function () { + $resourceUser = ResourceUser::first(); + + // Assert resource user was created using the provided attributes and default values + expect($resourceUser->global_id)->toBe('acme'); + expect($resourceUser->name)->toBe('John Doe'); + expect($resourceUser->email)->toBe('john@localhost'); + // default values + expect($resourceUser->role)->toBe('admin'); + expect($resourceUser->password)->toBe('secret'); + }); + + tenancy()->initialize($tenant2); + + // When resource model provides nothing/null, the central model will be created as a 1:1 copy of resource model + $resourceUser = ResourceUser::create([ + 'global_id' => 'acmey', + 'name' => 'John Doe', + 'email' => 'john@localhost', + 'password' => 'password', + 'role' => 'commentator' + ]); + + tenancy()->end(); + + $centralUser = CentralUserProvidingMixture::whereGlobalId('acmey')->first(); + expect($resourceUser->getSyncedCreationAttributes())->toBeNull(); + + $centralUser = $centralUser->toArray(); + $resourceUser = $resourceUser->toArray(); + unset($centralUser['id']); + unset($resourceUser['id']); + + // Assert central user created as 1:1 copy of resource model except "id" + expect($centralUser)->toBe($resourceUser); +}); + +// This tests no specified attributes on the central side, and mixed attribute list/defaults on the tenant side +// Those two don't depend on each other, we're just testing having each option on each side +// using tests that combine the two, to avoid having an excessively long and complex test suite +test('sync resource creation works when central model provides nothing and resource model provides mixture', function () { + [$tenant1, $tenant2] = createTenantsAndRunMigrations(); + + $centralUser = CentralUser::create([ + 'global_id' => 'acme', + 'name' => 'John Doe', + 'email' => 'john@localhost', + 'password' => 'password', + 'role' => 'commenter', + ]); + + $tenant1->run(function () { + expect(ResourceUserProvidingMixture::all())->toHaveCount(0); + }); + + // When central model provides nothing/null, the resource model will be created as a 1:1 copy of central model + $centralUser->tenants()->attach('t1'); + + expect($centralUser->getSyncedCreationAttributes())->toBeNull(); + $tenant1->run(function () use ($centralUser) { + $resourceUser = ResourceUserProvidingMixture::first(); + expect($resourceUser)->not()->toBeNull(); + $resourceUser = $resourceUser->toArray(); + $centralUser = $centralUser->withoutRelations()->toArray(); + unset($resourceUser['id']); + unset($centralUser['id']); + + expect($resourceUser)->toBe($centralUser); + }); + + tenancy()->initialize($tenant2); + + // When resource model provides the list of a mixture (attributes and default values), central model will be created from the provided list of mixture (attributes and default values) + ResourceUserProvidingMixture::create([ + 'global_id' => 'absd', + 'name' => 'John Doe', + 'email' => 'john@localhost', + 'password' => 'password', + 'role' => 'commenter', + ]); + + tenancy()->end(); + + $centralUser = CentralUser::whereGlobalId('absd')->first(); + + // Assert central user was created using the provided list of attributes and default values + expect($centralUser->name)->toBe('John Doe'); + expect($centralUser->email)->toBe('john@localhost'); + // default values + expect($centralUser->role)->toBe('admin'); + expect($centralUser->password)->toBe('secret'); +}); + test('creating the resource in tenant database creates it in central database and creates the mapping', function () { creatingResourceInTenantDatabaseCreatesAndMapInCentralDatabase(); }); @@ -152,7 +378,7 @@ test('attaching a tenant to the central resource triggers a pull from the tenant $tenant = ResourceTenant::create([ 'id' => 't1', ]); - migrateTenantsResource(); + migrateUsersTableForTenants(); $tenant->run(function () { expect(ResourceUser::all())->toHaveCount(0); @@ -177,7 +403,7 @@ test('attaching users to tenants does not do anything', function () { $tenant = ResourceTenant::create([ 'id' => 't1', ]); - migrateTenantsResource(); + migrateUsersTableForTenants(); $tenant->run(function () { expect(ResourceUser::all())->toHaveCount(0); @@ -212,7 +438,7 @@ test('resources are synced only to workspaces that have the resource', function $t3 = ResourceTenant::create([ 'id' => 't3', ]); - migrateTenantsResource(); + migrateUsersTableForTenants(); $centralUser->tenants()->attach('t1'); $centralUser->tenants()->attach('t2'); @@ -250,7 +476,7 @@ test('when a resource exists in other tenant dbs but is created in a tenant db t $t2 = ResourceTenant::create([ 'id' => 't2', ]); - migrateTenantsResource(); + migrateUsersTableForTenants(); // Copy (cascade) user to t1 DB $centralUser->tenants()->attach('t1'); @@ -298,7 +524,7 @@ test('the synced columns are updated in other tenant dbs where the resource exis $t3 = ResourceTenant::create([ 'id' => 't3', ]); - migrateTenantsResource(); + migrateUsersTableForTenants(); // Copy (cascade) user to t1 DB $centralUser->tenants()->attach('t1'); @@ -353,7 +579,7 @@ test('when the resource doesnt exist in the tenant db non synced columns will ca 'id' => 't1', ]); - migrateTenantsResource(); + migrateUsersTableForTenants(); $centralUser->tenants()->attach('t1'); @@ -367,7 +593,7 @@ test('when the resource doesnt exist in the central db non synced columns will b 'id' => 't1', ]); - migrateTenantsResource(); + migrateUsersTableForTenants(); $t1->run(function () { ResourceUser::create([ @@ -389,7 +615,7 @@ test('the listener can be queued', function () { 'id' => 't1', ]); - migrateTenantsResource(); + migrateUsersTableForTenants(); Queue::assertNothingPushed(); @@ -428,7 +654,7 @@ test('an event is fired for all touched resources', function () { $t3 = ResourceTenant::create([ 'id' => 't3', ]); - migrateTenantsResource(); + migrateUsersTableForTenants(); // Copy (cascade) user to t1 DB $centralUser->tenants()->attach('t1'); @@ -509,7 +735,7 @@ function creatingResourceInTenantDatabaseCreatesAndMapInCentralDatabase() expect(ResourceUser::all())->toHaveCount(0); $tenant = ResourceTenant::create(); - migrateTenantsResource(); + migrateUsersTableForTenants(); tenancy()->initialize($tenant); @@ -524,7 +750,7 @@ function creatingResourceInTenantDatabaseCreatesAndMapInCentralDatabase() tenancy()->end(); - // Asset user was created + // Assert user was created expect(CentralUser::first()->global_id)->toBe('acme'); expect(CentralUser::first()->role)->toBe('commenter'); @@ -537,7 +763,28 @@ function creatingResourceInTenantDatabaseCreatesAndMapInCentralDatabase() expect(ResourceUser::first()->role)->toBe('commenter'); } -function migrateTenantsResource() +/** + * Create two tenants and run migrations for those tenants. + */ +function createTenantsAndRunMigrations(): array +{ + [$tenant1, $tenant2] = [ResourceTenant::create(['id' => 't1']), ResourceTenant::create(['id' => 't2'])]; + + migrateUsersTableForTenants(); + + return [$tenant1, $tenant2]; +} + +function addExtraColumnToCentralDB(): void +{ + // migrate extra column "foo" in central DB + pest()->artisan('migrate', [ + '--path' => __DIR__ . '/Etc/synced_resource_migrations/users_extra', + '--realpath' => true, + ])->assertExitCode(0); +} + +function migrateUsersTableForTenants(): void { pest()->artisan('tenants:migrate', [ '--path' => __DIR__ . '/Etc/synced_resource_migrations/users', @@ -593,6 +840,7 @@ class CentralUser extends Model implements SyncMaster public function getSyncedAttributeNames(): array { return [ + 'global_id', 'name', 'password', 'email', @@ -628,9 +876,106 @@ class ResourceUser extends Model implements Syncable public function getSyncedAttributeNames(): array { return [ + 'global_id', 'name', 'password', 'email', ]; } } + +// override method in ResourceUser class to return default attribute values +class ResourceUserProvidingDefaultValues extends ResourceUser +{ + public function getSyncedCreationAttributes(): array + { + // Default values when creating resources from tenant to central DB + return + [ + 'name' => 'Default Name', + 'email' => 'default@localhost', + 'password' => 'password', + 'role' => 'admin', + 'foo' => 'bar' + ]; + } +} + +// override method in ResourceUser class to return attribute names +class ResourceUserProvidingAttributeNames extends ResourceUser +{ + public function getSyncedCreationAttributes(): array + { + // Attributes used when creating resources from tenant to central DB + // Notice here we are not adding "code" filed because it doesn't + // exist in central model + return + [ + 'name', + 'password', + 'email', + 'role', + 'foo' => 'bar' + ]; + } + +} + +// override method in CentralUser class to return attribute default values +class CentralUserProvidingDefaultValues extends CentralUser +{ + public function getSyncedCreationAttributes(): array + { + // Attributes default values when creating resources from central to tenant model + return + [ + 'name' => 'Default User', + 'email' => 'default@localhost', + 'password' => 'password', + 'role' => 'admin', + ]; + } +} + +// override method in CentralUser class to return attribute names +class CentralUserProvidingAttributeNames extends CentralUser +{ + public function getSyncedCreationAttributes(): array + { + // Attributes used when creating resources from central to tenant DB + return + [ + 'global_id', + 'name', + 'password', + 'email', + 'role', + ]; + } +} + +class CentralUserProvidingMixture extends CentralUser +{ + public function getSyncedCreationAttributes(): array + { + return [ + 'name', + 'email', + 'role' => 'admin', + 'password' => 'secret', + ]; + } +} + +class ResourceUserProvidingMixture extends ResourceUser +{ + public function getSyncedCreationAttributes(): array + { + return [ + 'name', + 'email', + 'role' => 'admin', + 'password' => 'secret', + ]; + } +}