DI and actions
The facade is the recommended default, not the only way in. There are three equivalent entry points:
- The Forms facade — shortest, and what the rest of these docs use.
- The manager, RoundlyConsulting\Forms\FormsManager — the facade root. Inject it through the constructor for the same API with an explicit dependency and no static calls.
- Actions — single-purpose classes in RoundlyConsulting\Forms\Actions with an execute() method, for composing into your own actions, jobs and commands.
Injecting the manager
use Illuminate\Http\Request;
use RoundlyConsulting\Forms\FormsManager;
final class ContactFormController
{
public function __construct(private FormsManager $forms) {}
public function store(Request $request)
{
$form = $this->forms->find('contact');
$this->forms->validate($form, $request);
$result = $this->forms->submit($form, $request, $request->user());
$this->forms->submission($result->uuid)->get(); // builders and handles work too
return back()->with('reference', $result->uuid);
}
}FormsManager is an autowired singleton and deliberately not final: Forms::fake() swaps in FormsFake, a FormsManager subtype, so injected managers are faked too.
Calling an action
use RoundlyConsulting\Forms\Actions\FindFormAction;
use RoundlyConsulting\Forms\Actions\StoreSubmissionAction;
// Resolve from the container (or inject into your own action, job or command).
$form = app(FindFormAction::class)->execute('contact');
$result = app(StoreSubmissionAction::class)->execute($form, $request, $user);The manager resolves each action from the container per call, so binding your own implementation of an action changes the facade’s behaviour as well. The fake records at the manager — an action you call directly is not recorded.
Facade method → action
| Facade method | Action | execute() |
|---|---|---|
find() | FindFormAction | (string $key): Form |
create(), define()->create() | CreateFormAction | (FormDefinitionData $data): Form |
update()->save() | UpdateFormAction | (FormDefinitionData $data): Form |
sync(), forms:sync | SyncFormsAction | (?array $definitions = null): list<string> |
validate() | ValidateSubmissionAction | (Form $form, Request $request): array |
submit() | StoreSubmissionAction | (Form, Request, ?Model $sender, bool $bypassClosed): SubmissionResult |
draft() | DraftSubmissionAction | (Form, Request, ?Model $sender, ?string $uuid, bool $bypassClosed): SubmissionResult |
finalize(), submission()->finalize() | FinalizeSubmissionAction | (string $uuid, bool $bypassClosed = false): SubmissionResult |
submission()->model(), review($uuid) | FindSubmissionAction | (string $uuid): FormSubmission |
review()->…->open() | ReviewSubmissionAction | (FormSubmission, array $approvers, ApprovalRule $rule, ?int $quorum): ApprovalRequest |
createSubmission() | CreateSubmissionAction | (SubmissionData $data): Submission |
ValidateFieldTypesAction and CreateFormSubmissionAction are @internal building blocks of validation and submitting — not for host use.
Actions that take DTOs
CreateFormAction and UpdateFormAction take a FormDefinitionData (build it with FormDefinitionData::fromArray()); CreateSubmissionAction takes a SubmissionData (SubmissionData::forField()). The raw row action skips the closed-form check that Forms::createSubmission() adds:
use RoundlyConsulting\Forms\Actions\CreateFormAction;
use RoundlyConsulting\Forms\Actions\CreateSubmissionAction;
use RoundlyConsulting\Forms\DataTransferObjects\FormDefinitionData;
use RoundlyConsulting\Forms\DataTransferObjects\SubmissionData;
$form = app(CreateFormAction::class)->execute(FormDefinitionData::fromArray([
'key' => 'newsletter',
'name' => 'Newsletter',
'groups' => [],
]));
// The raw row write — no closed-form check (Forms::createSubmission() adds it).
$row = app(CreateSubmissionAction::class)->execute(
SubmissionData::forField($field, $uuid, ['value' => 'Jane'], $user),
);Show your open-source love
This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.
More ways to support, including cryptoBy donating, you agree to our donation terms.
Want this built into your product?
We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.