We are introducing the special oneOf and anyOf types in our PHP SDKs.
Details
In OpenAPI specification, an API call can return multiple types of responses, or take in multiple types of requests:
paths:
/connection:
post:
requestBody:
content:
application/json:
schema:
anyOf:
- $ref: '#/components/schemas/Cat'
- $ref: '#/components/schemas/Dog'
responses:
'200':
description: Updated,
content:
application/json":
schema:
anyOf:
- $ref: '#/components/schemas/Contact'
- $ref: '#/components/schemas/Employee'
...
The above OAS API call description shows a post API call that takes in either Cat
or Dog
as request and returns Contact
or Employee
as response, which in APIMatic's PHP SDK should look like:
- Before
- After
$catOrDog = [
"name" => "kitty",
"weight" => 15
];
$contactOrEmployee = $client->getConnectionController()->sendConnection($catOrDog);
$catOrDog = CatBuilder::init()
->name("kitty")
->weight(15);
$contactOrEmployee = $client->getConnectionController()->postConnection($catOrDog);
In the above API call code sample, $catOrDog
can be anyOf
Cat
or Dog
instance. Previously, it was sent as an associative array
instead of proper model instances.
Let's look at the response type of the same API call in the following example:
- Before
- After
$contactOrEmployee = $client->getConnectionController()->postConnection($catOrDog);
Assert::assertInstanceOf(Contact::class, $contactOrEmployee); // false
Assert::assertInstanceOf(Employee::class, $contactOrEmployee); // false
$contactOrEmployee = $client->getConnectionController()->postConnection($catOrDog);
Assert::assertInstanceOf(Contact::class, $contactOrEmployee); // true
Assert::assertInstanceOf(Employee::class, $contactOrEmployee); // false
In the above example, the API call postConnection()
could return anyOf
Contact
or Employee
instance in its response. But previously, it was only returning an associative array
.
Now, with the introduction of oneOf and anyOf support, we can assert that the response can be of either types based on the received JSON data.
What has Changed?
Our PHP SDKs now have the ability to handle multiple types in requests and responses. This support for oneOf and anyOf also brings in type validations in our PHP SDKs.
Introducing @MapsBy annotation
We are introducing the special oneOf and anyOf type group string formats for our PHP SDKs, which will be used via annotation @mapsBy
.
To have a deeper understanding of this, consider A and B are any two types, then:
oneOf(A,B)
represents either A or B not both.anyOf(A,B)
represents either A, B or both.oneOf(A,B)[]
represents anarray
where an element can either be A or B not both.anyOf(A,B)[]
represents anarray
where an element can either be A, B or both.array<string,oneOf(A,B)>
represents amap
where an element can either be A or B not both.array<string,anyOf(A,B)>
represents amap
where an element can either be A, B or both.oneOf{discField}(A{discValA},B{discValB})
represents the oneOf between A and B while thediscField
represents the discriminator property whilediscValA
anddiscValB
represents the discriminator values for their respective models.
In all the above formats, A or B might represent a simple type like string
, int
, Model
, float[]
or they may also represent a combination of types like, anyOf string and int i.e. anyOf(string,int)
, oneOf Car and map of float i.e. oneOf(Car,array<string,float>)
or an array of oneOf Person and Employee i.e. oneOf(Person,Employee)[]
.
Changes in Models
Introducing union type hints over setters for combination of multiple types along with a newly introduced annotation @mapsBy
which shows the actual combination of types in a special string format that we have already discussed above.
The following example shows a setter with union type hint Morning|Evening
while @mapsBy
annotation shows the type of combination anyOf
.
- Before
- After
/**
* Sets Connection.
*
* @required
* @maps connection
*
* @param array $connection
*/
public function setConnection(array $connection): void
{
$this->connection = $connection;
}
/**
* Sets Connection.
*
* @required
* @maps connection
* @mapsBy anyOf(Contact,Employee)
*
* @param Contact|Employee $connection
*/
public function setConnection($connection): void
{
$this->connection = $connection;
}
The @mapsBy
annotation will be used during the deserialization of a JSON into a specific response model.
Actual union types are only introduced for PHP 8.1 and above, but our SDKs must support PHP 7.2 runtime. Which is why we are currently using union type hints in setters and getters.
So, this actual mixed
type opens door for our SDK users to still use our traditional setter to set any value other than the required one. To avoid such cases, we are now verifying types for all fields which are using multiple type groups before the model's serialization in jsonSerialize()
, i.e.
- Before
- After
public function jsonSerialize()
{
$json = [];
$json['connection'] = $this->connection;
return $json;
}
public function jsonSerialize()
{
$json = [];
$json['connection'] = ApiHelper::getJsonHelper()->verifyTypes(
$this->connection,
'anyOf(Contact,Employee)'
);
return $json;
}
In the above example verifyTypes
will verify whether the field connection
can be mapped by a model Contact
or Employee
.
Changes in Endpoints
We have now started using union type hints for endpoint's requests and responses.
In requests, type verification is also being done through strictType
method by passing the union type format with the parameters.
The following endpoint example shows oneOf/anyOf changes and type verification in the request parameter connection
. Here connection’s union type hint is Contact|Employee
.
- Before
- After
/**
* @param array $connection
*
* @return ServerResponse Response from the API call
*
* @throws ApiException Thrown if API call fails
*/
public function sendConnection(array $connection): ServerResponse {
$_reqBuilder = $this->requestBuilder(RequestMethod::POST, '/send/connection')
->parameters(
FormParam::init('connection', $connection)
);
$_resHandler = $this->responseHandler()->type(ServerResponse::class);
return $this->execute($_reqBuilder, $_resHandler);
}
/**
* @param Contact|Employee $connection
*
* @return ServerResponse Response from the API call
*
* @throws ApiException Thrown if API call fails
*/
public function sendConnection($connection): ServerResponse {
$_reqBuilder = $this->requestBuilder(RequestMethod::POST, '/send/connection')
->parameters(
FormParam::init('connection', $connection)
->strictType('anyOf(Contact,Employee)')
);
$_resHandler = $this->responseHandler()->type(ServerResponse::class);
return $this->execute($_reqBuilder, $_resHandler);
}
In responses, type verification will be done through typeGroup
method by passing the union type format in the response handler.
The following endpoint example shows oneOf/anyOf changes in response, where its type is described by a union type hint Contact|Employee
while the function call typeGroup
is responsible for mapping response JSON data by the specified type group.
- Before
- After
/**
* @return array Response from the API call
*
* @throws ApiException Thrown if API call fails
*/
public function getConnection(): array {
$_reqBuilder = $this->requestBuilder(RequestMethod::POST, '/get/connection');
return $this->execute($_reqBuilder);
}
/**
* @return Contact|Employee Response from the API call
*
* @throws ApiException Thrown if API call fails
*/
public function getConnection() {
$_reqBuilder = $this->requestBuilder(RequestMethod::POST, '/get/connection');
$_resHandler = $this->responseHandler()->typeGroup('anyOf(Contact,Employee)');
return $this->execute($_reqBuilder, $_resHandler);
}
Validation Exceptions
We are now introducing OneOfValidationException
and AnyOfValidationException
which will be thrown whenever an input with invalid type is provided to an endpoint or invalid typed data is received via response from the server. This feature will enforce type strictness and validation of requests and responses within the SDK.
Following code example will end up in OneOfValidationException
catch block if the response JSON data maps to more then one of the multiple types. If none of the types are mapped to the response JSON data, then AnyOfValidationException
catch block will be executed.
try {
$result = $client->getConnectionController()->getConnection();
var_dump($result);
} catch (OneOfValidationException $e) {
// If more then one type matches
echo 'Caught OneOfValidationException: ', $e->getMessage(), "\n";
} catch (AnyOfValidationException $e) {
// If none of the types matches
echo 'Caught AnyOfValidationException: ', $e->getMessage(), "\n";
}
Readme Changes
Now, the PHP SDK README docs will also reflect the changes for oneOf and anyOf types in Parameters. It will also properly link all of the models to the model documentation.
- Before
- After
Similarly, we have also covered the response types for oneOf and anyOf cases like:
- Before
- After
Portal Changes
We have also added the capability to select and configure any of the types from our API Code Playground. With this feature, you can generate your own code samples for all the types.