cubesystems / api-client
API Client provides a modular way to consume API's for Laravel.
Installs: 2 852
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 4
Forks: 1
Open Issues: 0
Requires
- php: 8.0.*
- ext-bcmath: *
- ext-intl: *
- ext-soap: *
- ext-xsl: *
- codedredd/laravel-soap: ^3.0
- illuminate/contracts: ^9.0
- nesbot/carbon: ^2.66
- spatie/laravel-package-tools: ^1.14.0
Requires (Dev)
- barryvdh/laravel-debugbar: ^3.8
- laravel/pint: ^1.0
- mockery/mockery: ^1.6
- nunomaduro/collision: ^6
- nunomaduro/larastan: ^2.0.1
- orchestra/testbench: ^7.0
- pestphp/pest: ^1.0
- pestphp/pest-plugin-laravel: ^1.0
- phpstan/extension-installer: ^1.1
- phpstan/phpstan-deprecation-rules: ^1.0
- phpstan/phpstan-phpunit: ^1.0
- spatie/pest-plugin-test-time: ^1.1
README
This package defines contracts and provides abstract partial implementations of those contracts. It is meant to be used as a base building block for your own highly modular and customisable implementation of API consumption. Feel free to use as little or as much of it as you need.
Installation
You can install the package via composer:
composer require cubesystems/api-client
Configuration
To use Laravel Debugbar integration, enable it within debugbar.php
config file:
return [ ... 'collectors' => [ ... 'api' => true ] ];
Creating custom API client implementation
First of all, let's define some vocabulary.
SOAP
Methods, services and endpoints
REST
Methods, services and endpoints
After this, we can look at the relations between all interfaces:
Relationship between main interfaces
Define an endpoint
For this, we need to implement CubeSystems\ApiClient\Client\Contracts\Endpoint
contract which can be done by extending CubeSystems\ApiClient\Client\AbstractRestEndpoint
or CubeSystems\ApiClient\Client\AbstractSoapEndpoint
class:
use CubeSystems\ApiClient\Client\AbstractSoapEndpoint; class MyEndpoint extends AbstractSoapEndpoint {}
Define a service accessible within an endpoint
For this, we need to implement CubeSystems\ApiClient\Client\Contracts\Service
contract which can be done by extending CubeSystems\ApiClient\Client\AbstractRestService
or CubeSystems\ApiClient\Client\AbstractSoapService
class:
use CubeSystems\ApiClient\Client\AbstractSoapService; class MyService extends AbstractSoapService { protected const SERVICE_PATH = 'path/to/service'; }
Create a request payload class
This can be done by implementing CubeSystems\ApiClient\Client\Contracts\Payload
contract directly or by extending CubeSystems\ApiClient\Client\Payloads\AbstractPayload
class:
use CubeSystems\ApiClient\Client\Payloads\AbstractPayload; class MyPayload extends AbstractPayload { private string $parameter; public function setId(string $id): MyPayload { $this->id = $id; return $this; } public function toArray(): array { return [ 'id' => $this->id ]; } public function getCacheKey(): string { return self::class . $this->id; } }
Create a response class
This can be done by implementing CubeSystems\ApiClient\Client\Contracts\Response
contract directly or by extending CubeSystems\ApiClient\Client\Responses\AbstractResponse
class:
use CubeSystems\ApiClient\Client\Responses\AbstractResponse; class MyResponse extends AbstractResponse { private MyDto $myDto; public function getMyDto(): MyDto { return $this->myDto; } public function setMyDto(MyDto $myDto): MyResponse { $this->myDto = $myDto; return $this; } }
Create a method for a service
CubeSystems\ApiClient\Client\Contracts\Method
is implemented by CubeSystems\ApiClient\Client\Methods\AbstractRestMethod
and CubeSystems\ApiClient\Client\Methods\AbstractSoapMethod
classes.
use CubeSystems\ApiClient\Client\Methods\AbstractSoapMethod; use CubeSystems\ApiClient\Client\Plugs\PlugManager; use CubeSystems\ApiClient\Client\Strategies\NeverCacheStrategy; use Illuminate\Support\Arr; class MyMethod extends AbstractSoapMethod { protected const METHOD_NAME = 'MyMethod'; public function __construct( MyService $service, NeverCacheStrategy $cacheStrategy, PlugManager $plugManager, ) { parent::__construct($service, $cacheStrategy, $plugManager); } protected function toResponse(array $rawResponse, int $httpCode): MyResponse { $response = new MyResponse(); ... $myDto = new MyDto(); $myDto->setName(Arr::get($rawResponse, 'name')); $myDto->setAge((int) Arr::get($rawResponse, 'age')); $response->setDto($myDto); return $response; } }
Calling an API
After all that, you can make calls like this:
use Foo\Endpoints\MyEndpoint; use Foo\Services\MyService; use Foo\Methods\MyMethod; use Foo\Payloads\MyPayload; class MyRepository { public function getMyDtoById(string $id): MyDto { $myEndpoint = new MyEndpoint(config('api-client.endpoints.myEndpoint.url')); $myService = new MyService($myEndpoint, collect(), collect(), new ApiClient()); $myMethod = new MyMethod($myService, new NeverCacheStrategy()); $myPayload = new MyPayload(); $myPayload->setId($id); return $myMethod->call($myPayload)->getMyDto(); } }
Or take advantage of dependency injection done by Laravel.
Extend CubeSystems\ApiClient\ApiClientServiceProvider
class and register your bindings:
public function register(): void { parent::register(); $this->app->singleton(MyEndpoint::class, function (Application $app) { $url = config('api-client.endpoints.myEndpoint.url'); return new MyEndpoint($url); }); }
Now the same thing can be done more concisely:
use Foo\Methods\MyMethod; use Foo\Payloads\MyPayload; class MyRepository { public function getMyDtoById(string $id): MyDto { $myMethod = app(MyMethod::class); $myPayload = new MyPayload(); $myPayload->setId($id); return $myMethod->call($myPayload)->getMyDto(); } }
Take a look at tests for more detailed examples.
Caching
AbstractMethod
class includes a caching mechanism. Just pass desired cache strategy to the constructor. There are 3 strategies available:
CubeSystems\ApiClient\Client\Cache\NeverCacheStrategy
- never cache the responseCubeSystems\ApiClient\Client\Cache\RequestCacheStrategy
- cache the response for the duration of the requestCubeSystems\ApiClient\Client\Cache\TimeIntervalCacheStrategy
- cache the response for a given time interval
You can also create your own strategy by implementing CubeSystems\ApiClient\Client\Contracts\CacheStrategy
contract.
CubeSystems\ApiClient\Client\Contracts\Payload::getCacheKey()
method is used to decide if there are valid cache entries for a given payload. If there are, the response is retrieved from cache. Otherwise, the remote API is called.
Hierarchical caching
If payload returns true
from isUsingCacheHierarchy
method, then corresponding call will be made part of the cache hierarchy. This allows to invalidate all cache entries for given hierarchy at once by calling AbstractMethod
's removeHierarchyFromCache
method.
For this to work, CubeSystems\ApiClient\Client\Contracts\Payload::getCacheHierarchyKey
method must be implemented according to your needs.
Moreover, AbstractPayload
now accepts optional CachePrefix
parameter which can be used to differentiate between cache hierarchies for the same type of method and payload. Typical use case would be to distinguish between different users' cache hierarchies.
There is more
Events
CubeSystems\ApiClient\Client\Methods\AbstractMethod
class fires the following events:
CubeSystems\ApiClient\Events\ApiCalled
- after a remote API is calledCubeSystems\ApiClient\Events\ResponseRetrievedFromCache
- after response is retrieved from cache without calling a remote API
You can listen to those and do some additional stuff like logging.
Laravel Debugbar integration
If debugbar.collectors.api
configuration option is set to true
as described above, all API calls (and cache retrievals) will be visible within Laravel Debugbar:
Testing
This package uses Pest testing framework. You can run tests with
composer test
Changelog
Please see CHANGELOG for more information on what has changed recently.