loyaltycorp / sdkblueprint
A library for quickly building an SDK for a RESTful API
Installs: 325
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 9
Forks: 0
Open Issues: 0
Type:package
Requires
- php: >=7.1
- ext-dom: *
- ext-json: *
- ext-libxml: *
- ext-mbstring: *
- ext-simplexml: *
- doctrine/annotations: ^1.6
- doctrine/cache: ^1.7
- eoneopay/utils: ^0.2
- guzzlehttp/guzzle: ^6.0
- symfony/expression-language: ^4.1
- symfony/intl: ^4.1
- symfony/property-access: ^4.1
- symfony/property-info: ^4.1
- symfony/serializer: ^4.1
Requires (Dev)
- eoneopay/standards: dev-master
- friendsofphp/php-cs-fixer: ^2.9
- indigophp/doctrine-annotation-autoload: ^0.1.0
- php-http/guzzle6-adapter: ^1.1
- phpmd/phpmd: ^2.6
- phpstan/phpstan: ^0.11
- phpstan/phpstan-phpunit: ^0.11
- phpstan/phpstan-strict-rules: ^0.11
- phpunit/phpunit: ^7.0
- roave/security-advisories: dev-master
- sebastian/phpcpd: ^4.0
- squizlabs/php_codesniffer: 3.*
- woohoolabs/yang: ^1.2
Suggests
- sensiolabs/security-checker: Check project's dependencies for known vulnerabilities
This package is auto-updated.
Last update: 2019-11-20 02:24:59 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:
- PHP >= 7.1
- php-dom
- php-json
- php-mbstring
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()
orhasX()
) 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 |
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 |