Skip to content

Making HTTP requests ​

Yii doesn't include its own HTTP client implementation. Use a client that implements PSR-18 and depend on PSR interfaces in your application code.

This recipe uses Guzzle as the HTTP client. Guzzle provides a PSR-18 client and uses PSR-7 messages.

Installation ​

Install Guzzle:

shell
composer require guzzlehttp/guzzle

The package installs the PSR HTTP interfaces and guzzlehttp/psr7, which provides PSR-7 messages and PSR-17 factories.

Configure the container ​

Create config/common/di/http-client.php:

php
<?php

declare(strict_types=1);

use GuzzleHttp\Client;
use GuzzleHttp\Psr7\HttpFactory;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestFactoryInterface;
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\StreamFactoryInterface;
use Psr\Http\Message\UriFactoryInterface;
use Yiisoft\Definitions\Reference;

return [
    ClientInterface::class => [
        'class' => Client::class,
        '__construct()' => [
            'config' => [
                'timeout' => 10.0,
                'connect_timeout' => 3.0,
            ],
        ],
    ],

    HttpFactory::class => HttpFactory::class,
    RequestFactoryInterface::class => Reference::to(HttpFactory::class),
    ResponseFactoryInterface::class => Reference::to(HttpFactory::class),
    StreamFactoryInterface::class => Reference::to(HttpFactory::class),
    UriFactoryInterface::class => Reference::to(HttpFactory::class),
];

Use ClientInterface for sending requests and PSR-17 factories for creating requests, streams, and URIs.

Send a GET request ​

Inject Psr\Http\Client\ClientInterface and Psr\Http\Message\RequestFactoryInterface into the service that talks to the external API:

php
<?php

declare(strict_types=1);

namespace App\GitHub;

use Psr\Http\Client\ClientExceptionInterface;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestFactoryInterface;
use RuntimeException;

final readonly class ReleaseClient
{
    public function __construct(
        private ClientInterface $httpClient,
        private RequestFactoryInterface $requestFactory,
    ) {
    }

    /**
     * @return array<string, mixed>
     */
    public function latest(string $owner, string $repository): array
    {
        $request = $this->requestFactory
            ->createRequest(
                'GET',
                sprintf(
                    'https://api.github.com/repos/%s/%s/releases/latest',
                    rawurlencode($owner),
                    rawurlencode($repository),
                ),
            )
            ->withHeader('Accept', 'application/vnd.github+json');

        try {
            $response = $this->httpClient->sendRequest($request);
        } catch (ClientExceptionInterface $e) {
            throw new RuntimeException('GitHub request failed.', previous: $e);
        }

        if ($response->getStatusCode() !== 200) {
            throw new RuntimeException(sprintf(
                'GitHub returned HTTP %d.',
                $response->getStatusCode(),
            ));
        }

        return json_decode((string) $response->getBody(), true, 512, JSON_THROW_ON_ERROR);
    }
}

PSR-18 clients throw ClientExceptionInterface for request sending errors. HTTP status codes such as 404 and 500 are valid responses, so check the status code in your service.

Send JSON ​

Use StreamFactoryInterface when you need a request body:

php
<?php

declare(strict_types=1);

namespace App\Webhook;

use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestFactoryInterface;
use Psr\Http\Message\StreamFactoryInterface;

final readonly class WebhookClient
{
    public function __construct(
        private ClientInterface $httpClient,
        private RequestFactoryInterface $requestFactory,
        private StreamFactoryInterface $streamFactory,
    ) {
    }

    /**
     * @param array<string, mixed> $payload
     */
    public function send(array $payload): void
    {
        $body = $this->streamFactory->createStream(json_encode($payload, JSON_THROW_ON_ERROR));

        $request = $this->requestFactory
            ->createRequest('POST', 'https://api.example.com/webhooks')
            ->withHeader('Content-Type', 'application/json')
            ->withHeader('Accept', 'application/json')
            ->withBody($body);

        $this->httpClient->sendRequest($request);
    }
}

Testing ​

For unit tests, pass a client configured with Guzzle's MockHandler:

php
<?php

declare(strict_types=1);

use App\GitHub\ReleaseClient;
use GuzzleHttp\Client;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Psr7\HttpFactory;
use GuzzleHttp\Psr7\Response;
use PHPUnit\Framework\TestCase;

final class ReleaseClientTest extends TestCase
{
    public function testLatestReturnsDecodedRelease(): void
    {
        $handler = new MockHandler([
            new Response(200, [], json_encode(['tag_name' => '1.0.0'], JSON_THROW_ON_ERROR)),
        ]);

        $client = new Client(['handler' => HandlerStack::create($handler)]);
        $factory = new HttpFactory();

        $releaseClient = new ReleaseClient($client, $factory);

        $release = $releaseClient->latest('yiisoft', 'docs');

        $this->assertSame('1.0.0', $release['tag_name']);
    }
}

When to use Guzzle directly ​

Depend on PSR interfaces for regular application services. Use GuzzleHttp\Client directly in infrastructure code that needs Guzzle-only features such as asynchronous requests, handler stacks, or middleware.