Skip to content

Commit

Permalink
Merge pull request #16 from JustSteveKing/feature/new-release
Browse files Browse the repository at this point in the history
Feature/new release
  • Loading branch information
JustSteveKing authored Jan 2, 2024
2 parents fcab5ec + 5757605 commit 81df125
Show file tree
Hide file tree
Showing 40 changed files with 874 additions and 969 deletions.
12 changes: 6 additions & 6 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,19 @@ jobs:
strategy:
fail-fast: true
matrix:
php: [ 8.0, 8.1 ]
php: [ 8.3 ]

name: PHP ${{ matrix.php }} on Linux

steps:
- name: Checkout Code
uses: actions/checkout@v2
uses: actions/checkout@v4

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: dom, curl, libxml, mbstring, zip, http
extensions: dom, curl, fileinfo, json, libxml, mbstring, zip, http
tools: composer:v2
coverage: none

Expand All @@ -37,7 +37,7 @@ jobs:
strategy:
fail-fast: true
matrix:
php: [ 8.0, 8.1 ]
php: [ 8.3 ]

name: PHP ${{ matrix.php }} on Windows

Expand All @@ -48,13 +48,13 @@ jobs:
git config --global core.autocrlf false
git config --global core.eol lf
- name: Checkout code
uses: actions/checkout@v2
uses: actions/checkout@v4

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: dom, curl, libxml, mbstring, zip, http
extensions: dom, curl, fileinfo, json, libxml, mbstring, zip, http
tools: composer:v2
coverage: none
ini-values: memory_limit=512M
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
.idea/
.phpunit.result.cache
composer.lock
/.phpunit.cache
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2020 Steve McDougall
Copyright (c) 2023 Steve McDougall<juststevemcd@gmail.com>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
259 changes: 100 additions & 159 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
# PHP SDK

<p align="center">

![](./banner.png)

</p>

<!-- BADGES_START -->
[![Latest Version][badge-release]][packagist]
[![PHP Version][badge-php]][php]
Expand All @@ -21,7 +15,7 @@
[downloads]: https://packagist.org/packages/juststeveking/php-sdk
<!-- BADGES_END -->

A framework for building simple to use SDKs in PHP 8.0 and above.
A framework for building SDKs in PHP.

## Installation

Expand All @@ -35,203 +29,150 @@ The purpose of this package is to provide a consistent and interoperable way to

## Usage

Working with this library is relatively simple, and an example can be found in the [demo](./demo) and [examples](./examples) directories.

The basic concept is that you will need to provide:

- PSR-17 Request and Response Factory.
- PSR-7 Messages

Inside this library we are using a PSR-18 implementation allowing you to connect the pieces together under the hood and provide SDK functionality using a replaceable set of components.

I highly recommend either:

