---
title: "DI and actions — Safe Fetch for Laravel | Roundly"
description: "Skip the facade: inject SafeFetchManager or call a single-purpose action — every action with its execute() signature and facade method."
url: https://roundly-consulting.com/open-source/docs/safe-fetch-for-laravel/without-facade
language: en
---

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

[Safe Fetch for Laravel](https://roundly-consulting.com/open-source/docs/safe-fetch-for-laravel.md)

# DI and actions

The facade is the recommended default, not the only way in. Every facade method is a method of RoundlyConsulting\\SafeFetch\\SafeFetchManager — the facade’s root, a container singleton — and each resolves one action through the container, so a container override of an action and SafeFetch::fake() both apply. All three styles run the same code:

```php
// Facade / macro
Http::safe()->withToken($token)->post($url, $payload);
$vetted = SafeFetch::vet($url);
$page = SafeFetch::fetch($url, maxBytes: 1_000_000);
$file = SafeFetch::download($url, $path, maxBytes: 50_000_000);
SafeFetch::precheck($url);

// Dependency injection
public function __construct(private SafeFetchManager $safeFetch) {}
$this->safeFetch->guard(Http::withToken($token), $policy)->post($url, $payload);
$this->safeFetch->vet($url);
$this->safeFetch->download($url, $path, maxBytes: 50_000_000);
$this->safeFetch->precheck($url);

// The actions
app(GuardRequestAction::class)->execute(Http::withToken($token), SafeFetch::policy())->post($url, $payload);
app(VetDestinationAction::class)->execute($url, SafeFetch::policy());
app(FetchUrlAction::class)->execute($url, new FetchOptions(maxBytes: 1_000_000), SafeFetch::policy());
app(DownloadUrlAction::class)->execute($url, $path, new FetchOptions(maxBytes: 50_000_000), SafeFetch::policy());
app(PrecheckDestinationAction::class)->execute($url, SafeFetch::policy());
```

## Inject the manager

```php
use Illuminate\Support\Facades\Http;
use RoundlyConsulting\SafeFetch\SafeFetchManager;

final class SendWebhook
{
    public function __construct(private SafeFetchManager $safeFetch) {}

    public function handle(string $url, array $payload): void
    {
        $this->safeFetch->guard(Http::timeout(10))->post($url, $payload);
    }
}
```

The manager is not final on purpose — the fake extends it, so code that injects SafeFetchManager is faked too under SafeFetch::fake().

## Call an action

```php
use RoundlyConsulting\SafeFetch\Actions\FetchUrlAction;
use RoundlyConsulting\SafeFetch\DataTransferObjects\FetchOptions;
use RoundlyConsulting\SafeFetch\Facades\SafeFetch;

$page = app(FetchUrlAction::class)->execute(
    $url,
    new FetchOptions(maxBytes: 1_000_000, timeout: 20, accept: 'text/html'),
    SafeFetch::policy(),
);
```

## Facade method → action

Namespaces are relative to RoundlyConsulting\\SafeFetch\\Actions; every action is host-facing:

| Facade method | Action | execute() |
| --- | --- | --- |
| `vet() · allows()` | `VetDestinationAction` | `(string $url, DestinationPolicy $policy, string $via = 'vet'): VettedDestination` |
| `precheck()` | `PrecheckDestinationAction` | `(string $url, DestinationPolicy $policy): CanonicalUrl` |
| `http() · guard() · Http::safe()` | `GuardRequestAction` | `(PendingRequest $request, DestinationPolicy $policy): PendingRequest` |
| `fetch()` | `FetchUrlAction` | `(string $url, FetchOptions $options, DestinationPolicy $policy): FetchedResponse` |
| `download()` | `DownloadUrlAction` | `(string $url, string $path, FetchOptions $options, DestinationPolicy $policy): DownloadedFile` |
| `truncated() · policy() · networks() · urls()` | — | No action — stateless computation on the manager. |

- VetDestinationAction dispatches DestinationRefused on every refusal, with $via set to vet, allows, request, fetch or download; PrecheckDestinationAction dispatches it with via precheck.
- fetch() and download() build their request from the container’s HTTP client factory, so Http::fake() answers and records it, and guard it like Http::safe().
- The fake records calls made through the manager. Calling an action directly is not recorded as a vet, fetch or download — but every hop of a guarded request is, because the guard vets each hop through the manager.

## 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/safe-fetch-for-laravel.md)
