PHPackages                             nsrosenqvist/php-api-toolkit - 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. nsrosenqvist/php-api-toolkit

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

nsrosenqvist/php-api-toolkit
============================

A versatile toolkit for easily integrating with remote APIs

1.0.0(3y ago)02MITPHPPHP &gt;=7.4.0

Since Dec 10Pushed 1y ago1 watchersCompare

[ Source](https://github.com/nsrosenqvist/php-api-toolkit)[ Packagist](https://packagist.org/packages/nsrosenqvist/php-api-toolkit)[ Docs](https://github.com/nsrosenqvist/php-api-toolkit)[ RSS](/packages/nsrosenqvist-php-api-toolkit/feed)WikiDiscussions main Synced 1mo ago

READMEChangelogDependencies (13)Versions (3)Used By (0)

PHP Api Toolkit
===============

[](#php-api-toolkit)

This toolkit is a wrapper around [Guzzle 7](https://docs.guzzlephp.org/en/stable/) that allow you to work with dedicated clients for a particular remote API. It automatically applies configuration in a seamless manner and keeps code modular and neatly organized. The goal of this library is to remove the need for API specific SDKs and instead provide most functionality you need out of the box, and this includes a comprehensive mocking middleware.

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

[](#installation)

The library requires PHP 7.4+ and can be installed using composer:

```
composer require nsrosenqvist/php-api-toolkit
```

Usage
-----

[](#usage)

The core concept is that each remote API and its associated configuration and authorization is contained within a single class which inherits `NSRosenqvist\ApiToolkit\ADK`. The ADK class contains the core functionality which the library is utilizing, but a more featured base class comes bundled that provides the functionality that one would commonly need. Therefore, one would most often base one's drivers on `NSRosenqvist\ApiToolkit\StandardApi` instead, and any required customizations can easily be performed by overloading methods.

A simple driver only needs basic client configuration and the means of authorization:

```
namespace MyApp\Api;

use NSRosenqvist\ApiToolkit\StandardApi;

class Example extends StandardApi
{
    public function __construct()
    {
        parent::__construct([
            'base_uri' => 'https://example.com',
            'http_errors' => false,
        ], [
            'cache' => true,
            'retries' => 3,
            'auth' => [
                getenv('EXAMPLE_USER'),
                getenv('EXAMPLE_PASS')
            ],
        ]);
    }

    // ...
}
```

The first parameter to the parent ADK class is an `array` defining the Guzzle client options, the second one define the default request options. All the default middleware are configured through request options.

Now one can make requests using this configuration by invoking the regular Guzzle methods on the driver class.

```
$example = new MyApp\Api\Example();

// Synchronous request
$response = $example->get('foo/bar', [
    'query' => ['id' => 100],
]);

// Asynchronous request
$promise = $example->getAsync('foo/bar', [
    'query' => ['id' => 100],
]);

$response = $promise->wait();
```

### Inheritance

[](#inheritance)

No methods are private, since the intention is that child-classes should be able to overload and modify functionality of the base library as needed. The documentation touches on the most common use cases, but please refer to the source code for a comprehensive overview.

#### Handlers

[](#handlers)

Most often, only two methods need to be overloaded by the driver. The first is `resultHandler` which processes the resulting response, both for synchronous and asynchronous requests. It needs to return a `\Psr\Http\Message\ResponseInterface`, and the example below makes use of the built-in `\NSRosenqvist\ApiToolkit\Result` structure that implements the interface while also providing some additional useful features.

```
use Psr\Http\Message\ResponseInterface;
use NSRosenqvist\ApiToolkit\Structures\Result;
use NSRosenqvist\ApiToolkit\Structures\ListData;
use NSRosenqvist\ApiToolkit\Structures\ObjectData;

// ...

    public function resultHandler(ResponseInterface $response, array $options): Result
    {
        $type = $this->contentType($response);
        $data = $this->decode($response->getBody(), $type);

        if (is_array($data)) {
            $data = new ListData($data);
        } elseif (is_object($data)) {
            $data = new ObjectData($data);
        }

        return new Result($response, $data);
    }
```

The error handler gets invoked whenever the request fails for some reason. Here you can take any required action, such as logging, and then either return the exception or rethrow it.

```
    public function errorHandler(RequestException $reason, array $options): RequestException
    {
        $this->logError($reason->getMessage());

        return $reason;
    }
```

Please note that if `http_errors` are set to false (as in the example), then the error handler won't process automatically but must instead be manually called from the result handler. Either one could handle successful requests and errors both in the result handler, or just create a `\GuzzleHttp\Exception\RequestException` and pass it on to the error handler. The built-in recorder middleware makes sure that the original request is accessible through the response.

```
use Psr\Http\Message\ResponseInterface;
use GuzzleHttp\Exception\RequestException;
use NSRosenqvist\ApiToolkit\Structures\Result;

// ...

    public function resultHandler(ResponseInterface $response, array $options): Result
    {
        $result = new Result($response);

        if ($result->failure()) {
            $request = $response->getRequest();
            $exception = RequestException::create($request, $response);

            $this->errorHandler($exception);
        } else {
            // ...
        }

        return $result;
    }
```

### Manager

[](#manager)

A manager class comes built-in, which is meant to be used as a singleton. Use this to easily access APIs from different places in your codebase, preferably through dependency injection or a facade (see the included `LaravelApiProvider` service provider).

```
require 'vendor/autoload.php';

$manager = new NSRosenqvist\ApiToolkit\Manager();
$manager->registerDriver('example', '\\MyApp\\Api\\Example');

$example = $manager->get('example'); // instance of \MyApp\Api\Example
```

If you register the APIs by their FQN as strings and use an autoloader, then they won't be loaded into memory before you actually make use of them.

### Chains

[](#chains)

One of the big additions of this library is the concept of request chains. If a non-existant property is accessed on the driver, a new request chain is created. These are simple object-oriented representations of a request, suitable for when integrating towards RESTful APIs. All usual Guzzle magic methods are supported, and the only difference is that they don't accept the URI in the method call, instead it gets dynamically resolved from the chain.

```
$api = $manager->get('example');

$result = $api->customers->get();        // GET /customers
$result = $api->customers->{100}->get(); // GET /customers/100

// The chain object is immutable, so one could even store a specific endpoint
// and make later chained requests based off of it
$settings = $api->settings; // Instance of \NSRosenqvist\ApiToolkit\RequestChain
$bar = $settings->foo->get();
$ipsum = $settings->lorem->get();

// Async calls works just as well
$promise = $api->customers->getAsync();

// There are also helper functions, these three all perform the same request
$deals = $api->deals->get([
    'query' => ['sort' => 'desc'],
]);
$deals = $api->deals->option('query', ['sort' => 'desc'])->get();
$deals = $api->deals->query(['sort' => 'desc'])->get();
$deals = $api->deals->queryArg('sort', 'desc')->get();
```

The chain gets resolved by the initating driver, so to customize how it should be resolved one would overload the `resolve` method.

```
    // ...

    public function resolve(RequestChain $chain): string
    {
        $options = $chain->getOptions();

        if (isset($options['api_version'])) {
            return 'v' . $options['api_version'] . '/' . $chain->resolve();
        } else {
            return $chain->resolve();
        }
    }
```

Chains can easily be initated with custom options, if one would like to provide a simple convenience method for defining the target API version, like in the example above, one could create a custom method on the driver returning a new request chain.

```
use NSRosenqvist\ApiToolkit\RequestChain;

    // ...

    public function version($version = '1.5'): RequestChain
    {
        return $this->chain([
            'api_version' => (string) $version,
        ]);
    }
```

Then one could simply call a specific version of the API by initiating the chain using the method `version`:

```
$result = $api->version(2)->customers->get();
```

### Mocking

[](#mocking)

It's easy to test APIs built using this toolkit, either include the mock middleware in your handler stack or base the driver on `\NSRosenqvist\ApiToolkit\StandardApi` to have it included by default. To simply make all requests return 200 (by default), and never be sent away to a remote server, just enable mocking using the request option `mock` or by toggling it for subsequent requests through the method `mocking`.

```
$api = $manager->get('httpbin');
$api->mocking(true);

$result = $api->status->{500}->get(); // Will return 200 instead of 500

$api->mocking(false);

$result = $api->status->{500}->get(); // 500 Server Error
```

#### Queue

[](#queue)

A better method is to predefine what responses should be returned using the queue system. When responses are queued, the mocking middleware will be enabled automatically and doesn't need to be disabled afterwards.

```
$api = $manager->get('httpbin');
$api->queue([
    $api->response(200),
    $api->response(201),
    $api->response(202),
]);

$result = $api->status->{500}->get(); // Will return 200
$result = $api->status->{500}->get(); // Will return 201
$result = $api->status->{500}->get(); // Will return 202
```

#### Manifest

[](#manifest)

Mock manifests are basically router files that define what responses should be given to different requests. It's a quite flexible syntax that is designed to help you write as little configuration as possible. Only PHP and JSON files are supported out of the box, but you can easily enable JSONC or Yaml support by requiring either `adhocore/json-comment` or `symfony/yaml` in your `composer.json` file.

The basic syntax is as follows:

```
{
    // Request URI
    "foo/bar": {
        "code": 200,                     // Return code
        "content": "Lorem Ipsum",        // Response body
        "headers": {                     // Response headers
            "Content-Type": "text/plain" // This isn't required, we will attempt to set the mime type automatically
        },
        "reasonPhrase": "OK",            // Reason phrase
        "version": "1.1",                // Protocol version
    }
}
```

`content` can either be a simple string, or a path to directory containing files defining response bodies (set the default request parameter `stubs`). Absolute paths will work as well.

##### Variable matching

[](#variable-matching)

One can create multiple response definitions for the same route by wrapping them in an array and using a match statement. In the example below, a request to `foo/bar/lorem` would yield a 200 response, while a request to `foo/bar/ipsum` would yield a 202.

```
{
    // Named variable in route definition
    "foo/bar/{var}": [
        {
            "code": 200,
            "match": {
                "var": "lorem"
            }
        },
        {
            "code": 202,
            "match": {
                "var": "ipsum"
            }
        }
    ]
}
```

The variable names "query" and "method" are reserved, since they are used for defining conditions for method and query matching.

##### Wildcard patterns

[](#wildcard-patterns)

You can even mix and match named variables with glob wildcards:

```
{
    // Named variable in route definition
    "foo/bar/{var}": [
        // ...
    ],
    "foo/bar/*": {
        "code": 404
    }
}
```

The routes will be matched according to specificity, so named variables will be prioritized before wildcards. Under the hood the wildcard matching uses `fnmatch` and therefore one could use more advanced patterns, but they are not officially supported.

##### Method matching

[](#method-matching)

The method key in the match definition allows one to specify different responses for different methods.

```
{
    "foo/bar": [
        {
            "code": 200,
            "match": {
                "method": "GET"
            }
        },
        {
            "code": 202,
            "match": {
                "method": "POST"
            }
        }
    ]
}
```

##### Query conditions

[](#query-conditions)

In addition to variable matching, one can also test the query parameters (these will also be prioritized in order of specificity).

```
{
    "foo/bar": [
        {
            "code": 200,
            "match": {
                "query": {
                    "type": "foo"
                }
            }
        },
        {
            "code": 202,
            "match": {
                "query": {
                    "type": "bar"
                }
            }
        }
    ]
}
```

###### Advanced query matching

[](#advanced-query-matching)

In addition to direct comparisons, one can instead set any of these special comparison operators:

- `__isset__`: Tests whether the parameter exist.
- `__missing__`: Tests whether the parameter does not exist.
- `__true__`: Tests whether the parameter is truthy (this includes, "yes", "y", 1, etc.).
- `__false__`: Tests whether the parameter is falsey (this includes, "no", "n", 0, etc.).
- `__bool__`: Tests whether the parameter is booly.
- `__string__`: Tests whether the parameter is a string.
- `__numeric__`: Tests whether the parameter is a numeric.
- `__int__`: Tests whether the parameter is an int.
- `__float__`: Tests whether the parameter is a float.
- `__array__`: Tests whether the parameter is an array.

##### Alternative syntaxes

[](#alternative-syntaxes)

In order to minimize required configuration, some alternative syntaxes are also supported. These will be normalized upon import.

###### Short syntax

[](#short-syntax)

```
{
    "alternative/syntax/short-code": 100,                // Will return a response with status 100
    "alternative/syntax/short-content": "Response body", // Will return a 200 response with custom body
    "alternative/syntax/short-stub": "file.txt",         // Will return a 200 response with body loaded from a file
}
```

###### REST syntax

[](#rest-syntax)

The REST syntax allows one to define responses according to HTTP method (the method will be expanded into `match->method`, like a regular method match). This syntax also supports short syntax definitions.

```
{
    "alternative/syntax/rest": {
        "GET": "response body",
        "POST": 204,
        "*": 404 // Catch-all definition is also supported
    }
}
```

### Structures

[](#structures)

The library comes with a couple of built-in general data structures that are meant to remove the need for custom data structures. However, this comes with no type validation, but that could easily be implemented in the result handler using something like JSON Schema, if desired.

#### ObjectData

[](#objectdata)

The `NSRosenqvist\ApiToolkit\Structures\ObjectData` class is a general purpose object that provides both property and array access to its members. This means that the following means of using it are equivalent and the code sample will output "true":

```
use NSRosenqvist\ApiToolkit\Structures\ObjectData;

$data = new ObjectData([
    'foo' => 'bar',
]);

if ($data['foo'] === $data->foo) {
    echo 'true';
}
```

It also implements `Illuminate\Contracts\Support\Arrayable` and `Illuminate\Contracts\Support\Jsonable` which allows it to be easily converted into an array (`toArray`) or JSON (`toJson`).

#### ListData

[](#listdata)

The `NSRosenqvist\ApiToolkit\Structures\ListData` class is a general purpose list data structure. It supports array access, and like `NSRosenqvist\ApiToolkit\Structures\ObjectData`, it can also be easily converted to an array or JSON.

#### Macros

[](#macros)

Both `NSRosenqvist\ApiToolkit\Structures\ObjectData` and `NSRosenqvist\ApiToolkit\Structures\ListData` supports macros. You can define custom methods that will be available on all instances of the classes (or on a child-class if you want to keep macros separate per API driver).

```
use NSRosenqvist\ApiToolkit\Structures\ObjectData;

ObjectData::macro('html', function() {
    return "{$this->foo}";
});

$data = new ObjectData([
    'foo' => 'bar',
]);

echo $data->html(); // "bar"
```

#### Result

[](#result)

The `NSRosenqvist\ApiToolkit\Structures\Result` class is an implementation of `\Psr\Http\Message\ResponseInterface` that provides direct access to the underlying response body data. It defines a couple of convenience methods, like `success` and `failure`, but mostly it serves as a wrapper around the provided data. If the data is a `NSRosenqvist\ApiToolkit\Structures\ListData`, it can be iterated upon directly, and if `NSRosenqvist\ApiToolkit\Structures\ObjectData`, then it can be accessed directly.

```
use GuzzleHttp\Psr7\Response;
use NSRosenqvist\ApiToolkit\Structures\Result;
use NSRosenqvist\ApiToolkit\Structures\ListData;
use NSRosenqvist\ApiToolkit\Structures\ObjectData;

// ObjectData
$result = new Result(new Response(/* ... */), new ObjectData([
    'foo' => 'bar',
]));

echo $result->foo; // "bar"

// ListData
$result = new Result(new Response(/* ... */), new ListData([
    new ObjectData(['foo' => 'bar']),
    new ObjectData(['foo' => 'ipsum']),
]));

foreach ($result as $item) {
    echo $result->foo;
    // First iteration: "bar"
    // Second iteration: "ipsum"
}
```

### Included Middleware

[](#included-middleware)

`\NSRosenqvist\ApiToolkit\StandardApi` includes a couple of default middleware that are meant to provide common API SDK functionality.

#### CacheMiddleware

[](#cachemiddleware)

The included caching mechanism is a very simple implementation that only makes sure that the same request isn't executed more than once per PHP request process. It only does this for GET requests. The middleware can be toggled on and off by using the option flag `cache`.

`\NSRosenqvist\ApiToolkit\StandardApi` implements a helper method, named `cache`, that toggles the setting for current request chain.

```
$api = $manager->get('example');

$customer = $api->cache(true)->customer->{100}->get(); // Fetched from remote
$customer = $api->cache(true)->customer->{100}->get(); // Fetched from cache
$customer = $api->cache(false)->customer->{100}->get(); // Since cache was set to false, it disregarded the cache and fetched it anew from the remote
```

#### MockMiddleware

[](#mockmiddleware)

The functionality of the mocking middleware is described under the section on mocking. It is configured for each request using request options, but this is handled automatically in `\NSRosenqvist\ApiToolkit\StandardApi`. The options it accepts are:

- `mock`: Enable or disable the middleware.
- `manifest`: An object or path to the manifest for mock routing.
- `stubs`: The directory containing files that the manifest can return as content.
- `response`: A directly defined response that will be returned (used by StandardApi's queue functionality).

#### RecorderMiddleware

[](#recordermiddleware)

The recorder middleware simply replaces the response with a `\NSRosenqvist\ApiToolkit\RetrospectiveResponse` which also keeps a reference to the current request. This is in order to be able to do request dependent processing in the result handler. If this isn't required you can simply overload the `getHandlerStack` method and skip including this middleware.

Similar functionality can be achieved by inspecting the option `endpoint`, which gets automatically set on requests relative to the base URI. Absolute requests will instead have the option `url` set.

#### RetryMiddleware

[](#retrymiddleware)

The retry middleware will perform the request again if the remote returned 429, with a maximun number of retries and exponential delay. This middleware is controlled by the option `max_retries`. It also supports custom retry-decider callbacks.

License
-------

[](#license)

The library is licensed under [MIT](https://raw.githubusercontent.com/nsrosenqvist/php-api-toolkit/master/LICENSE.md) and certain middlewares are based on ones included by default in Guzzle 7, which are also under [MIT](https://raw.githubusercontent.com/guzzle/guzzle/7.5.0/LICENSE). Some of the data structures are based on ones from the Illuminate libraries, which are also licensed under [MIT](https://raw.githubusercontent.com/illuminate/support/master/LICENSE.md).

Development
-----------

[](#development)

Contributions are very much welcome. If you clone the repo and then run `composer install`, certain hooks should automatically be configured that make sure code standards are upheld before any changes can be pushed. We try to follow PSR-12 and maintain compatibity with PHP 7.4 for now. See [composer.json](https://raw.githubusercontent.com/nsrosenqvist/php-api-toolkit/master/composer.json) for configured commands (e.g. `test`, `lint`, `compat`).

###  Health Score

23

—

LowBetter than 27% of packages

Maintenance28

Infrequent updates — may be unmaintained

Popularity2

Limited adoption so far

Community7

Small or concentrated contributor base

Maturity47

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

1253d ago

### Community

Maintainers

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

---

Top Contributors

[![nsrosenqvist](https://avatars.githubusercontent.com/u/1303475?v=4)](https://github.com/nsrosenqvist "nsrosenqvist (6 commits)")

---

Tags

apiguzzlelaravelmockingphptoolkit

###  Code Quality

TestsPest

Code StylePHP\_CodeSniffer

### Embed Badge

![Health badge](/badges/nsrosenqvist-php-api-toolkit/health.svg)

```
[![Health](https://phpackages.com/badges/nsrosenqvist-php-api-toolkit/health.svg)](https://phpackages.com/packages/nsrosenqvist-php-api-toolkit)
```

###  Alternatives

[illuminate/http

The Illuminate Http package.

11936.0M5.1k](/packages/illuminate-http)[aedart/athenaeum

Athenaeum is a mono repository; a collection of various PHP packages

245.2k](/packages/aedart-athenaeum)[laudis/neo4j-php-client

Neo4j-PHP-Client is the most advanced PHP Client for Neo4j

184616.9k31](/packages/laudis-neo4j-php-client)[akamai-open/edgegrid-client

Implements the Akamai {OPEN} EdgeGrid Authentication specified by https://developer.akamai.com/introduction/Client\_Auth.html

482.5M6](/packages/akamai-open-edgegrid-client)[art4/requests-psr18-adapter

Use WordPress/Requests as a PSR-18 HTTP client

153.3k](/packages/art4-requests-psr18-adapter)

PHPackages © 2026

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