loyaltycorp/sdkblueprint

This package is abandoned and no longer maintained. The author suggests using the eonx-com/sdkblueprint package instead.

A library for quickly building an SDK for a RESTful API

v1.0.0-beta6 2019-09-24 05:41 UTC

README

Package providing base code to develop a PHP SDK.

Contents

Getting started
SDK functionality
Using the SDK
Errors and exceptions

Getting started

Requirements

Before installing the SDK your system must meet the following requirements:

  1. PHP >= 7.1
  2. php-dom
  3. php-json
  4. php-mbstring
  5. allow_url_fopen must be enabled in your php.ini file

Installation

The SDK is installed via composer:

$ composer require loyaltycorp/sdkblueprint

SDK functionality

The SDK provides some global functionality to assist with development. The documentation will use some SDK specific terminology which is described in this section.

Collections and repositories

The SDK uses collections and repositories extensively to store and retrieve data:

  • Collections are used for non-associative arrays which contain arrays
  • Repositories are used for associative arrays

Collections and repositories help avoid common run-time errors such as Undefined index on an array or Trying to get property of non-object when an object is expected.

Extended functionality

Collections and repositories contain some extended functionality over an array that allows simpler access to items:

Method Returns Description
__toString() string The items in json format
count() int The number of items in the collection/repository
first() mixed The first item in the collection/repository
getNth(int) mixed The nth item in the collection/repository
last() mixed The last item in the collection/repository
toArray() array Resolve all collection/repository back to an array recursively

Collections and repositories will both automatically convert child arrays to collections or repositories.

Collections and repositories also implement Traversable which allows them to be iterated with a loop like a standard array.

Collections

A collection is an non-associative array of arrays with extended functionality.

If a collection is instantiated with an associative array an InvalidCollectionException will be thrown. Repositories should be used with associative arrays.

Collection references

For collections first(), getNth() and last() will return references to the items which will allow in-place updates. Collections also have a method deleteNth() to remove an item from a collection and clear() to remove all items from a collection.

$collection = new \LoyaltyCorp\SdkBlueprint\Sdk\Collection([
	new \LoyaltyCorp\SdkBlueprint\Sdk\Repository(['name' => 'One']),
	new \LoyaltyCorp\SdkBlueprint\Sdk\Repository(['name' => 'Two']),
	new \LoyaltyCorp\SdkBlueprint\Sdk\Repository(['name' => 'Three']),
]);

$collection->toArray();
// 0 => ['name' => 'One']
// 1 => ['name' => 'Two']
// 2 => ['name' => 'Three']

// Get second item and update name
$second = $collection->getNth(2);
$second->getName(); // Two
$second->setName('Updated');
$collection->toArray();
// 0 => ['name' => 'One']
// 1 => ['name' => 'Updated']
// 2 => ['name' => 'Three']

// Remove first item, note that keys reset after deletion
$collection->deleteNth(1);
$collection->toArray();
// 0 => ['name' => 'Updated']
// 1 => ['name' => 'Three']

// References are chainable with repository functionality
$collection->first()->setName('First');
$collection->toArray();
// 0 => ['name' => 'First']
// 1 => ['name' => 'Three']

// Remove all items from collection
$collection->clear();
$collection->toArray();
//

Repositories

A repository is a container for associative arrays with extended functionality.

Repositories have four magic methods for accessing data:

Method Returns Description
getX() mixed Gets the value of the attribute within the repository, returns null if the value hasn't been set
hasX() bool Returns true if the attribute exists within the repository and has a non-null value
isX() bool Used specifically for boolean values, returns the true if the value of the attribute is truthy, otherwise returns false
setX(mixed) Repository Sets a value within the repository. This method is chainable

If a magic method is used with an invalid attribute, e.g. an attribute that doesn't exist or can't be set (if attempting to set a non-mutable attribute) an UndefinedMethodException will be thrown.

Attribute conversion

Attributes are stored with their original key however the magic methods require the use of StudlyCase to access data:

Original Studly Example
userid Userid getUserid()
user_id UserId getUserId()
userID UserId getUserId()

When converting a repository back to an array the original key will be restored.

Entities and resources

The SDK uses entities and resources to interact with the API.

Note: Most methods on entities and repositories that do not return a value (such as getX() or hasX()) are chainable.

Entities

An entity represents a singular item in the API, such as a customer. All entities extend a repository to piggyback on the magic getters and setters.

Properties and Inheritance

Entities provides some properties to configure their behaviour, you can either set the properties values directly as follows:

