PHPackages                             vanilla/garden-http - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [HTTP &amp; Networking](/categories/http)
4. /
5. vanilla/garden-http

ActiveLibrary[HTTP &amp; Networking](/categories/http)

vanilla/garden-http
===================

An unbloated HTTP client library for building RESTful API clients.

v3.1.0(1y ago)11396.8k↑46.9%16MITPHPPHP &gt;=8.0CI failing

Since Feb 5Pushed 1y ago15 watchersCompare

[ Source](https://github.com/vanilla/garden-http)[ Packagist](https://packagist.org/packages/vanilla/garden-http)[ RSS](/packages/vanilla-garden-http/feed)WikiDiscussions master Synced 2d ago

READMEChangelog (10)Dependencies (6)Versions (46)Used By (6)

Garden HTTP
===========

[](#garden-http)

[![CI Tests](https://github.com/vanilla/garden-http/actions/workflows/ci.yml/badge.svg)](https://github.com/vanilla/garden-http/actions/workflows/ci.yml)[![Packagist Version](https://camo.githubusercontent.com/c61e47cfb4ebf1e3e0e992ef21d268d94dd4e4f062a6cc02f81dd57283590a3e/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f76616e696c6c612f67617264656e2d687474702e7376673f7374796c653d666c6174)](https://packagist.org/packages/vanilla/garden-http)[![CLA](https://camo.githubusercontent.com/2d6e75447c5070792dbc26c7e0d8fa562d75db9c803d448a53b7b905cd605eb5/68747470733a2f2f636c612d617373697374616e742e696f2f726561646d652f62616467652f76616e696c6c612f67617264656e2d68747470)](https://cla-assistant.io/vanilla/garden-http)

Garden HTTP is an unbloated HTTP client library for building RESTful API clients. It's meant to allow you to access people's APIs without having to copy/paste a bunch of cURL setup and without having to double the size of your codebase. You can use this library as is for quick API clients or extend the `HttpClient` class to make structured API clients that you use regularly.

Installation
------------

[](#installation)

*Garden HTTP requires PHP 7.4 or higher and libcurl*

Garden HTTP is [PSR-4](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4-autoloader.md) compliant and can be installed using [composer](//getcomposer.org). Just add `vanilla/garden-http` to your composer.json.

Garden request and response objects are [PSR-7](https://www.php-fig.org/psr/psr-7/) compliant as well.

Basic Example
-------------

[](#basic-example)

Almost all uses of Garden HTTP involve first creating an `HttpClient` object and then making requests from it. You can see below a default header is also set to pass a standard header to every request made with the client.

```
use Garden\Http\HttpClient;

$api = new HttpClient('http://httpbin.org');
$api->setDefaultHeader('Content-Type', 'application/json');

// Get some data from the API.
$response = $api->get('/get'); // requests off of base url
if ($response->isSuccessful()) {
    $data = $response->getBody(); // returns array of json decoded data
}

$response = $api->post('https://httpbin.org/post', ['foo' => 'bar']);
if ($response->isResponseClass('2xx')) {
    // Access the response like an array.
    $posted = $response['json']; // should be ['foo' => 'bar']
}
```

Throwing Exceptions
-------------------

[](#throwing-exceptions)

You can tell the HTTP client to throw an exception on unsuccessful requests.

```
use Garden\Http\HttpClient;

$api = new HttpClient('https://httpbin.org');
$api->setThrowExceptions(true);

try {
    $api->get('/status/404');
} catch (\Exception $ex) {
    $code = $ex->getCode(); // should be 404
    throw $ex;
}

// If you don't want a specific request to throw.
$response = $api->get("/status/500", [], [], ["throw" => false]);
// But you could throw it yourself.
if (!$response->isSuccessful()) {
    throw $response->asException();
}
```

Exceptions will be thrown with a message indicating the failing response and structured data as well.

```
try {
    $response = new HttpResponse(501, ["content-type" => "application/json"], '{"message":"Some error occured."}');
    throw $response->asException();
    // Make an exception
} catch (\Garden\Http\HttpResponseException $ex) {
    // Request POST /some/path failed with a response code of 501 and a custom message of "Some error occured."
    $ex->getMessage();

    // [
    //      "request" => [
    //          'url' => '/some/path',
    //          'method' => 'POST',
    //      ],
    //      "response" => [
    //          'statusCode' => 501,
    //          'content-type' => 'application/json',
    //          'body' => '{"message":"Some error occured."}',
    //      ]
    // ]
    $ex->getContext();

    // It's serializable too.
    json_encode($ex);
}
```

Basic Authentication
--------------------

[](#basic-authentication)

You can specify a username and password for basic authentication using the `auth` option.

```
use Garden\Http\HttpClient;

$api = new HttpClient('https://httpbin.org');
$api->setDefaultOption('auth', ['username', 'password123']);

// This request is made with the default authentication set above.
$r1 = $api->get('/basic-auth/username/password123');

// This request overrides the basic authentication.
$r2 = $api->get('/basic-auth/username/password', [], [], ['auth' => ['username', 'password']]);
```

Extending the HttpClient through subclassing
--------------------------------------------

[](#extending-the-httpclient-through-subclassing)

If you are going to be calling the same API over and over again you might want to extend the `HttpClient` class to make an API client that is more convenient to reuse.

```
use Garden\Http\HttpClient;
use Garden\Http\HttpHandlerInterface

// A custom HTTP client to access the github API.
class GithubClient extends HttpClient {

    // Set default options in your constructor.
    public function __construct(HttpHandlerInterface $handler = null) {
        parent::__construct('https://api.github.com', $handler);
        $this
            ->setDefaultHeader('Content-Type', 'application/json')
            ->setThrowExceptions(true);
    }

    // Use a default header to authorize every request.
    public function setAccessToken($token) {
        $this->setDefaultHeader('Authorization', "Bearer $token");
    }

    // Get the repos for a given user.
    public function getRepos($username = '') {
        if ($username) {
            return $this->get("/users/$username/repos");
        } else {
            return $this->get("/user/repos"); // my repos
        }
    }

    // Create a new repo.
    public function createRepo($name, $description, $private) {
        return $this->post(
            '/user/repos',
            ['name' => $name, 'description' => $description, 'private' => $private]
        );
    }

    // Get a repo.
    public function getRepo($owner, $repo) {
        return $this->get("/repos/$owner/$repo");
    }

    // Edit a repo.
    public function editRepo($owner, $repo, $name, $description = null, $private = null) {
        return $this->patch(
            "/repos/$owner/$repo",
            ['name' => $name, 'description' => $description, 'private' => $private]
        );
    }

    // Different APIs will return different responses on errors.
    // Override this method to handle errors in a way that is appropriate for the API.
    public function handleErrorResponse(HttpResponse $response, $options = []) {
        if ($this->val('throw', $options, $this->throwExceptions)) {
            $body = $response->getBody();
            if (is_array($body)) {
                $message = $this->val('message', $body, $response->getReasonPhrase());
            } else {
                $message = $response->getReasonPhrase();
            }
            throw new \HttpResponseExceptionException($response, $message);
        }
    }
}
```

Extending the HttpClient with middleware
----------------------------------------

[](#extending-the-httpclient-with-middleware)

The `HttpClient` class has an `addMiddleware()` method that lets you add a function that can modify the request and response before and after being sent. Middleware lets you develop a library of reusable utilities that can be used with any client. Middleware is good for things like advanced authentication, caching layers, CORS support, etc.

### Writing middleware

[](#writing-middleware)

Middleware is a callable that accepts two arguments: an `HttpRequest` object, and the next middleware. Each middleware must return an `HttpResponse` object.

```
function (HttpRequest $request, callable $next): HttpResponse {
    // Do something to the request.
    $request->setHeader('X-Foo', '...');

    // Call the next middleware to get the response.
    $response = $next($request);

    // Do something to the response.
    $response->setHeader('Cache-Control', 'public, max-age=31536000');

    return $response;
}
```

You have to call `$next` or else the request won't be processed by the `HttpClient`. Of course, you may want to short circuit processing of the request in the case of a caching layer so in that case you can leave out the call to `$next`.

### Example: Modifying the request with middleware

[](#example-modifying-the-request-with-middleware)

Consider the following class that implements HMAC SHA256 hashing for a hypothetical API that expects more than just a static access token.

```
class HmacMiddleware {
    protected $apiKey;

    protected $secret;

    public function __construct(string $apiKey, string $secret) {
        $this->apiKey = $apiKey;
        $this->secret = $secret;
    }

    public function __invoke(HttpRequest $request, callable $next): HttpResponse {
        $msg = time().$this->apiKey;
        $sig = hash_hmac('sha256', $msg, $this->secret);

        $request->setHeader('Authorization', "$msg.$sig");

        return $next($request);
    }
}
```

This middleware calculates a new authorization header for each request and then adds it to the request. It then calls the `$next` closure to perform the rest of the request.

The HttpHandlerInterface
------------------------

[](#the-httphandlerinterface)

In Garden HTTP, requests are executed with an HTTP handler. The currently included default handler executes requests with cURL. However, you can implement the the `HttpHandlerInterface` however you want and completely change the way requests are handled. The interface includes only one method:

```
public function send(HttpRequest $request): HttpResponse;
```

The method is supposed to transform a request into a response. To use it, just pass an `HttpRequest` object to it.

You can also use your custom handler with the `HttpClient`. Just pass it to the constructor:

```
$api = new HttpClient('https://example.com', new CustomHandler());
```

Inspecting requests and responses
---------------------------------

[](#inspecting-requests-and-responses)

Sometimes when you get a response you want to know what request generated it. The `HttpResponse` class has an `getRequest()` method for this. The `HttpRequest` class has a `getResponse()` method for the inverse.

Exceptions that are thrown from `HttpClient` objects are instances of the `HttpResponseException` class. That class has `getRequest()` and `getResponse()` methods so that you can inspect both the request and the response for the exception. This exception is of particular use since request objects are created inside the client and not by the programmer directly.

Mocking for Tests
-----------------

[](#mocking-for-tests)

An `HttpHandlerInterface` implementation and utilities are provided for mocking requests and responses.

### Setup

[](#setup)

```
use Garden\Http\HttpClient
use Garden\Http\Mocks\MockHttpHandler;

// Manually apply the handler.
$httpClient = new HttpClient();
$mockHandler = new MockHttpHandler();
$httpClient->setHandler($mockHandler);

// Automatically apply a handler to `HttpClient` instances.
// You can call this again later to retrieve the same handler.
$mockHandler = MockHttpHandler::mock();

// Don't forget this in your phpunit `teardown()`
MockHttpHandler::clearMock();;

// Reset the handler instance
$mockHandler->reset();
```

### Mocking Requests

[](#mocking-requests)

```
use Garden\Http\Mocks\MockHttpHandler;
use Garden\Http\Mocks\MockResponse;

// By default this will return 404 for all requests.
$mockHttp = MockHttpHandler::mock();

$mockHttp
    // Explicit request and response
    ->addMockRequest(
        new \Garden\Http\HttpRequest("GET", "https://domain.com/some/url"),
        new \Garden\Http\HttpResponse(200, ["content-type" => "application/json"], '{"json": "here"}'),
    )
    // Shorthand
    ->addMockRequest(
        "GET https://domain.com/some/url",
        MockResponse::json(["json" => "here"])
    )
    // Even shorter-hand
    // Mocking 200 JSON responses to GET requests is very easy.
    ->addMockRequest(
        "https://domain.com/some/url",
        ["json" => "here"]
    )

    // Wildcards
    // Wildcards match with lower priority than explicitly matching requests.

    // Explicit wildcard hostname.
    ->addMockRequest("https://*/some/path", MockResponse::success())
    // Implied wildcard hostname.
    ->addMockRequest("/some/path", MockResponse::success())
    // wildcard in path
    ->addMockRequest("https://some-doain.com/some/*", MockResponse::success())
    // Total wildcard
    ->addMockRequest("*", MockResponse::notFound())
;

// Mock multiple requests at once
$mockHttp->mockMulti([
    "GET /some/path" => MockResponse::success()
    "POST /other/path" => MockResponse::json([])
]);
```

### Response Sequences

[](#response-sequences)

Anywhere you can use a mocked `HttpResponse` you can also use a `MockHttpSequence`.

Each item pushed into the sequence will return exactly once. Once that response has been returned it will not be returned again.

If the whole sequence is exhausted it will return 404 responses.

```
use Garden\Http\Mocks\MockHttpHandler;
use Garden\Http\Mocks\MockResponse;

$mockHttp = MockHttpHandler::mock();

$mockHttp->mockMulti([
    "GET /some/path" => MockResponse::sequence()
        ->push(new \Garden\Http\HttpResponse(500, [], ""))
        ->push(MockResponse::success())
        ->push(MockResponse::json([])
        ->push([]) // Implied json
    ,
]);
```

### Response Functions

[](#response-functions)

You can make a mock dynamic by providing a callable.

```
use Garden\Http\Mocks\MockHttpHandler;
use Garden\Http\Mocks\MockResponse;
use \Garden\Http\HttpRequest;
use \Garden\Http\HttpResponse;

$mockHttp = MockHttpHandler::mock();
$mockHttp->addMockRequest("*", function (\Garden\Http\HttpRequest $request): HttpResponse {
    return MockResponse::json([
        "requestedUrl" => $request->getUrl(),
    ]);
})
```

### Assertions about requests

[](#assertions-about-requests)

Some utilities are provided to make assertions against requests that were made. This can be particularly useful with a wildcard response.

```
use Garden\Http\Mocks\MockHttpHandler;
use Garden\Http\Mocks\MockResponse;
use Garden\Http\HttpRequest;

$mockHttp = MockHttpHandler::mock();

$mockHttp->addMockRequest("*", MockResponse::success());

// Ensure no requests were made.
$mockHttp->assertNothingSent();

// Check that a request was made
$foundRequest = $mockHttp->assertSent(fn (HttpRequest $request) => $request->getUri()->getPath() === "/some/path");

// Check that a request was not made.
$foundRequest = $mockHttp->assertNotSent(fn (HttpRequest $request) => $request->getUri()->getPath() === "/some/path");

// Clear the history (and mocked requests)
$mockHttp->reset();
```

###  Health Score

54

—

FairBetter than 96% of packages

Maintenance48

Moderate activity, may be stable

Popularity43

Moderate usage in the ecosystem

Community30

Small or concentrated contributor base

Maturity81

Battle-tested with a long release history

 Bus Factor1

Top contributor holds 76.5% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~140 days

Recently: every ~159 days

Total

28

Last Release

384d ago

Major Versions

v1.1.3 → v2.0.02018-02-15

v1.1.4 → v2.0.22019-06-11

v2.9.0 → v3.0.02024-11-21

PHP version history (6 changes)v1.0.0PHP &gt;=5.4.0

v2.0.0PHP &gt;=7.0.0

v2.2PHP &gt;=7.1

v2.5PHP &gt;=7.2

v2.5.2PHP &gt;=7.4

v3.0.0PHP &gt;=8.0

### Community

Maintainers

![](https://www.gravatar.com/avatar/da29e060f227028e8ee38bb5873ba2188fb5236b20b3dffec766c768019d8bd8?d=identicon)[tburry](/maintainers/tburry)

![](https://www.gravatar.com/avatar/ea17fedfd653c4692cf93a4faada50927cef9537dafed0dd4b1517e54fd71728?d=identicon)[charrondev](/maintainers/charrondev)

![](https://avatars.githubusercontent.com/u/248212?v=4)[Tim Gunter](/maintainers/kaecyra)[@kaecyra](https://github.com/kaecyra)

---

Top Contributors

[![tburry](https://avatars.githubusercontent.com/u/97432?v=4)](https://github.com/tburry "tburry (140 commits)")[![charrondev](https://avatars.githubusercontent.com/u/1770056?v=4)](https://github.com/charrondev "charrondev (16 commits)")[![vanilla-dbarbier](https://avatars.githubusercontent.com/u/61435952?v=4)](https://github.com/vanilla-dbarbier "vanilla-dbarbier (7 commits)")[![maximepoulin-hl](https://avatars.githubusercontent.com/u/70532110?v=4)](https://github.com/maximepoulin-hl "maximepoulin-hl (7 commits)")[![linc](https://avatars.githubusercontent.com/u/117672?v=4)](https://github.com/linc "linc (4 commits)")[![DaazKu](https://avatars.githubusercontent.com/u/2412909?v=4)](https://github.com/DaazKu "DaazKu (3 commits)")[![acharron-hl](https://avatars.githubusercontent.com/u/146114816?v=4)](https://github.com/acharron-hl "acharron-hl (2 commits)")[![kaecyra](https://avatars.githubusercontent.com/u/248212?v=4)](https://github.com/kaecyra "kaecyra (2 commits)")[![OlivierLamyCanuel](https://avatars.githubusercontent.com/u/39598345?v=4)](https://github.com/OlivierLamyCanuel "OlivierLamyCanuel (1 commits)")[![scrutinizer-auto-fixer](https://avatars.githubusercontent.com/u/6253494?v=4)](https://github.com/scrutinizer-auto-fixer "scrutinizer-auto-fixer (1 commits)")

---

Tags

production

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/vanilla-garden-http/health.svg)

```
[![Health](https://phpackages.com/badges/vanilla-garden-http/health.svg)](https://phpackages.com/packages/vanilla-garden-http)
```

###  Alternatives

[guzzlehttp/psr7

PSR-7 message implementation that also provides common utility methods

8.0k1.1B4.0k](/packages/guzzlehttp-psr7)[aws/aws-sdk-php

AWS SDK for PHP - Use Amazon Web Services in your PHP project

6.3k543.5M2.6k](/packages/aws-aws-sdk-php)[neuron-core/neuron-ai

The PHP Agentic Framework.

2.0k656.1k38](/packages/neuron-core-neuron-ai)[tempest/framework

The PHP framework that gets out of your way.

2.2k34.4k15](/packages/tempest-framework)[phlak/directory-lister

PHP directory lister

2.5k1.4k](/packages/phlak-directory-lister)[telnyx/telnyx-php

Official Telnyx PHP SDK — APIs for Voice, SMS, MMS, WhatsApp, Fax, SIP Trunking, Wireless IoT, Call Control, and more. Build global communications on Telnyx's private carrier-grade network.

35789.4k2](/packages/telnyx-telnyx-php)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