- [nyholm/psr7](https://github.com/Nyholm/psr7/)
- [slim/psr7](https://github.com/slimphp/Slim-Psr7)
- [symfony/http-client](https://github.com/symfony/http-client)
- [laminas/diactoros](https://github.com/laminas/laminas-diactoros)

To handle the Http PSRs as they are lightweight and designed to be simple and PSR compliant.

### Building the SDK

To begin with we need to be able to build our SDK, to do this we can either use the `constructor` or use the static `build` method.:

#### SDK constructor

To create an SDK instance; simply pass through a uri, a Http Client that uses auto-discovery to find the available PSR-18 client, an authentication strategy, and an instance of the `Container`.

```php
use JustSteveKing\HttpAuth\Strategies\BasicStrategy;
use JustSteveKing\HttpSlim\HttpClient;
use JustSteveKing\UriBuilder\Uri;
use JustSteveKing\PhpSdk\SDK;
use PHPFox\Container\Container;

$sdk = new SDK(
uri: Uri::fromString('https://www.domain.com'),
client: HttpClient::build(),
strategy: new BasicStrategy(
authString: base64_encode("username:password")
),
container: Container::getInstance(),
);
```

#### SDK build
To get started with this library, you need to start by extending the `Client` class. Let's walk through building an SDK.

To use the static build method, the only requirement is to pass through a uri. If you want to set a custom Authentication Strategy you can also pass this through otherwise it will default to a Null Strategy.
### Create your SDK class

```php
use JustSteveKing\UriBuilder\Uri;
use JustSteveKing\PhpSdk\SDK;
use JustSteveKing\Sdk\Client;

$sdk = SDK::build(
uri: 'https://www.domain.com',
);
final class YourSDK extends Client
{
//
}
```

### Adding Resources to our SDK

Each Resource you add to your SDK requires 2 things:

- Implements `ResourceContract`
- Extends `AbstractResource`

Your resource should look like this:
Once this is in place, you can start adding your resources to the class. Let's add a `projects` method for a projects resource.

```php
use JustSteveKing\PhpSdk\Contracts\ResourceContract;
use JustSteveKing\PhpSdk\Resources\AbstractResource;
use JustSteveKing\Sdk\Client;
use JustSteveKing\Sdk\Tests\Stubs\Resources\ProjectResource;

class TestResource extends AbstractResource implements ResourceContract
final class YourSDK extends Client
{
protected string $path = '/test';

public static function name(): string
public function projects()
{
return 'tests';
return new ProjectResource(
client: $this,
);
}
}

```

The Path property allows you to set the uri path for this resource, and the static name method is how this resource is stored on the container.
We return a new instance of our resource classes, passing through your SDK as a `client`. This is so that each resource is able to talk through the client to send requests.

To add this resource to the SDK, you can use the add method:
Now, let's look at how to structure a resource.

```php
$sdk->add(
name: TestResource::name(),
resource: TestResource::class,
);
final class ProjectResource
{
//
}
```

Internally this will add the resource onto container and inject the SDK into the constructor, allowing you to access the Http Client and other aspects of the SDK.
To save time, there are a collection of traits that you can use on your resources.

### Calling a Resource
- `CanAccessClient` - which will add the default constructor required for a resource.
- `CanCreateDataObjects` - which will allow you to create DataObjects from API responses.
- `CanCreateRequests` - which will allow you to create HTTP requests and payloads using PSR-17 Factories.

Now that you have added a resource to the SDK, you are able to call it using the PHP magic __get method:
Let's look at an example of a full resource class.

```php
$response = $sdk->tests->get();
```

This will return a nice PSR-7 response for you to work with inside your SDK code.

### API
use Exception;
use JustSteveKing\Sdk\Concerns\Resources;
use JustSteveKing\Tools\Http\Enums\Method;
use Ramsey\Collection\Collection;
use Throwable;

The below documents the API of the PHP-SDK:
final class ProjectResource
{
use Resources\CanAccessClient;
use Resources\CanCreateDataObjects;
use Resources\CanCreateRequests;

#### SDK class
public function all(): Collection
{
$request = $this->request(
method: Method::GET,
uri: '/projects',
);

Your own SDK class should extend the base SDK class for easier integration.
try {
$response = $this->client->send(
request: $request,
);
} catch (Throwable $exception) {
throw new Exception(
message: 'Failed to list test.',
code: $exception->getCode(),
previous: $exception,
);
}

return (new Collection(
collectionType: Project::class,
data: array_map(
callback: static fn(array $data): Project => Project::make(
data: $data,
),
array: (array) json_decode(
json: $response->getBody()->getContents(),
associative: true,
flags: JSON_THROW_ON_ERROR,
),
),
));
}
}
```

- `__construct(URI $uri, HttpClient $client, Container $container, null|StrategyInterface $strategy)` **The SDK constructor.**
- `static build(string $uri, null|StrategyInterface $strategy = null, null|Container = null): SDK` **This static build method allows the defaults to be set for you to get an SDK quickly.**
- `add(string $name, string $resource): self` **The add method allows you to add resources onto the SDK container, and checks that the resource being passed extends the AbstractResource and implements the ResourceContract.**
- `uri(): Uri` **Return the setup instance of UriBuilder that is being used, to allow you to manipulate the URI string.**
- `client(): HttpClient` **Return the setup instance of the HttpClient that is being used, to allow you to enforce any changes that are required.**
- `strategy(): StrategyInterface` **Returns the setup instance of the Authentication Strategy that is being used, to allow you to export the auth header array.**
- `container(): Container` **Returns the setup instance of the Container, to allow you to bind make and work with the container directly should it be required.**
- `__get(string $name): ResourceContract` **Returns a build instance of the called Resource if it has been added to the container.**
We start by creating a request, and then try to get a response by sending it through the client.

#### AbstractResource class
Once we have a response, we create a `Collection` thanks to a package by Ben Ramsey. We pass through the type of each item we expect it to be,
then the data as an array. To create the data we map over the response content and statically create a new Data Object.

Your resources must all extend the Abstract Resource class.
This allows us to keep our code clean, concise, and testable.

- `__construct(SDK $sdk, null|string $path = null, string $authHeader = 'Bearer', array $with = [], array $relations = [], bool $strictRelations = false, null|string $load = null)` **The Resource constructor.**
- `sdk(): SDK` **Return the setup instance of the SDK that has been passed through to the resource.**
- `getWith(): array` **Return an array of relations to sideload onto the request.**
- `getLoad(): string|null` **Return a string or null if a specific resource identifier has been passed in.**
- `load(string|int $identifier): self` **The load method allows you to set a specific resource identifier to look for on an API.**
- `uri(Uri $uri): self` **The uri method allows you to completely override the URI Builder on the SDK.**
- `client(HttpClient $http): self` **The client method allows you to completely override the Http Client on the SDK.**
- `strategy(StrategyInterface $strategy): self` **The strategy method allows you to completely override the Authentication Strategy on the SDK.**
- `loadPath(): self` **Loads the path from the resource into to URI builder on the SDK.**
- `get(): ResponseInterface` **Performs a GET request to the resource path, to return a list of resources.**
- `find(string|int $identifier): ResponseInterface` **Performs a GET request to the resource path with an identifier appended to it, to return a single resource.**
- `create(array $data): ResponseInterface` **Performs a POST request to the resource path, to create a new single resource.**
- `update($identifier, array $data, string $method = 'patch'): ResponseInterface` **Performs either a PATCH or PUT request to the resource path with an identifier appended to it, to update a single resource.**
- `delete(string|int $identifier): ResponseInterface` **Performs a DELETE request to the resource path with an identifier appended to it, to remove a resource.**
- `where(string $key, $value): self` **Builds the query parameters in a famility query builder style syntax.**
## Testing

#### ResourceContract Interface
To run the test:

Your resources must implement the ResourceContract interface.
```bash
composer run test
```

- `static name(): string` **Returns a string representation of the resource name, to allow it to be bound the the SDK container.**
## Static analysis

To run the static analysis checks:

It is highly recommended that you use all of these internally in your API to give you the ability to control the process.
```bash
composer run stan
```

### Building an SDK
## Code Style

To build an SDK, you can simply extend the SDK like so:
To run the code style fixer:

```php
use Demo\Resources\Server;
use JustSteveKing\HttpAuth\Strategies\NullStrategy;
use JustSteveKing\HttpSlim\HttpClient;
use JustSteveKing\PhpSdk\SDK;
use JustSteveKing\UriBuilder\Uri;
use PHPFox\Container\Container;

class MySDK extends SDK
{
public function __construct()
{
parent::__construct(
uri: Uri::fromString(
uri: 'https://www.domain.tld',
),
client: HttpClient::build(),
container: Container::getInstance(),
strategy: new NullStrategy()),
);
}
```bash
composer run pint
```

public static function boot(): MySDK
{
$client = new MySDK();
## Refactoring

$client->add(
name: TestResource::name(),
resource: TestResource::class,
);
To run the rector code refactoring:

return $client;
}
}
```bash
composer run refactor
```

## Testing
## Special Thanks

TO run the test:
Without the following packages and people, this framework would have been a lot harder to build.

```bash
composer run test
```
- [The PHP League - Object Mapper](https://github.com/thephpleague/object-mapper)
- [Ben Ramsey - Collection](https://github.com/ramsey/collection)
- [Larry Garfield - Serde](https://github.com/crell/serde)

## Credits

Expand All @@ -240,4 +181,4 @@ composer run test

## LICENSE

The MIT LIcense (MIT). Please see [License File](./LICENSE) for more information.
The MIT License (MIT). Please see [License File](./LICENSE) for more information.
Binary file removed banner.png
Binary file not shown.
Loading

0 comments on commit 81df125

Please sign in to comment.