http clients and apis
Stream-Based HTTP Requests
PHP can make basic HTTP requests with streams, usually through file_get_contents() and stream_context_create(). This is useful for small scripts and for understanding the raw pieces of an HTTP request.
Streams are not the best tool for every integration. Production API clients often need stronger timeout handling, retries, logging, authentication helpers, testing support, and clearer errors than raw streams provide.
Create a stream context
The context tells PHP how to make the request.
<?php
declare(strict_types=1);
$context = stream_context_create([
'http' => [
'method' => 'GET',
'header' => "Accept: application/json\r\n",
'timeout' => 3,
],
]);
echo 'Stream context ready for GET request' . PHP_EOL;
// Prints:
// Stream context ready for GET request
In a real request, you pass the context to file_get_contents($url, false, $context).
Build headers carefully
The stream wrapper expects HTTP headers as a string with line breaks.
<?php
declare(strict_types=1);
function headerLines(array $headers): string
{
$lines = [];
foreach ($headers as $name => $value) {
$lines[] = $name . ': ' . $value;
}
return implode("\r\n", $lines) . "\r\n";
}
echo headerLines([
'Accept' => 'application/json',
'User-Agent' => 'ExampleClient/1.0',
]);
// Prints:
// Accept: application/json
// User-Agent: ExampleClient/1.0
Do not put untrusted user input into header names or raw header values.
Send a JSON body
For a POST request, encode the body and set the Content-Type header.
<?php
declare(strict_types=1);
$payload = ['name' => 'Keyboard', 'price' => 99.99];
$body = json_encode($payload, JSON_THROW_ON_ERROR);
$contextOptions = [
'http' => [
'method' => 'POST',
'header' => "Content-Type: application/json\r\nAccept: application/json\r\n",
'content' => $body,
'timeout' => 5,
],
];
echo $contextOptions['http']['method'] . ' ' . $contextOptions['http']['content'] . PHP_EOL;
// Prints:
// POST {"name":"Keyboard","price":99.99}
This prepares the request. It does not call the network in the example.
Read response metadata
After a stream request, PHP may populate $http_response_header in the local scope. It contains the response status line and headers.
<?php
declare(strict_types=1);
$responseHeaders = [
'HTTP/1.1 200 OK',
'Content-Type: application/json',
];
$statusLine = $responseHeaders[0] ?? '';
preg_match('#HTTP/\S+\s+(\d{3})#', $statusLine, $matches);
echo 'Status: ' . ($matches[1] ?? 'unknown') . PHP_EOL;
// Prints:
// Status: 200
If you need robust response objects, redirect handling, middleware, or test doubles, a library such as Guzzle or Symfony HttpClient is usually a better choice.
Error handling limitations
file_get_contents() can return false for network or stream errors. HTTP error responses can also be awkward depending on context options, status codes, and wrapper behaviour.
For learning, streams reveal the basic mechanics. For important third-party API integrations, prefer a dedicated HTTP client with explicit exceptions, response objects, timeout options, and better diagnostics.
When streams are reasonable
Streams can be fine for:
- small internal scripts
- quick health checks
- simple one-off integrations
- examples where avoiding dependencies is useful
Use a fuller HTTP client when the code needs authentication, retries, observability, testing, request signing, proxy support, or production reliability.
What to check in a project
Check that stream requests set a timeout. The default can leave requests hanging too long.
Check that errors are handled when file_get_contents() returns false.
Check that JSON bodies use json_encode() and the correct Content-Type.
Check that credentials are not logged or built into URLs.
Check whether a stream request has grown complex enough to move to cURL, Guzzle, Symfony HttpClient, or a framework client.
What you should be able to do
After this lesson, you should be able to create a stream context, set method, headers, timeout, and body, understand where response headers appear, and recognise when raw streams are too limited for production API work.
Practice
Task: Prepare Stream Request Options
Write a small PHP script that prepares stream context options for a JSON API request.
Requirements
- Use
declare(strict_types=1);. - Create a function that accepts method, headers, timeout, and optional payload.
- Build the
httpoptions array expected bystream_context_create(). - Encode the payload as JSON when one is provided.
- Include a GET case with no body.
- Include a POST case with a JSON body.
- Print enough output to show the method, timeout, and body behaviour.
Check Your Work
Run the script and confirm the GET has no content value while the POST does.
Show solution
This solution prepares stream options without making a network call.
<?php
declare(strict_types=1);
/**
* @param array<string, string> $headers
* @param array<string, mixed>|null $payload
* @return array{http: array<string, mixed>}
*/
function streamRequestOptions(string $method, array $headers, int $timeout, ?array $payload = null): array
{
$headerLines = [];
foreach ($headers as $name => $value) {
$headerLines[] = $name . ': ' . $value;
}
$options = [
'http' => [
'method' => strtoupper($method),
'header' => implode("\r\n", $headerLines) . "\r\n",
'timeout' => $timeout,
],
];
if ($payload !== null) {
$options['http']['content'] = json_encode($payload, JSON_THROW_ON_ERROR);
}
return $options;
}
$get = streamRequestOptions('GET', ['Accept' => 'application/json'], 3);
$post = streamRequestOptions('POST', [
'Accept' => 'application/json',
'Content-Type' => 'application/json',
], 5, ['name' => 'Keyboard']);
echo $get['http']['method'] . ' timeout ' . $get['http']['timeout'] . PHP_EOL;
echo $post['http']['method'] . ' body ' . $post['http']['content'] . PHP_EOL;
// Prints:
// GET timeout 3
// POST body {"name":"Keyboard"}
Why This Works
The GET case proves a simple read request can be configured without a body. The POST case proves the JSON payload is encoded and attached as request content.