PHPackages                             radiergummi/wander - 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. [Utility &amp; Helpers](/categories/utility)
4. /
5. radiergummi/wander

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

radiergummi/wander
==================

A PHP client for the modern world

0.1.0(5y ago)38MITPHPPHP &gt;=7.4

Since Sep 24Pushed 5y ago1 watchersCompare

[ Source](https://github.com/Radiergummi/wander)[ Packagist](https://packagist.org/packages/radiergummi/wander)[ RSS](/packages/radiergummi-wander/feed)WikiDiscussions master Synced today

READMEChangelog (1)Dependencies (9)Versions (2)Used By (0)

Wander [![Build Status](https://camo.githubusercontent.com/05c61815b35e54575a63ac571acc365a7aa0978d130d48681dda73a089a00f98/68747470733a2f2f6170692e7472617669732d63692e6f72672f52616469657267756d6d692f77616e6465722e7376673f6272616e63683d6d6173746572)](https://camo.githubusercontent.com/05c61815b35e54575a63ac571acc365a7aa0978d130d48681dda73a089a00f98/68747470733a2f2f6170692e7472617669732d63692e6f72672f52616469657267756d6d692f77616e6465722e7376673f6272616e63683d6d6173746572) [![License](https://camo.githubusercontent.com/a549a7a30bacba7bfceebdc207a8e86c3f2c02995a2527640dca30048fd2b64e/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d417061636865253230322e302d626c75652e737667)](https://opensource.org/licenses/Apache-2.0) [![Packagist Version (including pre-releases)](https://camo.githubusercontent.com/dc5d2981fa92b9f8bb8911235cf9f034692d3b279427840b016fa4f1c7e73ae0/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f72616469657267756d6d692f77616e6465723f696e636c7564655f70726572656c6561736573266c6162656c3d6c6174657374)](https://camo.githubusercontent.com/dc5d2981fa92b9f8bb8911235cf9f034692d3b279427840b016fa4f1c7e73ae0/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f72616469657267756d6d692f77616e6465723f696e636c7564655f70726572656c6561736573266c6162656c3d6c6174657374)
===================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================

[](#wander---)

> A modern, lightweight, fast and type-safe HTTP client for PHP 7.4

> **Note:** I'm still actively working on Wander. Help out if you'd like to, but I'd advise against using it yet.

Introduction
------------

[](#introduction)

Making HTTP requests in PHP can be a pain. Either you go full-low-level mode and use streams or curl, or you'll need to use one of the existing choices with array-based configuration, lots of overhead, or missing PSR compatibility. Wander attempts to provide an alternative: It doesn't try to solve every problem under the sun out of the box, but stay simple and extensible. Wander makes some sensible assumptions, but allows you to extend it if those assumptions turn out to be wrong for your use case.

### Features

[](#features)

- **Simple, discoverable API**Wander exposes *all* request options as chainable methods.
- **Fully standards compliant**Wander relies on PSR-17 factories, PSR-18 client drivers and PSR-7 requests/responses. Use our implementations of choice ([nyholm/psr7](https://github.com/nyholm/psr7)) or bring your own.
- **Pluggable serialization**Request and response bodies are serialized depending on the content type, transparently and automatically. Use a format we don't know yet? Add your own (and submit a PR!).
- **Compatible with other solutions**As drivers are, essentially, PSR-18 clients, you can swap in any other client library and make it work out of the box. This provides for a smooth migration path.
- **Extensive exceptions**Wander throws several exceptions, all of which follow a clear inheritance structure. This makes it exceptionally easy to handle errors as coarsely or fine-grained as necessary.

```
$responseBody = (new Wander())
    ->patch('https://example.com')
    ->withQueryParameter('foo', 'bar')
    ->withBasicAuthorization('User', 'Pass')
    ->withHeaders([
        Header::ACCEPT => MediaType::APPLICATION_JSON,
    ])
    ->withoutHeader(Header::USER_AGENT)
    ->withBody([ 'test' => true ])
    ->asJson()
    ->run()
    ->getBody()
    ->getContents();
```

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

[](#installation)

Install using composer:

```
composer require radiergummi/wander
```

Usage
-----

[](#usage)

The following section provides usage several, concrete examples. For a full reference, view the [reference section](#reference).

### Request shorthands

[](#request-shorthands)

Wander has several layers of shorthands built in, which make working with it as simple as possible. To perform a simple `GET` request, the following is enough:

```
$client = new Wander();
$response = $client
    ->get('https://example.com')
    ->run();
```

Wander has several shorthands for common HTTP methods on the `Wander` object (`GET`, `PUT`, `POST`, `DELETE` and so on).

### Creating request contexts

[](#creating-request-contexts)

A slightly longer version of the above example, using the `createContext` method the shorthands also use internally:

```
$client = new Wander();
$response = $client
    ->createContext(Method::GET, 'https://example.com')
    ->run();
```

This context created here wraps around [PSR-7 requests](https://www.php-fig.org/psr/psr-7/) and adds a few helper methods, making it possible to chain the method calls. Doing so requires creating request instances, which of course relies on a [PSR-17 factory](https://www.php-fig.org/psr/psr-17/) you can swap out for your own. More on that below.

### Sending PSR-7 requests directly

[](#sending-psr-7-requests-directly)

Wander also supports direct handling of request instances:

```
$client = new Wander();
$request = new Request(Method::GET, 'https://example.com');
$response = $client->request($request);
```

### Sending arbitrary request bodies

[](#sending-arbitrary-request-bodies)

Wander accepts any kind of data as for the request body. It will be serialized just before dispatching the request, depending on the `Content-Type` header set *at that time*. This means that you don't have to take care of body serialization.
If you set a PSR-7 `StreamInterface` instance as the body, however, Wander will not attempt to modify the stream and use it as-is.

Sending a JSON request:

```
$client = new Wander();
$response = $client
    ->post('https://example.com')
    ->withBody([
        'anything' => true
    ])
    ->asJson()
    ->run();
```

Sending a raw stream:

```
$client = new Wander();
$response = $client
    ->post('https://example.com')
    ->withBody(Stream::create('/home/moritz/large.mp4'))
    ->run();
```

### Retrieving parsed response bodies

[](#retrieving-parsed-response-bodies)

As request contexts wrap around request instances, there's also response contexts wrapping around [PSR-7 responses](https://www.php-fig.org/psr/psr-7/), providing additional helpers, for example to get a parsed representation of the response body, if possible.

```
$client = new Wander();
$response = $client
    ->get('https://example.com')
    ->run()
    ->getParsedBody();
```

### Exception handling

[](#exception-handling)

Wander follows an exception hierarchy that represents different classes of errors. In contrary to PSR-18 clients, I firmly believe response status codes from the 400 or 500 range *should* throw an exception, because you end up checking for them anyway. Exceptions are friends! Especially in thee case of HTTP, where an error could be an expected part of the flow.

The exception tree looks as follows:

```
 WanderException (inherits from \RuntimeException)
  ├─ ClientException (implements PSR-18 ClientExceptionInterface)
  ├─ DriverException (implements PSR-18 RequestExceptionInterface)
  ├─ ConnectionException (implements PSR-18 NetworkExceptionInterface)
  ├─ SslCertificateException (implements PSR-18 NetworkExceptionInterface)
  ├─ UnresolvableHostException (implements PSR-18 NetworkExceptionInterface)
  └─ ResponseErrorException
      ├─ ClientErrorException
      │   ├─ BadRequestException
      │   ├─ UnauthorizedException
      │   ├─ PaymentRequiredException
      │   ├─ ForbiddenException
      │   ├─ NotFoundException
      │   ├─ MethodNotAllowedException
      │   ├─ NotAcceptableException
      │   ├─ ProxyAuthenticationRequiredException
      │   ├─ RequestTimeoutException
      │   ├─ ConflictException
      │   ├─ GoneException
      │   ├─ LengthRequiredException
      │   ├─ PreconditionFailedException
      │   ├─ PayloadTooLargeException
      │   ├─ UriTooLongException
      │   ├─ UnsupportedMediaTypeException
      │   ├─ RequestedRangeNotSatisfyableException
      │   ├─ ExpectationFailedException
      │   ├─ ImATeapotException
      │   ├─ MisdirectedRequestException
      │   ├─ UnprocessableEntityException
      │   ├─ LockedException
      │   ├─ FailedDependencyException
      │   ├─ TooEarlyException
      │   ├─ UpgradeRequiredException
      │   ├─ PreconditionRequiredException
      │   ├─ TooManyRequestsException
      │   ├─ RequestHeaderFieldsTooLargeException
      │   └─ UnavailableForLegalReasonsException
      └─ ServerErrorException
          ├─ InternalServerErrorException
          ├─ NotImplementedException
          ├─ BadGatewayException
          ├─ ServiceUnavailableException
          ├─ GatewayTimeoutException
          ├─ HTTPVersionNotSupportedException
          ├─ VariantAlsoNegotiatesException
          ├─ InsufficientStorageException
          ├─ LoopDetectedException
          ├─ NotExtendedException
          └─ NetworkAuthenticationRequiredException

```

All response error exceptions provide getters for the request and response instance, so you can do stuff like this easily:

```
try {
    $request->run();
} catch (UnauthorizedException | ForbiddenException $e) {
    $this->refreshAccessToken();

    return $this->retry();
} catch (GoneException $e) {
    throw new RecordDeletedExeption(
        $e->getRequest()->getUri()->getPath()
    );
} catch (BadRequestException $e) {
    $responseBody = $e->getResponse()->getBody()->getContents();
    $error = json_decode($responseBody, JSON_THROW_ON_ERROR);
    $field = $error['field'] ?? null;

    if ($field) {
        throw new ValidatorException("Failed to validate {$field}");
    }

    throw new UnknownException($error);
} catch (WanderException $e) {

    // Simply catch all others
    throw new RuntimeException(
        'Server returned an unknown error: ' .
        $e->getResponse()->getBody()->getContents()
    );
}
```

This was just one of a myriad of ways to handle errors with these kinds of exceptions!

### Setting a request timeout

[](#setting-a-request-timeout)

Request timeouts can be configured on your driver instance:

```
$driver = new StreamDriver();
$driver->setTimeout(3000); // 3000ms / 3s

$client = new Wander($driver);
```

> **Note:**
> Request timeouts are an optional feature for drivers, indicated by the [`SupportsTimeoutsInterface`](./src/Interfaces/Features/SupportsTimeoutsInterface.php). All default drivers implement this interface, though, so you'll only need to check this if you use another implementation.

### Disable following redirects

[](#disable-following-redirects)

By default, drivers will follow redirects. If you want to disable this behavior, configure it on your driver instance:

```
$driver = new StreamDriver();
$driver->followRedirects(false);

$client = new Wander($driver);
```

> **Note:**
> Redirects are an optional feature for drivers, indicated by the [`SupportsRedirectsInterface`](./src/Interfaces/Features/SupportsRedirectsInterface.php). All default drivers implement this interface, though, so you'll only need to check this if you use another implementation.

### Limiting the maximum number of redirects

[](#limiting-the-maximum-number-of-redirects)

By default, drivers will follow redirect indefinitely. If you want to limit the maximum number of redirects, configure it on your driver instance:

```
$driver = new StreamDriver();
$driver->setMaximumRedirects(3);

$client = new Wander($driver);
```

> **Note:**
> Redirects are an optional feature for drivers, indicated by the [`SupportsRedirectsInterface`](./src/Interfaces/Features/SupportsRedirectsInterface.php). All default drivers implement this interface, though, so you'll only need to check this if you use another implementation.

### Adding a body serializer

[](#adding-a-body-serializer)

Wander supports transparent body serialization for requests and responses, by passing the data through a serializer class. Out of the box, Wander ships with serializers for plain text, JSON, XML, form data, and multipart bodies. Serializers follow a well-defined interface, so you can easily add you own serializer for any data format:

```
$client = new Wander();
$client->addSerializer('your/media-type', new CustomSerializer());
```

The serializer will be invoked for any requests and responses with this media type set as its `Content-Type` header.

### Using a custom driver

[](#using-a-custom-driver)

Drivers are what actually handles dispatching requests and processing responses. They have one, simple responsibility: Transform a request instance into a response instance. By default, Wander uses a driver that wraps streams, but it also ships with a curl driver. If you need something else, or require a variation of one of the default drivers, you can either create a new driver implementing the [`DriverInterface`](./src/Interfaces/DriverInterface.php) or extend one of the defaults.

```
$driver = new class implements DriverInterface {
    public function sendRequest(RequestInterface $request): ResponseInterface
    {
      // TODO: Implement sendRequest() method.
    }

    public function setResponseFactory(ResponseFactoryInterface $responseFactory): void
    {
      // TODO: Implement setResponseFactory() method.
    }
};

$client = new Wander($driver);
```

Reference
---------

[](#reference)

This reference shows all available methods.

### Wander: HTTP Client

[](#wander-http-client)

This section describes all methods of the HTTP client itself. When creating a new instance, you can pass several dependencies:

```
new Wander(
    DriverInterface $driver = null,
    ?RequestFactoryInterface $requestFactory = null,
    ?ResponseFactoryInterface $responseFactory = null
)
```

ParameterTypeRequiredDescription`$driver``DriverInterface`NoUnderlying HTTP client driver. Defaults to curl`$requestFactory``RequestFactoryInterface`NoPSR-17 request factory`$responseFactory``ResponseFactoryInterface`NoPSR-17 response factory#### `get`: Create Context Shorthand

[](#get-create-context-shorthand)

Creates a new request context for a `GET` request.

```
get(UriInterface|string $uri): Context
```

ParameterTypeRequiredDescription`$uri``string` or `UriInterface`YesURI instance or string to create one from.#### `post`: Create Context Shorthand

[](#post-create-context-shorthand)

Creates a new request context for a `POST` request.

```
post(UriInterface|string $uri, ?mixed $body = null): Context
```

ParameterTypeRequiredDescription`$uri``string` or `UriInterface`YesURI instance or string to create one from.`$body`Any typeNoData to use as the request body.#### `put`: Create Context Shorthand

[](#put-create-context-shorthand)

Creates a new request context for a `PUT` request.

```
put(UriInterface|string $uri, ?mixed $body = null): Context
```

ParameterTypeRequiredDescription`$uri``string` or `UriInterface`YesURI instance or string to create one from.`$body`Any typeNoData to use as the request body.#### `patch`: Create Context Shorthand

[](#patch-create-context-shorthand)

Creates a new request context for a `PATCH` request.

```
patch(UriInterface|string $uri, ?mixed $body = null): Context
```

ParameterTypeRequiredDescription`$uri``string` or `UriInterface`YesURI instance or string to create one from.`$body`Any typeNoData to use as the request body.#### `delete`: Create Context Shorthand

[](#delete-create-context-shorthand)

Creates a new request context for a `DELETE` request.

```
delete(UriInterface|string $uri, ?mixed $body = null): Context
```

ParameterTypeRequiredDescription`$uri``string` or `UriInterface`YesURI instance or string to create one from.#### `head`: Create Context Shorthand

[](#head-create-context-shorthand)

Creates a new request context for a `HEAD` request.

```
head(UriInterface|string $uri, ?mixed $body = null): Context
```

ParameterTypeRequiredDescription`$uri``string` or `UriInterface`YesURI instance or string to create one from.#### `options`: Create Context Shorthand

[](#options-create-context-shorthand)

Creates a new request context for a `OPTIONS` request.

```
options(UriInterface|string $uri, ?mixed $body = null): Context
```

ParameterTypeRequiredDescription`$uri``string` or `UriInterface`YesURI instance or string to create one from.#### `createContext`

[](#createcontext)

Allows creation of a new request context for an arbitrary request method.

```
createContext(string $method, UriInterface|string $uri): Context
```

ParameterTypeRequiredDescription`$method``string`YesAny request method, case sensitive.`$uri``string` or `UriInterface`YesURI instance or string to create one from.#### `createContextFromRequest`

[](#createcontextfromrequest)

Allows creation of a new request context from an existing request instance.

```
createContextFromRequest(RequestInterface $request): Context
```

ParameterTypeRequiredDescription`$request``RequestInterface`YesExisting request instance to create the context from.#### `request`

[](#request)

Dispatches a request instance on the client instances driver and returns the response.

```
request(RequestInterface $request): ResponseInterface
```

ParameterTypeRequiredDescription`$request``RequestInterface`YesRequest to dispatch.### Context: Request context

[](#context-request-context)

The context object performs transformations on an underlying request instance. In spirit with PSR-7, the request is of course immutable. The context will only keep reference to the current instance. This allows us to chain all method calls and dispatch requests, all without leaving "the chain" even once. We can also add helper methods and keep references to other objects--like the client itself, for example--making it very easy to use and extend. Note that you should rely on the client creating contexts for you; using the constructor manually is discouraged.

```
new Context(
    HttpClientInterface $client,
    RequestInterface $request
)
```

ParameterTypeRequiredDescription`$client``HttpClientInterface`YesHTTP client instance to dispatch the request with.`$request``RequestInterface`YesRequest as created by our request factory.#### `setRequest`

[](#setrequest)

Replaces the request instance.

#### `getRequest`

[](#getrequest)

Retrieves the request instance.

#### `withMethod`

[](#withmethod)

Replaces the HTTP request method.

#### `getMethod`

[](#getmethod)

Retrieves the HTTP request method.

#### `withUri`

[](#withuri)

Replaces the URI instance.

#### `getUri`

[](#geturi)

Retrieves the URI instance.

#### `withQueryString`

[](#withquerystring)

Adds a query string to the URI.

#### `getQueryString`

[](#getquerystring)

Retrieves the query string from the URI.

#### `withQueryParameters`

[](#withqueryparameters)

Adds multiple query parameters to the URI.

#### `getQueryParameters`

[](#getqueryparameters)

Retrieves all query parameters from the URI as a dictionary.

#### `withQueryParameter`

[](#withqueryparameter)

Adds a query parameter to the URI.

#### `withoutQueryParameter`

[](#withoutqueryparameter)

Removes a single query parameter from the URI.

#### `getQueryParameter`

[](#getqueryparameter)

Retrieves a single query parameter from the URI by name.

#### `withHeaders`

[](#withheaders)

Adds multiple headers to the request.

#### `getHeaders`

[](#getheaders)

Retrieves all request headers as a dictionary. Proxy to the PSR-7 request method.

#### `withHeader`

[](#withheader)

Adds a given header to the request. Proxy to the PSR-7 request method.

#### `withoutHeader`

[](#withoutheader)

Removes a given header if it is set on the request. Proxy to the PSR-7 request method.

#### `getHeader`

[](#getheader)

Retrieves an array of all header values. Proxy to the PSR-7 request method.

#### `getHeaderLine`

[](#getheaderline)

Retrieves all header values, delimited by a comma, as a single string. Proxy to the PSR-7 request method.

#### `withAuthorization`

[](#withauthorization)

Sets the `Authorization` header to the given authentication type and credentials.

#### `withBasicAuthorization`

[](#withbasicauthorization)

Sets the `Authorization` header to the type `Basic` and encodes the comma-delimited credentials as Base64.

#### `withBearerAuthorization`

[](#withbearerauthorization)

Sets the `Authorization` header to the type `Bearer` and uses the token for the credentials.

#### `withContentType`

[](#withcontenttype)

Sets the `Content-Type` header.

#### `getContentType`

[](#getcontenttype)

Retrieves the value of the `Content-Type` header if set, returns `null` otherwise.

#### `asJson`

[](#asjson)

Sets the `Content-Type` header to JSON (`application/json`).

#### `asXml`

[](#asxml)

Sets the `Content-Type` header to XML (`text/xml`).

#### `asPlainText`

[](#asplaintext)

Sets the `Content-Type` header to plain text (`text/plain`).

#### `withBody`

[](#withbody)

Sets the (unserialized) body data on the context. This will be serialized according to the `Content-Type` header before dispatching the request, taking care of serialization automatically, so you don't have to. By passing a Stream instance, this process will be skipped in the body will be set on the request as-is.

#### `getBody`

[](#getbody)

Retrieves the current body data.

#### `hasBody`

[](#hasbody)

Checks whether the context has any data in its body.

#### `run`

[](#run)

Dispatches the request to the client instance and creates a response context

Contributing
------------

[](#contributing)

All contributions are welcome, but please be aware of a few requirements:

- We use [psalm](https://psalm.dev/) for static analysis and would like to keep the level at *at least* 2 (but would like to reach 1 in the long run). Any PR with degraded analysis results will not be accepted. To run psalm, use `composer run static-analysis`.
- Unit and integration tests must be supplied with every PR. To run all test suites, use `composer run test`.

###  Health Score

22

—

LowBetter than 21% of packages

Maintenance20

Infrequent updates — may be unmaintained

Popularity8

Limited adoption so far

Community7

Small or concentrated contributor base

Maturity44

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% 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

Unknown

Total

1

Last Release

2107d ago

### Community

Maintainers

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

---

Top Contributors

[![Radiergummi](https://avatars.githubusercontent.com/u/6115429?v=4)](https://github.com/Radiergummi "Radiergummi (27 commits)")

###  Code Quality

TestsPHPUnit

Static AnalysisPsalm

Type Coverage Yes

### Embed Badge

![Health badge](/badges/radiergummi-wander/health.svg)

```
[![Health](https://phpackages.com/badges/radiergummi-wander/health.svg)](https://phpackages.com/packages/radiergummi-wander)
```

###  Alternatives

[tempest/framework

The PHP framework that gets out of your way.

2.2k34.4k15](/packages/tempest-framework)[guzzlehttp/psr7

PSR-7 message implementation that also provides common utility methods

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

Mollie API client library for PHP. Mollie is a European Payment Service provider and offers international payment methods such as Mastercard, VISA, American Express and PayPal, and local payment methods such as iDEAL, Bancontact, SOFORT Banking, SEPA direct debit, Belfius Direct Net, KBC Payment Button and various gift cards such as Podiumcadeaukaart and fashioncheque.

60216.0M83](/packages/mollie-mollie-api-php)[flow-php/flow

PHP ETL - Extract Transform Load - Data processing framework

85036.3k](/packages/flow-php-flow)[cakephp/cakephp

The CakePHP framework

8.9k19.5M1.8k](/packages/cakephp-cakephp)[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)
