Chapter 24: Interoperability and Networking
Modern PHP applications often need to connect with other services, whether by HTTP, sockets, or message queues. This chapter explores how to build robust client connections, handle structured data like JSON, listen for inbound callbacks, and manage concurrent or queued work safely and efficiently.
HTTP Clients with curl and Guzzle
PHP provides the curl extension for low-level HTTP operations. For higher-level usage, libraries such as Guzzle build on top of curl and provide clean interfaces for requests, responses, middleware, and retries.
Making simple requests with curl
Using the built-in curl functions, you can send requests and read responses while configuring headers, methods, and timeouts.
<?php
$url = 'https://api.example.com/data';
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
CURLOPT_HTTPHEADER => ['Accept: application/json'],
]);
$response = curl_exec($ch);
if (curl_errno($ch)) {
fwrite(STDERR, 'Request failed: ' . curl_error($ch) . "\n");
exit(1);
}
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
echo "Status: $code\n";
echo "Body: $response\n";
Using Guzzle for structured HTTP clients
Guzzle simplifies requests, provides promise-based async methods, and integrates with PSR-7 and PSR-18 standards.
<?php
require __DIR__ . '/vendor/autoload.php';
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
$client = new Client(['base_uri' => 'https://api.example.com/']);
try {
$response = $client->get('articles', ['query' => ['limit' => 3]]);
$data = json_decode($response->getBody()->getContents(), true);
print_r($data);
} catch (RequestException $e) {
echo "HTTP error: " . $e->getMessage() . "\n";
}
Consuming and Producing JSON
JSON remains the lingua franca of web APIs. PHP’s json_encode() and json_decode() functions make serialization straightforward, but you must always validate and handle errors carefully.
Parsing incoming JSON safely
<?php
$raw = file_get_contents('php://input') ?: '';
$data = json_decode($raw, true);
if (json_last_error() !== JSON_ERROR_NONE) {
http_response_code(400);
echo json_encode(['error' => 'Invalid JSON']);
exit;
}
echo "Received keys: " . implode(', ', array_keys($data)) . "\n";
Encoding JSON responses clearly
Use JSON_UNESCAPED_UNICODE and JSON_UNESCAPED_SLASHES for readability, and JSON_THROW_ON_ERROR to detect encoding failures via exceptions.
<?php
try {
$payload = ['status' => 'ok', 'data' => ['id' => 1, 'title' => 'Example']];
echo json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
error_log('JSON encode failed: ' . $e->getMessage());
}
Webhooks and Callbacks
Webhooks let other systems notify your PHP application via HTTP. Your endpoint should verify the sender, validate input, and respond quickly to avoid timeouts on the sender side.
Handling webhook payloads predictably
<?php
// webhook.php
$signature = $_SERVER['HTTP_X_SIGNATURE'] ?? '';
$payload = file_get_contents('php://input') ?: '';
if (!hash_equals(hash_hmac('sha256', $payload, 'secret-key'), $signature)) {
http_response_code(403);
echo "Invalid signature";
exit;
}
$data = json_decode($payload, true);
file_put_contents('webhook.log', date('c') . ' ' . json_encode($data) . "\n", FILE_APPEND);
http_response_code(202);
echo "Accepted";
Designing webhook reliability and retries
Many webhook providers retry failed requests. Keep your handler idempotent so repeated deliveries do not cause duplicates. Respond with 2xx codes only when you have safely processed or queued the event.
Sockets and Streams
Sockets let PHP scripts talk directly to TCP or UDP endpoints for custom protocols or lower-level services. Stream wrappers abstract many protocols into file-like interfaces so you can read or write with familiar functions.
Creating and using raw TCP sockets
<?php
$socket = stream_socket_client('tcp://example.com:80', $errno, $errstr, 5);
if (!$socket) {
echo "Connect failed: $errstr\n";
exit(1);
}
fwrite($socket, "GET / HTTP/1.0\r\nHost: example.com\r\n\r\n");
while (!feof($socket)) {
echo fgets($socket);
}
fclose($socket);
Using stream contexts for secure connections
You can control TLS verification and headers via stream contexts for both fopen() and file_get_contents().
<?php
$context = stream_context_create([
'ssl' => ['verify_peer' => true, 'verify_peer_name' => true],
'http' => ['header' => "User-Agent: PHPExample\r\n"]
]);
$data = file_get_contents('https://example.com/api', false, $context);
echo $data;
Asynchronous Patterns with Fibers
Fibers, introduced in PHP 8.1, provide cooperative multitasking within a single thread. They allow you to suspend and resume blocks of code without complex callbacks or generators, enabling lightweight asynchronous execution.
Creating and resuming fibers
<?php
$fiber = new Fiber(function ($value) {
echo "Fiber started with $value\n";
$pause = Fiber::suspend('paused');
echo "Resumed with $pause\n";
return 'done';
});
$result = $fiber->start('init');
echo "Suspend returned: " . $result . "\n";
echo "Return value: " . $fiber->resume('resume-value') . "\n";
Combining fibers with event loops
Libraries like amphp and reactphp use fibers internally to implement non-blocking I/O. You can schedule multiple fibers to handle concurrent network tasks while the event loop waits on I/O efficiently.
Message Queues and Jobs
Message queues decouple producers and consumers, letting you build scalable systems where each component processes tasks at its own pace. PHP can interact with brokers like Redis, RabbitMQ, or AWS SQS through their APIs or client libraries.
Publishing and consuming with Redis lists
<?php
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
// Producer
$redis->rPush('jobs', json_encode(['task' => 'send_email', 'to' => 'user@example.com']));
// Consumer
while (true) {
$job = $redis->blPop(['jobs'], 5);
if ($job) {
[$queue, $payload] = $job;
$data = json_decode($payload, true);
echo "Processing job: " . $data['task'] . "\n";
}
}
Integrating with frameworks and external brokers
Laravel’s queue system and Symfony Messenger both abstract different backends behind common interfaces. You can push jobs, configure retries, and define failure handling consistently regardless of the broker implementation.