Skip to main content

Introducing Core Libraries in PHP

· 7 min read

APIMatic has introduced core libraries to provide a stable runtime that powers all functionality of our SDKs. In this release, we have revamped our PHP SDKs to improve the code quality and provide better test coverage.

note

This feature is not yet released. We are publishing this changelog in advance to help solicit feedback and to help customers prepare for a major Code Generator update.

Refer to Introducing core libraries in APIMatic generated SDKs changelog to understand the importance of core libraries in our SDKs.

Details

APIMatic's PHP SDKs contained a lot of utility code that not only helped the SDK to make API calls and map JSON responses to Custom Models but was also involved in:

  • Creating HTTP requests from user date
  • Deserializing API responses back to object instances
  • Handling various API request states and errors
  • API authentication
  • Validating user and server data
  • Functionality such as File streaming and JSON pass-through

This utility code was growing with the introduction of new features and functionality, which not only increased the SDK size but also made the code untestable.

What has changed?

In order to remedy all these concerns, APIMatic is now introducing Core Libraries in PHP SDKs. These core libraries include:

These libraries contain the code that remained constant from one SDK to another as well as the unit tests to target 100% code coverage. This helps increase the stability of our PHP SDKs, and provides support for new features without having to re-generate and republish the client libraries.

The following changes are being made part of our current PHP SDKs with the introduction of core libraries.

Cleaner and leaner SDK code

Our SDKs now have lesser code than before. We have moved all the core logic of ApiHelper, DatetimeHelper, FileWrapper and HTTP Package to apimatic/core library.

We have also removed our internal utility classes like TestHelper, HttpCallbackCatcher, XMLSerializer, XMLDeserializer, and ExceptionInterface from our PHP SDKs, and moved their functionality with 100% code coverage to apimatic/core and apimatic/core-interfaces libraries.

The best feature introduced in our PHP SDKs through core libraries is a simpler and cleaner API Call code. We have now moved all the request parameter validation, serialization, and deserialization into the apimatic/core library and embedded the API call execution, and error handling code into the core libraries. Abstraction is also introduced in applying authorization parameters to the requests. Now, an endpoint in SDK may only look like this:

$_reqBuilder = $this->requestBuilder('POST', '/api/path')
->server('Server2')
->parameters(
QueryParam::init('date array', $query)->serializeBy([DateHelper::class, 'toRfc1123DateTimeArray']),
HeaderParam::init('header', $header)->required(),
FormParam::init('form 1', $form1)
FormParam::init('form 2', $form2)->unIndexed()
)
->auth('global')
->retryOption(RetryOption::ENABLE_RETRY);


$_resHandler = $this->responseHandler()
->type(ResponseType::class)
->throwErrorOn(405, ErrorType::init('Wrong payload 405', WrongPayloadException::class));


$this->execute($requestBuilder, $responseHandler);

Previously, the same endpoint code had all kinds of validations, mapping, and API call execution bundled into a single function.

//check that all required arguments are provided
if (!isset($textString)) {
throw new \InvalidArgumentException("One or more required arguments were NULL.");
}

//prepare query string for API call
$_queryUrl = $this->config->getBaseUri() . '/body/deletePlainTextBody';

//prepare headers
$_headers = [
'Accept' => 'application/json',
'content-type' => 'text/plain; charset=utf-8'
];

//json encode body
$_bodyJson = $textString;

$_httpRequest = new HttpRequest(HttpMethod::DELETE, $_headers, $_queryUrl);

//call on-before Http callback
if ($this->getHttpCallBack() != null) {
$this->getHttpCallBack()->callOnBeforeRequest($_httpRequest);
}

// and invoke the API call request to fetch the response
try {
$response = self::$request->delete($_httpRequest->getQueryUrl(), $_httpRequest->getHeaders(), $_bodyJson);
} catch (\Unirest\Exception $ex) {
throw new ApiException($ex->getMessage(), $_httpRequest);
}


