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:
// 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
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
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 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.