Drie heldere lagen: Request beschrijft de call, Transport voert hem uit, Response komt terug. Cache is een decorator (CachingTransport), niet ingebakken in de Request.
Url — fluent en immutable
parse() leest een url; withQuery() / withPath() leveren een nieuwe instance.
$url = Url::parse('https://api.example.com/v1/items?page=1');
$next = $url->withQuery(['page' => 2, 'limit' => 50]);
return '<code>' . htmlspecialchars((string) $next) . '</code>';https://api.example.com/v1/items?page=2&limit=50Request — with-pattern
Elke setter levert een clone. De originele $base blijft hergebruikbaar.
$base = Request::get('https://api.example.com/users')
->withHeader('Accept', 'application/json')
->withTimeout(5.0);
$page2 = $base->withQuery(['page' => 2])->withCacheTtl(300);
return [
'method' => $page2->method->value,
'url' => (string) $page2->url,
'ttl' => $page2->cacheTtl,
'headers' => $page2->headers->toLines(),
];Array
(
[method] => GET
[url] => https://api.example.com/users?page=2
[ttl] => 300
[headers] => Array
(
[0] => Accept: application/json
)
)
Client met FakeTransport
Voor testen / pages zonder netwerk: implementeer Transport zelf.
$fake = new class implements TransportInterface {
public function send(Request $r): Response {
return new Response(200, new Headers(['Content-Type' => 'application/json']),
'{"users":[{"id":1,"name":"Alice"}]}');
}
};
$client = new Client($fake);
$resp = $client->get('https://api.example.com/users');
return $resp->json(associative: true);Array
(
[users] => Array
(
[0] => Array
(
[id] => 1
[name] => Alice
)
)
)
CachingTransport — TTL via Request
Tweede call met dezelfde TTL gaat niet meer naar de inner transport. ($fake en $cacheDir komen uit de page-setup.)
$caching = new CachingTransport($fake, $cacheDir);
$req = Request::get('https://api.example.com/users')->withCacheTtl(60);
$before = count($fake->received);
$caching->send($req); // → fake.send()
$caching->send($req); // → uit cache, geen tweede fake-call
$after = count($fake->received);
return ['fake_received' => $after - $before, 'verwacht' => 1];Array
(
[fake_received] => 1
[verwacht] => 1
)
cacheKey: stabiel, exclusief auth-headers
Authorization en Cookie-headers tellen niet mee — dezelfde resource met andere user levert dezelfde cache-key.
$a = Request::get('https://example.com/x')->withHeader('Authorization', 'Bearer USER-A');
$b = Request::get('https://example.com/x')->withHeader('Authorization', 'Bearer USER-B');
return ['gelijk?' => $a->cacheKey() === $b->cacheKey()];Array
(
[gelijk?] => 1
)
Response — raw blijft, json() is opt-in
In tegenstelling tot library/Http: de raw body blijft beschikbaar, json() throwt nooit (returnt null bij parse-fout).
$r = new Response(200, new Headers(['Content-Type' => 'application/json']), '{"ok":true}');
return [
'status' => $r->status,
'isOk' => $r->isOk(),
'json.ok' => $r->json()->ok,
'raw_body' => $r->body,
];Array
(
[status] => 200
[isOk] => 1
[json.ok] => 1
[raw_body] => {"ok":true}
)
Voeg ?live=1 toe aan de URL om een echte HTTP-call te zien (httpbin.org).