# JSON APIs and HTTP Clients — PHP

Source: https://www.skillbyai.com/en/php/d-json-http

> Build JSON responses and call external APIs safely.

## Speaking JSON in both directions

Most modern PHP applications serve or consume **JSON**. Encode with `json_encode($data, JSON_THROW_ON_ERROR)`, adding flags such as `JSON_UNESCAPED_UNICODE` (keep Hindi text readable) and `JSON_UNESCAPED_SLASHES` where appropriate; decode with `json_decode($json, true, 512, JSON_THROW_ON_ERROR)` to get associative arrays, or map into typed objects with libraries such as Symfony Serializer or Valinor. PHP 8.3 added `json_validate()` to check syntax without decoding. Set `Content-Type: application/json` and correct **status codes** on responses. Implement `JsonSerializable` to control how objects are encoded. For **outgoing HTTP calls**, use a robust client: **Guzzle** or **Symfony HttpClient** (both widely used, and PSR-18 compatible clients can be swapped behind `Psr\Http\Client\ClientInterface`). Always set **timeouts** (connect and total), handle non-2xx responses explicitly, retry only idempotent requests with backoff, and never log full responses that may contain personal data or secrets.

## A JSON endpoint and a Guzzle call with timeouts

Encoding objects, decoding safely and calling an external API.

```php
<?php
declare(strict_types=1);

use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;

final class OrderView implements JsonSerializable
{
    public function __construct(private string $id, private int $totalPaise, private OrderStatus $status) {}

    public function jsonSerialize(): array
    {
        return ['id' => $this->id, 'total' => number_format($this->totalPaise / 100, 2, '.', ''), 'status' => $this->status->value];
    }
}

header('Content-Type: application/json; charset=utf-8');
echo json_encode(['data' => $orderViews], JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE);

$http = new Client(['base_uri' => 'https://rates.example.com', 'connect_timeout' => 2, 'timeout' => 5]);
try {
    $response = $http->get('/v1/rates', ['query' => ['base' => 'USD']]);
    $rates = json_decode((string) $response->getBody(), true, flags: JSON_THROW_ON_ERROR);
    $inr = $rates['rates']['INR'] ?? throw new UnexpectedValueException('INR rate missing');
} catch (GuzzleException | JsonException $e) {
    $logger->warning('Rate lookup failed', ['error' => $e->getMessage()]);
    $inr = $cache->get('last_inr_rate');      // fall back to the last known value
}
```

## Translating for a foreign client

json_encode is the translator who turns your notes into a language every system understands; the timeout is your rule that if the other side does not answer within five seconds, you move on with yesterday's figures.

**Quiz:** Which json_decode option makes invalid JSON throw an exception instead of returning null?

- [ ] JSON_PRETTY_PRINT
- [x] JSON_THROW_ON_ERROR
- [ ] JSON_UNESCAPED_UNICODE
- [ ] JSON_FORCE_OBJECT

*Answer:* JSON_THROW_ON_ERROR. JSON_THROW_ON_ERROR raises JsonException so failures cannot pass silently.
