---
title: "DI and actions — Forms for Laravel | Roundly"
description: "Prefer explicit dependencies? Inject FormsManager for the same API, or call the single-purpose actions behind each facade method."
url: https://roundly-consulting.com/open-source/docs/forms-for-laravel/without-facade
language: en
---

[All packages](https://roundly-consulting.com/open-source.md)

[Forms for Laravel](https://roundly-consulting.com/open-source/docs/forms-for-laravel.md)

# 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

```php
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

```php
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:

```php
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 crypto](https://roundly-consulting.com/support-us.md)

By donating, you agree to our [donation terms](https://roundly-consulting.com/donation-terms.md).

[Support our open source work (opens in a new tab)](https://donate.stripe.com/dRmeVe8FX5PF1Qd9pXcEw00) [Join us on Patreon (opens in a new tab)](https://www.patreon.com/cw/roundly)

## 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.

[Get a quote in 48 hours](https://roundly-consulting.com/contact.md) [Browse all packages](https://roundly-consulting.com/open-source/docs/forms-for-laravel.md)
