diff --git a/packages/actions/src/CreateAction.php b/packages/actions/src/CreateAction.php index d06b0de9ad..7d1887dde0 100644 --- a/packages/actions/src/CreateAction.php +++ b/packages/actions/src/CreateAction.php @@ -47,8 +47,6 @@ class CreateAction extends Action $this->record(null); - $this->databaseTransaction(); - $this->action(function (array $arguments, Form $form): void { $model = $this->getModel(); diff --git a/packages/actions/src/EditAction.php b/packages/actions/src/EditAction.php index 090ab208c4..3b19cbd0a7 100644 --- a/packages/actions/src/EditAction.php +++ b/packages/actions/src/EditAction.php @@ -47,8 +47,6 @@ class EditAction extends Action return $data; }); - $this->databaseTransaction(); - $this->action(function (): void { $this->process(function (array $data, HasActions $livewire, Model $record) { if ($translatableContentDriver = $livewire->makeFilamentTranslatableContentDriver()) { diff --git a/packages/panels/docs/09-configuration.md b/packages/panels/docs/09-configuration.md index 782ff1d85e..574ebbeba6 100644 --- a/packages/panels/docs/09-configuration.md +++ b/packages/panels/docs/09-configuration.md @@ -164,6 +164,65 @@ public function panel(Panel $panel): Panel > Please note: this feature is not compatible with [SPA mode](#spa-mode). +## Enabling database transactions + +By default, Filament does not wrap operations in database transactions, and allows the user to enable this themselves when they have tested to ensure that their operations are safe to be wrapped in a transaction. However, you can enable database transactions at once for all operations by using the `databaseTransactions()` method: + +```php +use Filament\Panel; + +public function panel(Panel $panel): Panel +{ + return $panel + // ... + ->databaseTransactions(); +} +``` + +For any actions you do not want to be wrapped in a transaction, you can use the `databaseTransactions(false)` method: + +```php +CreateAction::make() + ->databaseTransactions(false) +``` + +And for any pages like [Create resource](resources/creating-records) and [Edit resource](resources/editing-records), you can define the `$hasDatabaseTransactions` property to `false` on the page class: + +```php +use Filament\Resources\Pages\CreateRecord; + +class CreatePost extends CreateRecord +{ + protected ?bool $hasDatabaseTransactions = false; + + // ... +} +``` + +### Opting in to database transactions for specific actions and pages + +Instead of enabling database transactions everywhere and opting out of them for specific actions and pages, you can opt in to database transactions for specific actions and pages. + +For actions, you can use the `databaseTransactions()` method: + +```php +CreateAction::make() + ->databaseTransactions() +``` + +For pages like [Create resource](resources/creating-records) and [Edit resource](resources/editing-records), you can define the `$hasDatabaseTransactions` property to `true` on the page class: + +```php +use Filament\Resources\Pages\CreateRecord; + +class CreatePost extends CreateRecord +{ + protected ?bool $hasDatabaseTransactions = true; + + // ... +} +``` + ## Registering assets for a panel You can register [assets](../support/assets) that will only be loaded on pages within a specific panel, and not in the rest of the app. To do that, pass an array of assets to the `assets()` method: diff --git a/packages/panels/src/Pages/Auth/EditProfile.php b/packages/panels/src/Pages/Auth/EditProfile.php index 928e2bb730..6491a091fe 100644 --- a/packages/panels/src/Pages/Auth/EditProfile.php +++ b/packages/panels/src/Pages/Auth/EditProfile.php @@ -21,7 +21,6 @@ use Filament\Support\Facades\FilamentView; use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Contracts\Support\Htmlable; use Illuminate\Database\Eloquent\Model; -use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Route; use Illuminate\Validation\Rules\Password; @@ -34,6 +33,7 @@ use function Filament\Support\is_app_url; */ class EditProfile extends Page { + use Concerns\CanUseDatabaseTransactions; use Concerns\InteractsWithFormActions; /** @@ -145,7 +145,7 @@ class EditProfile extends Page public function save(): void { try { - DB::beginTransaction(); + $this->beginDatabaseTransaction(); $this->callHook('beforeValidate'); @@ -161,15 +161,15 @@ class EditProfile extends Page $this->callHook('afterSave'); - DB::commit(); + $this->commitDatabaseTransaction(); } catch (Halt $exception) { $exception->shouldRollbackDatabaseTransaction() ? - DB::rollBack() : - DB::commit(); + $this->rollBackDatabaseTransaction() : + $this->commitDatabaseTransaction(); return; } catch (Throwable $exception) { - DB::rollBack(); + $this->rollBackDatabaseTransaction(); throw $exception; } diff --git a/packages/panels/src/Pages/Auth/Register.php b/packages/panels/src/Pages/Auth/Register.php index 70a63c1c05..618bb95eed 100644 --- a/packages/panels/src/Pages/Auth/Register.php +++ b/packages/panels/src/Pages/Auth/Register.php @@ -15,6 +15,7 @@ use Filament\Forms\Form; use Filament\Http\Responses\Auth\Contracts\RegistrationResponse; use Filament\Notifications\Auth\VerifyEmail; use Filament\Notifications\Notification; +use Filament\Pages\Concerns\CanUseDatabaseTransactions; use Filament\Pages\Concerns\InteractsWithFormActions; use Filament\Pages\SimplePage; use Illuminate\Auth\EloquentUserProvider; @@ -22,7 +23,6 @@ use Illuminate\Auth\SessionGuard; use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Contracts\Support\Htmlable; use Illuminate\Database\Eloquent\Model; -use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Hash; use Illuminate\Validation\Rules\Password; @@ -31,6 +31,7 @@ use Illuminate\Validation\Rules\Password; */ class Register extends SimplePage { + use CanUseDatabaseTransactions; use InteractsWithFormActions; use WithRateLimiting; @@ -75,7 +76,7 @@ class Register extends SimplePage return null; } - $user = DB::transaction(function () { + $user = $this->wrapInDatabaseTransaction(function () { $data = $this->form->getState(); return $this->getUserModel()::create($data); diff --git a/packages/panels/src/Pages/Concerns/CanUseDatabaseTransactions.php b/packages/panels/src/Pages/Concerns/CanUseDatabaseTransactions.php new file mode 100644 index 0000000000..753e5ce754 --- /dev/null +++ b/packages/panels/src/Pages/Concerns/CanUseDatabaseTransactions.php @@ -0,0 +1,54 @@ +hasDatabaseTransactions ?? Filament::getCurrentPanel()->hasDatabaseTransactions(); + } + + protected function beginDatabaseTransaction(): void + { + if (! $this->hasDatabaseTransactions()) { + return; + } + + DB::beginTransaction(); + } + + protected function commitDatabaseTransaction(): void + { + if (! $this->hasDatabaseTransactions()) { + return; + } + + DB::commit(); + } + + protected function rollBackDatabaseTransaction(): void + { + if (! $this->hasDatabaseTransactions()) { + return; + } + + DB::rollBack(); + } + + protected function wrapInDatabaseTransaction(Closure $callback): mixed + { + if (! $this->hasDatabaseTransactions()) { + return $callback(); + } + + /** @phpstan-ignore-next-line */ + return DB::transaction($callback); + } +} diff --git a/packages/panels/src/Pages/Tenancy/EditTenantProfile.php b/packages/panels/src/Pages/Tenancy/EditTenantProfile.php index 907e8feda1..b2aa5dc030 100644 --- a/packages/panels/src/Pages/Tenancy/EditTenantProfile.php +++ b/packages/panels/src/Pages/Tenancy/EditTenantProfile.php @@ -15,7 +15,6 @@ use Filament\Support\Facades\FilamentView; use Illuminate\Auth\Access\AuthorizationException; use Illuminate\Contracts\Support\Htmlable; use Illuminate\Database\Eloquent\Model; -use Illuminate\Support\Facades\DB; use Livewire\Attributes\Locked; use Throwable; @@ -27,6 +26,7 @@ use function Filament\Support\is_app_url; */ abstract class EditTenantProfile extends Page { + use Concerns\CanUseDatabaseTransactions; use Concerns\HasRoutes; use Concerns\InteractsWithFormActions; @@ -110,7 +110,7 @@ abstract class EditTenantProfile extends Page public function save(): void { try { - DB::beginTransaction(); + $this->beginDatabaseTransaction(); $this->callHook('beforeValidate'); @@ -126,15 +126,15 @@ abstract class EditTenantProfile extends Page $this->callHook('afterSave'); - DB::commit(); + $this->commitDatabaseTransaction(); } catch (Halt $exception) { $exception->shouldRollbackDatabaseTransaction() ? - DB::rollBack() : - DB::commit(); + $this->rollBackDatabaseTransaction() : + $this->commitDatabaseTransaction(); return; } catch (Throwable $exception) { - DB::rollBack(); + $this->rollBackDatabaseTransaction(); throw $exception; } diff --git a/packages/panels/src/Pages/Tenancy/RegisterTenant.php b/packages/panels/src/Pages/Tenancy/RegisterTenant.php index fcae0c1fec..b741734874 100644 --- a/packages/panels/src/Pages/Tenancy/RegisterTenant.php +++ b/packages/panels/src/Pages/Tenancy/RegisterTenant.php @@ -15,7 +15,6 @@ use Filament\Support\Facades\FilamentView; use Illuminate\Auth\Access\AuthorizationException; use Illuminate\Contracts\Support\Htmlable; use Illuminate\Database\Eloquent\Model; -use Illuminate\Support\Facades\DB; use Throwable; use function Filament\authorize; @@ -26,6 +25,7 @@ use function Filament\Support\is_app_url; */ abstract class RegisterTenant extends SimplePage { + use Concerns\CanUseDatabaseTransactions; use Concerns\HasRoutes; use InteractsWithFormActions; @@ -72,7 +72,7 @@ abstract class RegisterTenant extends SimplePage public function register(): void { try { - DB::beginTransaction(); + $this->beginDatabaseTransaction(); $this->callHook('beforeValidate'); @@ -90,15 +90,15 @@ abstract class RegisterTenant extends SimplePage $this->callHook('afterRegister'); - DB::commit(); + $this->commitDatabaseTransaction(); } catch (Halt $exception) { $exception->shouldRollbackDatabaseTransaction() ? - DB::rollBack() : - DB::commit(); + $this->rollBackDatabaseTransaction() : + $this->commitDatabaseTransaction(); return; } catch (Throwable $exception) { - DB::rollBack(); + $this->rollBackDatabaseTransaction(); throw $exception; } diff --git a/packages/panels/src/Panel.php b/packages/panels/src/Panel.php index 6f84f84f93..1ea0f68516 100644 --- a/packages/panels/src/Panel.php +++ b/packages/panels/src/Panel.php @@ -3,6 +3,7 @@ namespace Filament; use Closure; +use Filament\Actions\MountableAction; use Filament\Support\Components\Component; use Filament\Support\Facades\FilamentColor; use Filament\Support\Facades\FilamentIcon; @@ -19,6 +20,7 @@ class Panel extends Component use Panel\Concerns\HasColors; use Panel\Concerns\HasComponents; use Panel\Concerns\HasDarkMode; + use Panel\Concerns\HasDatabaseTransactions; use Panel\Concerns\HasFavicon; use Panel\Concerns\HasFont; use Panel\Concerns\HasGlobalSearch; @@ -78,6 +80,12 @@ class Panel extends Component $this->registerRenderHooks(); + if ($this->hasDatabaseTransactions()) { + MountableAction::configureUsing( + fn (MountableAction $action) => $action->databaseTransaction(), + ); + } + foreach ($this->plugins as $plugin) { $plugin->boot($this); } diff --git a/packages/panels/src/Panel/Concerns/HasDatabaseTransactions.php b/packages/panels/src/Panel/Concerns/HasDatabaseTransactions.php new file mode 100644 index 0000000000..6d2e98c824 --- /dev/null +++ b/packages/panels/src/Panel/Concerns/HasDatabaseTransactions.php @@ -0,0 +1,22 @@ +hasDatabaseTransactions = $condition; + + return $this; + } + + public function hasDatabaseTransactions(): bool + { + return (bool) $this->evaluate($this->hasDatabaseTransactions); + } +} diff --git a/packages/panels/src/Resources/Pages/CreateRecord.php b/packages/panels/src/Resources/Pages/CreateRecord.php index 04d64d98a3..2489cfe3fc 100644 --- a/packages/panels/src/Resources/Pages/CreateRecord.php +++ b/packages/panels/src/Resources/Pages/CreateRecord.php @@ -7,6 +7,7 @@ use Filament\Actions\ActionGroup; use Filament\Facades\Filament; use Filament\Forms\Form; use Filament\Notifications\Notification; +use Filament\Pages\Concerns\CanUseDatabaseTransactions; use Filament\Pages\Concerns\HasUnsavedDataChangesAlert; use Filament\Pages\Concerns\InteractsWithFormActions; use Filament\Support\Exceptions\Halt; @@ -14,7 +15,6 @@ use Filament\Support\Facades\FilamentView; use Illuminate\Contracts\Support\Htmlable; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasManyThrough; -use Illuminate\Support\Facades\DB; use Throwable; use function Filament\Support\is_app_url; @@ -24,6 +24,7 @@ use function Filament\Support\is_app_url; */ class CreateRecord extends Page { + use CanUseDatabaseTransactions; use HasUnsavedDataChangesAlert; use InteractsWithFormActions; @@ -76,7 +77,7 @@ class CreateRecord extends Page $this->authorizeAccess(); try { - DB::beginTransaction(); + $this->beginDatabaseTransaction(); $this->callHook('beforeValidate'); @@ -94,15 +95,15 @@ class CreateRecord extends Page $this->callHook('afterCreate'); - DB::commit(); + $this->commitDatabaseTransaction(); } catch (Halt $exception) { $exception->shouldRollbackDatabaseTransaction() ? - DB::rollBack() : - DB::commit(); + $this->rollBackDatabaseTransaction() : + $this->commitDatabaseTransaction(); return; } catch (Throwable $exception) { - DB::rollBack(); + $this->rollBackDatabaseTransaction(); throw $exception; } diff --git a/packages/panels/src/Resources/Pages/EditRecord.php b/packages/panels/src/Resources/Pages/EditRecord.php index 22d1c3aa58..25d5daedcf 100644 --- a/packages/panels/src/Resources/Pages/EditRecord.php +++ b/packages/panels/src/Resources/Pages/EditRecord.php @@ -12,6 +12,7 @@ use Filament\Actions\ViewAction; use Filament\Forms\Form; use Filament\Infolists\Infolist; use Filament\Notifications\Notification; +use Filament\Pages\Concerns\CanUseDatabaseTransactions; use Filament\Pages\Concerns\HasUnsavedDataChangesAlert; use Filament\Pages\Concerns\InteractsWithFormActions; use Filament\Support\Exceptions\Halt; @@ -20,7 +21,6 @@ use Filament\Support\Facades\FilamentView; use Illuminate\Contracts\Support\Htmlable; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Arr; -use Illuminate\Support\Facades\DB; use Throwable; use function Filament\Support\is_app_url; @@ -30,6 +30,7 @@ use function Filament\Support\is_app_url; */ class EditRecord extends Page { + use CanUseDatabaseTransactions; use Concerns\HasRelationManagers; use Concerns\InteractsWithRecord { configureAction as configureActionRecord; @@ -132,7 +133,7 @@ class EditRecord extends Page $this->authorizeAccess(); try { - DB::beginTransaction(); + $this->beginDatabaseTransaction(); $this->callHook('beforeValidate'); @@ -148,15 +149,15 @@ class EditRecord extends Page $this->callHook('afterSave'); - DB::commit(); + $this->commitDatabaseTransaction(); } catch (Halt $exception) { $exception->shouldRollbackDatabaseTransaction() ? - DB::rollBack() : - DB::commit(); + $this->rollBackDatabaseTransaction() : + $this->commitDatabaseTransaction(); return; } catch (Throwable $exception) { - DB::rollBack(); + $this->rollBackDatabaseTransaction(); throw $exception; } diff --git a/packages/spatie-laravel-settings-plugin/src/Pages/SettingsPage.php b/packages/spatie-laravel-settings-plugin/src/Pages/SettingsPage.php index ebb20b802b..abf1cacbfb 100644 --- a/packages/spatie-laravel-settings-plugin/src/Pages/SettingsPage.php +++ b/packages/spatie-laravel-settings-plugin/src/Pages/SettingsPage.php @@ -7,9 +7,9 @@ use Filament\Actions\ActionGroup; use Filament\Forms\ComponentContainer; use Filament\Forms\Form; use Filament\Notifications\Notification; +use Filament\Pages\Concerns\CanUseDatabaseTransactions; use Filament\Support\Exceptions\Halt; use Filament\Support\Facades\FilamentView; -use Illuminate\Support\Facades\DB; use Throwable; use function Filament\Support\is_app_url; @@ -19,6 +19,7 @@ use function Filament\Support\is_app_url; */ class SettingsPage extends Page { + use CanUseDatabaseTransactions; use Concerns\InteractsWithFormActions; protected static string $settings; @@ -60,7 +61,7 @@ class SettingsPage extends Page public function save(): void { try { - DB::beginTransaction(); + $this->beginDatabaseTransaction(); $this->callHook('beforeValidate'); @@ -79,15 +80,15 @@ class SettingsPage extends Page $this->callHook('afterSave'); - DB::commit(); + $this->commitDatabaseTransaction(); } catch (Halt $exception) { $exception->shouldRollbackDatabaseTransaction() ? - DB::rollBack() : - DB::commit(); + $this->rollBackDatabaseTransaction() : + $this->commitDatabaseTransaction(); return; } catch (Throwable $exception) { - DB::rollBack(); + $this->rollBackDatabaseTransaction(); throw $exception; } diff --git a/packages/tables/src/Actions/CreateAction.php b/packages/tables/src/Actions/CreateAction.php index 5571c79b29..c1d8b8adb1 100644 --- a/packages/tables/src/Actions/CreateAction.php +++ b/packages/tables/src/Actions/CreateAction.php @@ -42,8 +42,6 @@ class CreateAction extends Action $this->successNotificationTitle(__('filament-actions::create.single.notifications.created.title')); - $this->databaseTransaction(); - $this->action(function (array $arguments, Form $form, HasTable $livewire): void { $model = $this->getModel(); diff --git a/packages/tables/src/Actions/EditAction.php b/packages/tables/src/Actions/EditAction.php index d781ebb9a5..625e97f642 100644 --- a/packages/tables/src/Actions/EditAction.php +++ b/packages/tables/src/Actions/EditAction.php @@ -49,8 +49,6 @@ class EditAction extends Action return $data; }); - $this->databaseTransaction(); - $this->action(function (): void { $this->process(function (array $data, Model $record, Table $table) { $relationship = $table->getRelationship();