class MyEntity extends Entity 
{
    /**
     * MyEntity constructor
     *
     * @param null|array $data
     */
    public function __construct(?array $data = null)
    {
        // Property value set directly in constructor
        $this->attributes = [ 
            //... 
        ];
    }
}

Or you can define a setter function for the property. The function MUST return an array otherwise an exception will be thrown. Define the function as follows:

class MyEntity extends Entity 
{
    /**
     * Returns entity attributes as an array.
     *
     * @return array
     */
    protected function setAttributes(): array
    {
        return [
            //... 
        ];
    }
}

This way of the setting properties values allows you to use inheritance to override/extends parent's properties values as follows:

class MyEntity extends Entity 
{
    /**
     * Returns entity attributes as an array.
     *
     * @return array
     */
    protected function setAttributes(): array
    {
        return \array_merge(parent::setAttributes(), [
            //... 
        ]);
    }
}

Here is the list of properties and associated setters functions:

Property Setter Description
$attributes setAttributes():array List of attributes used to build request from the entity
$endpoints setEndpoints(): array List of URL managed by the entity
$endpointVersions setEndpointVersions(): array List of version for specific endpoints
$mappings setMappings(): array Mapping keys with associated repositories to parse response content
$mutable setMutable(): array List of attributes you can update
$repositories setRepositories(): array List of sub-repositories for the entity
$rules setRules(): array List of validation rules for attributes for the entity

Resources

A resource indicates a group of items, such as customers. Resources have functionality to paginate and filter results:

Method Default Description
limit(int) 100 The number of results to return
page(int) 1 The page to get
whereX(string) n/a Sets a filter value

The whereX() method works in the same way as the getters and setters on repositories and accepts a string value.

At the moment filters are only available for exact matches which are case sensitive, e.g. whereName('Bob') is not the same as whereName('bob') and the API will return different results. Modifers also can't be used to find values greater than or less than, values passed to filters are always interpreted as equals.

Like the other magic methods, this method will throw an UndefinedMethodException if the attribute is invalid.

Validation

Entities and Resources provide an easy way to implement validation before processing request to avoid any API call with invalid data. Validation is defined via a 2 levels deep associative array within keys are HTTP methods to apply validation to and values are another array defining validation for each attribute as follows:

protected $rules = [
    'get' => [<attribute> => <rules>], // Apply validation to GET
    'post,put' => [<attribute> => <rules>] // Apply validation to POST and PUT
];

To define validation rules for an entity you MUST override the $rules property with the validation rules array described previously. If the definition of your validation rules requires more logic that you cannot perform through the property, you can define the getValidationRules(): array function which returns the validation rules array as follows:

protected function setRules(): array
{
    return [
        'get' => [<attribute> => <rules>], // Apply validation to GET
        'post,put' => [<attribute> => <rules>] // Apply validation to POST and PUT
    ];
}
Validation Rules
Name Syntax Description
Email email Validates given value is valid email address
Entity entity Validates given value is instance of LoyaltyCorp\SdkBlueprint\Sdk\Entity
Enum enum:val1,val2,val3 Validates given value is one of configured values
GreaterThan greaterThan:<integer> Validates given value is greater than configured integer
GreaterThanOrEqualTo greaterThanOrEqualTo:<integer> Validates given value is greater than or equal to configured integer
Instance instance:<FullyQualifiedClassName> Validates given value is instance of configured class
Integer integer Validates given value is an integer
Length length:<integer> Validates given value length equal configured integer
LessThan lessThan:<integer> Validates given value is lower than configured integer
LessThanOrEqualTo lessThanOrEqualTo:<integer> Validates given value is lower than or equal to configured integer
MaxLength maxLength:<integer> Validates given value length is lower than or equal to configured integer
MinLength minLength:<integer> Validates given value length is greater than or equal to configured integer
NotEmpty notEmpty Validates given value is not empty
Numeric numeric Validates given value is numeric
Regex regex:<regex> Validates given value matches configured regex
Required required Validates value is provided, preserves zero values and empty arrays
RequiredWith requiredWith:attr1,attr2,attrN... Validates value is provided if one of configured attributes is provided
RequiredWithout requiredWithout:attr1,attr2,attrN... Validates value is provided for at least one of configured attributes if no given value for current attribute
Type type:<type1>,<type2>... Validates the type of given value is the same as configured type(s)
Url url Validates given value is a valid URL

Responses

Unless a pre-request exception is thrown, responses received by the SDK will always be standardised. This will still happen even if an error is received from the API or an exception is raised by Guzzle.

Every response will always contain at least 4 attributes:

