resolveDraftForIdentity($workspace, $entraTenantId, $preferredDraft); $isNew = ! $draft instanceof TenantOnboardingSession; $wasCreated = $isNew; if ($isNew) { $draft = new TenantOnboardingSession; $draft->workspace_id = (int) $workspace->getKey(); $draft->entra_tenant_id = $entraTenantId; $draft->started_by_user_id = (int) $actor->getKey(); $draft->version = 0; } elseif ($expectedVersion !== null) { $this->assertExpectedVersion($draft, $expectedVersion); } $draft->entra_tenant_id = $entraTenantId; $draft->updated_by_user_id = (int) $actor->getKey(); $mutator($draft); $this->persistDraft( draft: $draft, incrementVersion: $incrementVersion || $isNew, ); return $draft->refresh(); }); } /** * @param callable(TenantOnboardingSession):void $mutator */ public function mutate( TenantOnboardingSession $draft, User $actor, callable $mutator, ?int $expectedVersion = null, bool $incrementVersion = true, bool $allowTerminal = false, ): TenantOnboardingSession { return DB::transaction(function () use ($draft, $actor, $mutator, $expectedVersion, $incrementVersion, $allowTerminal): TenantOnboardingSession { $lockedDraft = TenantOnboardingSession::query() ->whereKey($draft->getKey()) ->lockForUpdate() ->firstOrFail(); if (! $allowTerminal && $lockedDraft->lifecycleState()->isTerminal()) { throw new OnboardingDraftImmutableException( draftId: (int) $lockedDraft->getKey(), lifecycleState: $lockedDraft->lifecycleState(), ); } if ($expectedVersion !== null) { $this->assertExpectedVersion($lockedDraft, $expectedVersion); } $lockedDraft->updated_by_user_id = (int) $actor->getKey(); $mutator($lockedDraft); $this->persistDraft( draft: $lockedDraft, incrementVersion: $incrementVersion, ); return $lockedDraft->refresh(); }); } public function lockForTrustedMutation(TenantOnboardingSession|int|string $draft, Workspace $workspace): TenantOnboardingSession { $draftId = $draft instanceof TenantOnboardingSession ? (int) $draft->getKey() : (int) $draft; $lockedDraft = TenantOnboardingSession::query() ->whereKey($draftId) ->where('workspace_id', (int) $workspace->getKey()) ->lockForUpdate() ->first(); if (! $lockedDraft instanceof TenantOnboardingSession) { throw new NotFoundHttpException; } return $lockedDraft; } private function resolveDraftForIdentity( Workspace $workspace, string $entraTenantId, ?TenantOnboardingSession $preferredDraft = null, ): ?TenantOnboardingSession { if ($preferredDraft instanceof TenantOnboardingSession) { $lockedPreferredDraft = TenantOnboardingSession::query() ->whereKey($preferredDraft->getKey()) ->where('workspace_id', (int) $workspace->getKey()) ->lockForUpdate() ->first(); if ($lockedPreferredDraft instanceof TenantOnboardingSession && $lockedPreferredDraft->entra_tenant_id === $entraTenantId) { if ($lockedPreferredDraft->lifecycleState()->isTerminal()) { throw new OnboardingDraftImmutableException( draftId: (int) $lockedPreferredDraft->getKey(), lifecycleState: $lockedPreferredDraft->lifecycleState(), ); } return $lockedPreferredDraft; } } return TenantOnboardingSession::query() ->where('workspace_id', (int) $workspace->getKey()) ->where('entra_tenant_id', $entraTenantId) ->resumable() ->orderByDesc('updated_at') ->lockForUpdate() ->first(); } private function assertExpectedVersion(TenantOnboardingSession $draft, int $expectedVersion): void { $actualVersion = max(1, (int) ($draft->version ?? 1)); if ($expectedVersion === $actualVersion) { return; } throw new OnboardingDraftConflictException( draftId: (int) $draft->getKey(), expectedVersion: $expectedVersion, actualVersion: $actualVersion, ); } private function persistDraft(TenantOnboardingSession $draft, bool $incrementVersion): void { $currentVersion = max(0, (int) ($draft->version ?? 0)); $draft->version = $incrementVersion ? $currentVersion + 1 : max(1, $currentVersion); $this->lifecycleService->applySnapshot($draft, false); $draft->save(); } }