Three packages in the same application, each with its own curl wrapper, each with its own idea of what a timeout means.
// the interface is one method
interface ClientInterface
{
public function sendRequest(RequestInterface $request): ResponseInterface;
}
// which means a package takes the client rather than making one
public function __construct(
private ClientInterface $http,
private RequestFactoryInterface $requests,
) {}
// and the application decides on retries, timeouts, logging
// and the middleware stack, once.
The value is not the abstraction, it is that configuration moves to the place that knows about the environment. A package that constructs its own client cannot be given a proxy, a retry policy or a request log without an option for each, and it will have an option for each eventually. The factory interfaces from PSR-17 are the part that makes this practical — without them the package still has to name a concrete request implementation.