This documentation provides a full example of creating and using an API Client generated with this library.
Be aware that this documentation uses features that are only available in version 1.3.0 or later. Please upgrade to at least this version if you want to follow the following example.
The first step is to install this package:
composer require --dev jane/open-api
We install this library as a dev dependency, as there is no need for your user or the runtime environment to be aware of the generation stuff.
However the generated library will have some dependencies on other packages and classes in jane/open-api. For that, Jane OpenAPI offers a runtime library that you MUST depend on by requiring the following (into your non-dev dependencies):
composer require jane/openapi-runtime
You may need to create an OpenAPI Specification if the API doesn't give you one. For that please refer to this documentation on how to create it: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md
For this example we will assume that you have a swagger.json
file at the root of your
repository with the following content:
{
"swagger": "2.0",
"paths": {
"/foo": {
"get": {
"operationId": "all",
"responses": {
"200": {
"description": "no error",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/Foo"
}
}
}
},
"tags": ["Foo"]
},
"post": {
"operationId": "create",
"responses": {
"201": {
"description": "no error"
}
},
"parameters": [{
"name": "foo",
"in": "body",
"description": "Foo object to create",
"schema": {
"$ref": "#/definitions/Foo"
}
}],
"tags": ["Foo"]
}
},
"/foo/{id}": {
"get": {
"operationId": "get",
"responses": {
"200": {
"description": "no error",
"schema": {
"$ref": "#/definitions/Foo"
}
}
},
"parameters": [{
"name": "id",
"in": "path",
"required": true,
"description": "The foo identifier",
"type": "string"
}],
"tags": ["Foo"]
}
}
},
"definitions": {
"Foo": {
"type": "object",
"properties": {
"bar": {
"type": "string"
}
}
}
}
}
This API defines 3 endpoints:
- GET
/foo
which returns a collection ofFoo
- POST
/foo
which creates aFoo
- GET
/foo/{id}
which returns aFoo
given anid
The Foo
consists of an object having a property bar
(typehint as a string).
Once you have your swagger schema you can generate the API Client.
First you will need to specify some configurations options for this library by
creating a .jane-openapi
file at the root of your repository which returns
a configuration array for Jane OpenAPI:
<?php
return [
'openapi-file' => __DIR__ . '/swagger.json',
'namespace' => 'My\API\Client',
'directory' => __DIR__ . '/generated',
];
Available parameters:
openapi-file
: Path of your OpenAPI Specificationnamespace
: Namespace prefix of the generated filesdirectory
: Location of the generated files
It is highly recommanded that you generate the file in a different directory than your sources to not tempt users to modify these files.
You can then generate the library using the following command:
./vendor/bin/jane-openapi generate
This will create all the needed files in the generated
directory of your
project:
- The
Model
directory will contain all the value objects used by your API (Foo
class in this example) - The
Normalizer
directory will contain normalizers that are able to normalize and denormalize all value objects created (FooNormalizer
here) and also aNormalizerFactory
which lets you get all the created normalizers. - The
Resource
directory will contain classes that are able to call all endpoints specified in the API.
Naming of the resource depends directly of the name of the tag in your specification:
...
"tags": ["Foo"]
...
Each different tag will create a different resource. I.e. if you specify multiple tags then your endpoint will be duplicated in the resource.
If there is no tag for an endpoint, then this library will create a DefaultResource
containing this endpoint.
Be aware that, if you use a different directory for the generated files, you must add this directory to the autoload section of your composer file:
"autoload": {
"psr-4": {
...
"My\\API\\Client\\": "generated/"
}
}
Now that the library is generated, you can use it.
To use this library, you will use the generated Resource
classes.
In our example we will use the FooResource
.
$resource = new My\API\Client\Resource\FooResource($httpClient, $requestFactory, $serializer);
You can notice that your API depends on 3 specific objects:
$httpClient
is a implementation of the HttpClient
and/or the HttpAsyncClient
interface
from HTTPlug.
This allow use to most of the client implementations available in PHP (guzzle, react, zend, cakephp, ...).
To read more about this consult the HTTPlug documentation.
This client is used to make the calls to your API. By default there are made in an asynchronous mode, if the implementation does not support async it will fallback to the sync mode.
For this example we use Guzzle 6:
composer require php-http/guzzle6-adapter
use Http\Adapter\Guzzle6\Client as GuzzleAdapter;
use GuzzleHttp\Client as GuzzleClient;
$httpClient = new GuzzleAdapter(new GuzzleClient([]));
The $requestFactory
allows to create and use a PSR7 Request for the calls.
This library doesn't stick to a particular implementation that's why you must provide a factory to be able to create a PSR7 Request from Guzzle, Diactoros or any another PSR7 implementation.
You can learn more about this on this documentation.
For this example we use Guzzle implementation:
composer require guzzlehttp/psr7 php-http/message
use Http\Message\MessageFactory\GuzzleMessageFactory;
$requestFactory = new GuzzleMessageFactory();
The $serializer
service is used to serialize your value objects into json or
deserialize json into value objects.
The recommended way to create this service is to use the following code:
use My\API\Client\Normalizer\NormalizerFactory;
use Symfony\Component\Serializer\Encoder\JsonDecode;
use Symfony\Component\Serializer\Encoder\JsonEncode;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Serializer;
$serializer = new Serializer(
NormalizerFactory::create(),
[
new JsonEncoder(
new JsonEncode(),
new JsonDecode()
),
]
);
You may want to tweak this serializer for advanced usage (like using XML, or others thing no supported by this library).
However there is some things that you need to be aware of:
- The
NormalizerFactory::create()
is a generated method that allow to pass all the created normalizer when creating your API Client - The
JsonDecode
decoder must be able to transform JSON objects into\stdClass
. As this is the only way, in PHP, to distinguate between an empty collection[]
and an empty object{}
So your setup will look at something like this:
use GuzzleHttp\Client as GuzzleClient;
use Http\Adapter\Guzzle6\Client as GuzzleAdapter;
use Http\Message\MessageFactory\GuzzleMessageFactory;
use My\API\Client\Normalizer\NormalizerFactory;
use My\API\Client\Resource\FooResource;
use Symfony\Component\Serializer\Encoder\JsonDecode;
use Symfony\Component\Serializer\Encoder\JsonEncode;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Serializer;
$httpClient = new GuzzleAdapter(new GuzzleClient([]));
$requestFactory = new GuzzleMessageFactory();
$serializer = new Serializer(
NormalizerFactory::create(),
[
new JsonEncoder(
new JsonEncode(),
new JsonDecode()
),
]
);
$fooResource = new FooResource($httpClient, $requestFactory, $serializer);
To get all of your Foo
objects you just need to do the following call:
$foos = $fooResource->all();
foreach ($foos as $foo) {
echo $foo->getBar();
}
You can also create a Foo
:
$newFoo = new Foo();
$newFoo->setBar('bar_value');
$fooResource->create($newFoo);
Or get a specific Foo
:
echo $fooResource->get(15)->getBar();
Generated resources have a very complete PHPDoc for each endpoint, you should rely on it for more complex cases.
By default each endpoint will try to deserialize the Response
from the API
into value objects.
If there is no possible deserialization it will return a PSR7 ResponseInterface.
However you may want to always fetch a PSR7 ResponseInterface or make the call in an asynchronous mode. This is possible by setting the last parameter of the endpoint method, but if you need the value objets you will have to use the serializer yourself:
$response = $fooResource->all([], FooResource::FETCH_RESPONSE);
$foos = $serializer->deserialize($response, Foo::class, 'json');
$promise = $fooResource->all([], FooResource::FETCH_PROMISE);
...
$response = $promise->wait();
$foos = $serializer->deserialize($response, Foo::class, 'json');