$_httpResponse = new HttpResponse($response->code, $response->headers, $response->raw_body);
$_httpContext = new HttpContext($_httpRequest, $_httpResponse);

//call on-after Http callback
if ($this->getHttpCallBack() != null) {
$this->getHttpCallBack()->callOnAfterRequest($_httpContext);
}

//return null on 404
if ($response->code == 404) {
return null;
}
//handle errors defined at the API level
$this->validateResponse($_httpResponse, $_httpRequest);
return ApiHelper::mapClass($_httpRequest, $_httpResponse, $response->body, 'ServerResponse');

After improving the API calls, we have also improved our unit tests in PHP SDKs. This is achieved by removing the TestHelper utility class and moving all of its relevant logic to apimatic/core library. With this improvement, assertion unit tests have a much simpler and more readable interface.

$this->newTestCase($result)
->expectStatusRange(200, 208)
->expectHeaders(['content-type' => ['application/json', true]])
->allowExtraHeaders()
->bodyMatcher(KeysAndValuesBodyMatcher::init(TestParam::object('{"responseBody":"some value"}'), true))
->assert();

Previously, the same unit tests assertions were being handled a lot differently. It required a utility class TestHelper to check if the expected result matches the actual result. Following is a code sample of how it was being done:

// Test response code
$this->assertEquals(
200,
self::$httpResponse->getResponse()->getStatusCode(),
"Status is not 200"
);

$this->assertGreaterThanOrEqual(
200,
self::$httpResponse->getResponse()->getStatusCode(),
"Status is not greater then 200"
);

$this->assertLessThanOrEqual(
208,
self::$httpResponse->getResponse()->getStatusCode(),
"Status is not less then 208"
);

// Test whether the captured response is as we expected
$this->assertNotNull($result, "Result does not exist");

$this->assertTrue(
TestHelper::isJsonObjectProperSubsetOf(
'{"passed":true}',
self::$httpResponse->getResponse()->getRawBody(),
true,
true,
false
),
"Response body does not match in keys and/or values"
);

Faster bug fixes and security patches

One of the main advantages of having core libraries is to release security patches and fixes for bugs much faster than before. Now, with the release of core libraries in PHP, there will be no need to regenerate PHP SDKs. This is possible because we have moved all the functionality of our SDKs into the core libraries and its dependency will automatically be updated with each release of these libraries.

Improved test coverage visibility

After moving all the core functionality and logic of our PHP SDKs, we made it possible to write unit tests for each of our components and aim for 100% code coverage of our Utility Code. The following screenshot shows the current code coverage of apimatic/core library for PHP SDKs.

Code coverage of PHP SDK

Upgrades for major language versions and dependency releases

Each year PHP lang releases a new version and deprecates an older one. Every new version of the language comes with a whole lot of exciting new features that are waiting to be added to the SDKs. Core libraries come to the rescue to support major language and dependencies versions without any breaking changes and regeneration of our PHP SDKs. After the release of core libraries, our customers will no longer have to wait for the support of new dependencies and features in the latest PHP version.

New feature: Introduction of ClientBuilder

Although this release focused on making the PHP SDKs lean and clean and avoiding all breaking changes, we found time to squeeze in an extra feature into the release: a new type-safe way of instantiating the SDK!

Previously, SDK Client was initialized through an array of configurations, like:

$client = new SdkClient([
'environment' => 'production',
'enableRetries' => true,
'timeout' => 30
]);

Although this was a general way of providing configurations in PHP, but after initialization, it was causing issues like type mismatch, key not found, etc. To avoid such problems of spelling out array keys, we are providing a new way of client initialization and keeping the existing solution in order to avoid breaking changes. So now, we are introducing the client builder in PHP to set configuration with type checking and value validations, like this:

$client = SdkClientBuilder::init()
->environment(Environment::PROD)
->enableRetries(true)
->timeout(30)
->build();

What's next?

Next up, we are working on revamping our SDK quality in other languages in a similar manner. We intend to release these changes gradually over time so that we can gather feedback and ensure high quality across our releases.