---
title: "DI and actions — Two-Factor for Laravel | Roundly"
description: "Inject the TwoFactorService contract instead of calling the facade, or run a single action — the same code path, and the fake still applies."
url: https://roundly-consulting.com/open-source/docs/two-factor-for-laravel/without-facade
language: en
---

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

[Two-Factor for Laravel](https://roundly-consulting.com/open-source/docs/two-factor-for-laravel.md)

# DI and actions

Three entry points run exactly the same code, so pick whichever fits the call site:

- The TwoFactor facade — the shortest form and the recommended default.
- The manager, injected through the constructor — the same API with an explicit dependency and no static calls. The facade root is the RoundlyConsulting\\TwoFactor\\Contracts\\TwoFactorService contract, implemented by RoundlyConsulting\\TwoFactor\\TwoFactorManager.
- Actions — single-purpose classes with one execute() method, for composing into your own actions, jobs and commands.

## Injecting the service

Type-hint the contract, not TwoFactorManager: the manager is deliberately not aliased in the container, because the fake implements the contract. TwoFactor::fake() swaps this binding too, so injected code sees the fake:

```php
use RoundlyConsulting\TwoFactor\Contracts\TwoFactorService;

final readonly class ChallengeController
{
    public function __construct(private TwoFactorService $twoFactor) {}

    public function __invoke(Request $request): Response
    {
        $result = $this->twoFactor->for($request->user())->attempt($request->string('code')->toString());
        // ...
    }
}
```

## Running an action

Each write on the handle is one action in RoundlyConsulting\\TwoFactor\\Actions — resolve it from the container and call execute():

```php
use RoundlyConsulting\TwoFactor\Actions\DisableTwoFactor;
use RoundlyConsulting\TwoFactor\Actions\StartEnrolment;

$setup = app(StartEnrolment::class)->execute($user, issuer: 'Acme');

app(DisableTwoFactor::class)->execute($user); // clears all 2FA state
```

## Facade method → action

| Handle method | Action | Behaviour |
| --- | --- | --- |
| `for($user)->start($label, $issuer)` | `StartEnrolment::execute($user, $label, $issuer)` | Fresh secret and recovery codes, confirmed\_at cleared, TwoFactorEnrolmentStarted fired; returns TwoFactorSetup. Throws TwoFactorAlreadyEnabledException when 2FA is already on. |
| `for($user)->confirm($code)` | `ConfirmEnrolment::execute($user, $code)` | Stamps confirmed\_at, claims the confirming timestep, fires TwoFactorConfirmed. Throws TwoFactorNotPendingException or InvalidTwoFactorCodeException. |
| `for($user)->attempt($code)` | `AttemptTwoFactorCode::execute($user, $code)` | The login challenge — limiter, TOTP with replay guard, then a recovery code; returns VerificationResult. |
| `for($user)->recoveryCodes()->regenerate()` | `RegenerateRecoveryCodes::execute($user)` | Replaces the code set, fires RecoveryCodesRegenerated and returns the new plaintext codes. |
| `for($user)->disable()` | `DisableTwoFactor::execute($user)` | Sets every two-factor column to null and fires TwoFactorDisabled. |

status() and recoveryCodes()->remaining() are plain reads with no action behind them. The actions depend on TwoFactorService for the TOTP primitives, so under TwoFactor::fake() they run on the fake’s canned secret, codes and programmable verify(). The recovery-code storage helper, Support\\RecoveryCodeManager, is @internal — a building block of the actions, not for host use.

## 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/two-factor-for-laravel.md)
