<< Home

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.

💡 Networking code benefits from defensive design. Always validate inputs, set explicit timeouts, and handle transient failures gracefully rather than assuming ideal conditions.

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";
}
⚠️ Always set both connection and response timeouts to prevent hanging processes when remote servers are slow or unresponsive.

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());
}
💡 Always define a stable schema for both requests and responses so clients know what fields to expect even as you evolve the API.

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.

⚠️ Never trust webhook data blindly. Validate message integrity, check timestamps to avoid replay attacks, and limit payload sizes.

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;
💡 Close streams explicitly and set timeouts to avoid resource leaks. In daemons, monitor descriptors and memory usage regularly.

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.

⚠️ Fibers do not make blocking calls faster. You still need non-blocking I/O or event-driven libraries underneath to achieve true concurrency.

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.

💡 Keep job payloads small and self-contained. Store large data in object storage and include only references in the queue message.