Attribute Type Description
code int The code returned by the API
message string Error or success message returned by the API
status_code int The http status code from the response
successful bool Whether the request was successful or not

A successful response will also include one or more attributes to access data returned by the API which will be outlined under the endpoint specific documentation.

Verifying a response

The response contains an isSuccessful() method which should be used to verify whether a request was successful or not. This method is set correctly even if an error is returned with a successful http status code.

Implementing the SDK

This will provide a basic overview of how to implement the SDK.

Defining your entities and resources

First thing to do is to create your entities and resources objects. Make sure that all of them extends respectively LoyaltyCorp\SdkBlueprint\Sdk\Entity and LoyaltyCorp\SdkBlueprint\Sdk\Resource.

Defining your response

Once your entities and resources defined, create your response object which extends LoyaltyCorp\SdkBlueprint\Sdk\Response and defines your entities mapping via its $repositories property.

Instantiating the client

You can instantiate the client directly using your response object:

$client = new \LoyaltyCorp\SdkBlueprint\Sdk\Client($response);
Advanced usage
Setting the base url

By default the url is http://localhost, if you have another url you want to use you can instantiate the \LoyaltyCorp\SdkBlueprint\Sdk\Parsers\JsonRequestParser object, call the setBaseUrl(string $baseUrl) on it and pass it to the client's constructor as the 2nd parameter:

$requestParser = new JsonRequestParser();
$requestParser->setBaseUrl('https://api.loyaltycorp.com.au');

$client = new \LoyaltyCorp\SdkBlueprint\Sdk\Client($response, $requestParser);

The url must be a valid, if an invalid url is provided an InvalidBaseUrlException will be thrown.

Custom Response body format

The client uses a \LoyaltyCorp\SdkBlueprint\Sdk\Interfaces\ResponseParserInterface to parse the body of the response coming from the API. The default ResponseParser uses the built-in json_decode() function, if you have another parsing logic you want to use you can create a new object which implements ResponseParserInterface and pass it to the client's constructor as the 3rd parameter:

$responseParser = new MyCustomerResponseParser();

$client = new \LoyaltyCorp\SdkBlueprint\Sdk\Client($response, $requestParser, $responseParser);
Custom Guzzle client

You can pass a Guzzle client instance to the SDK client constructor. This is useful if you have enhanced logging, debugging, connection requirements or error handling on an existing client used in your application:

$guzzle = new \GuzzleHttp\Client(['proxy' => '192.168.16.1:10']);
$client = new \EoneoPaySdk\Client($response, $requestParser, $responseParser, $guzzle);

Making a request

All requests are made by passing a entity or resource to a method in the EoneoPay SDK client instance. The client has four methods for interacting with the API:

Method Description
create(entity) Create an entity
delete(entity) Remove an entity
get(entity) Get an entity
list(resource) Get all entities for a resource
update(entity) Update an entity

Not all entities support all CRUD methods, if an entity doesn't support a specific method a MethodNotSupportedException will be thrown.

Example code for every request is provided within the documentation for each endpoint.

Existing entities

Some methods require an existing entity to perform actions on. An existing entity is one of:

  • an entity retrieved via the get by id method
  • a previously created entity which has been stored
  • a previously retrieved entity which has been stored

The existing entity must be the correct class, if storing the entity data rather than the entity itself you must instantiate the entity with the stored data first.

Errors and exceptions

Errors

Errors returned by the API will be handled gracefully via the Response instance.

If an error occurs, the response will:

  • Have no entity/resource specific attributes
  • Return false via isSuccessful()
  • Return the API error code via getCode()
  • Return the API error message via getMessage()
  • Return the http status code via getStatusCode()

Exceptions

If a problem occurs an exception will be thrown:

Exception Reason
AttributeNotSetException The request requires a value that doesn't exist, such as adding a bank account without attaching it to a customer
EndpointValidationFailedException The entity pre-request validation for a request failed
InvalidBaseUrlException The base url being set isn't a valid url
InvalidCollectionException A collection was instantiated with invalid data
InvalidConfigurationException The API key is missing or hasn't been loaded from the .env file
InvalidEntityException An entity was instantiated with invalid data
InvalidEntityConnectionException An attempt was made to connect to entities which are not compatible
InvalidValueFromSetterException An entity defines a magic setter function which doesn't return the expected type
MethodNotSupportedException A call was attempted on a entity which doesn't support it, e.g. create on an entity which only supports get
UndefinedMethodException An invalid magic method was called on a repository or the attribute used in a magic method doesn't exist or is immutable