From 355880b9c054da27c8b011174d043330a84bab67 Mon Sep 17 00:00:00 2001 From: Steve McDougall Date: Fri, 22 Dec 2023 14:35:54 +0000 Subject: [PATCH 1/4] Started working on the next release --- .gitignore | 1 + LICENSE | 2 +- README.md | 259 +++++++---------- banner.png | Bin 114480 -> 0 bytes composer.json | 96 +++++-- demo/Forge.php | 48 ---- demo/Resources/Post.php | 21 -- demo/Resources/Server.php | 41 --- examples/client.php | 32 --- examples/forge.php | 15 - phpstan.neon.dist | 9 + phpunit.xml.dist | 41 ++- pint.json | 81 ++++++ rector.php | 27 ++ src/Client.php | 84 ++++++ .../DataObjects/CanCreateInstances.php | 23 ++ src/Concerns/Resources/CanAccessClient.php | 19 ++ .../Resources/CanCreateDataObjects.php | 28 ++ src/Concerns/Resources/CanCreateRequests.php | 38 +++ src/Contracts/ClientContract.php | 40 +++ src/Contracts/DataObjectContract.php | 24 ++ src/Contracts/ResourceContract.php | 13 - src/Exceptions/ClientSetupException.php | 9 + src/Resources/AbstractResource.php | 270 ------------------ src/SDK.php | 125 -------- tests/Feature/ClientTest.php | 39 +++ tests/Fixtures/tests/list.json | 10 + tests/PackageTestCase.php | 73 +++++ tests/Pest.php | 45 --- tests/SDKTest.php | 86 ------ tests/Stubs/DataObjects/TestDataObject.php | 26 ++ tests/Stubs/DummyClass.php | 12 - tests/Stubs/MockSDK.php | 18 ++ tests/Stubs/ProjectResource.php | 19 -- tests/Stubs/RelationResource.php | 12 - tests/Stubs/Resources/ProjectResource.php | 16 ++ tests/Stubs/Resources/TestResource.php | 55 ++++ tests/Stubs/ToDoResource.php | 12 - tests/Unit/ClientTest.php | 61 ++++ 39 files changed, 867 insertions(+), 963 deletions(-) delete mode 100644 banner.png delete mode 100644 demo/Forge.php delete mode 100644 demo/Resources/Post.php delete mode 100644 demo/Resources/Server.php delete mode 100644 examples/client.php delete mode 100644 examples/forge.php create mode 100644 phpstan.neon.dist create mode 100644 pint.json create mode 100644 rector.php create mode 100644 src/Client.php create mode 100644 src/Concerns/DataObjects/CanCreateInstances.php create mode 100644 src/Concerns/Resources/CanAccessClient.php create mode 100644 src/Concerns/Resources/CanCreateDataObjects.php create mode 100644 src/Concerns/Resources/CanCreateRequests.php create mode 100644 src/Contracts/ClientContract.php create mode 100644 src/Contracts/DataObjectContract.php delete mode 100644 src/Contracts/ResourceContract.php create mode 100644 src/Exceptions/ClientSetupException.php delete mode 100644 src/Resources/AbstractResource.php delete mode 100644 src/SDK.php create mode 100644 tests/Feature/ClientTest.php create mode 100644 tests/Fixtures/tests/list.json create mode 100644 tests/PackageTestCase.php delete mode 100644 tests/Pest.php delete mode 100644 tests/SDKTest.php create mode 100644 tests/Stubs/DataObjects/TestDataObject.php delete mode 100644 tests/Stubs/DummyClass.php create mode 100644 tests/Stubs/MockSDK.php delete mode 100644 tests/Stubs/ProjectResource.php delete mode 100644 tests/Stubs/RelationResource.php create mode 100644 tests/Stubs/Resources/ProjectResource.php create mode 100644 tests/Stubs/Resources/TestResource.php delete mode 100644 tests/Stubs/ToDoResource.php create mode 100644 tests/Unit/ClientTest.php diff --git a/.gitignore b/.gitignore index fe26886..6e221fb 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ .idea/ .phpunit.result.cache composer.lock +/.phpunit.cache diff --git a/LICENSE b/LICENSE index ee6331d..2eba56c 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020 Steve McDougall +Copyright (c) 2023 Steve McDougall Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 5e930cc..d081acd 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,5 @@ # PHP SDK -

- -![](./banner.png) - -

- [![Latest Version][badge-release]][packagist] [![PHP Version][badge-php]][php] @@ -21,7 +15,7 @@ [downloads]: https://packagist.org/packages/juststeveking/php-sdk -A framework for building simple to use SDKs in PHP 8.0 and above. +A framework for building SDKs in PHP. ## Installation @@ -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 @@ -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. diff --git a/banner.png b/banner.png deleted file mode 100644 index f963ea5586ce60032a5e5bb722791a4202dfa3ed..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 114480 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 RD#9PbjiwvY*W3Q@{{b`YSfl^| diff --git a/composer.json b/composer.json index f0a4791..560a265 100644 --- a/composer.json +++ b/composer.json @@ -1,57 +1,95 @@ { "name": "juststeveking/php-sdk", - "description": "A base sdk for your PHP SDKs", + "description": "A framework for building SDKs in PHP.", + "type": "library", "keywords": [ - "http", - "sdk", - "psr18", - "psr-18" + "php","sdk","sdk-framework","psr-18" ], - "type": "library", "license": "MIT", "authors": [ { - "role": "developer", "name": "Steve McDougall", "email": "juststevemcd@gmail.com", - "homepage": "https://www.juststeveking.uk/" + "homepage": "https://www.juststeveking.uk/", + "role": "Developer" + } + ], + "support": { + "issues": "https://github.com/juststeveking/php-sdk/issues" + }, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/JustSteveKing" } ], "require": { - "php": "^8.0|^8.1", - "juststeveking/http-auth-strategies": "^v1.2", - "juststeveking/http-slim": "^2.0", - "juststeveking/uri-builder": "^2.0", - "phpfox/container": "^0.3.0" + "php": "^8.3", + "crell/serde": "^1.0", + "juststeveking/sdk-tools": "^0.0.5", + "league/object-mapper": "dev-main", + "nyholm/psr7": "^1.8", + "php-http/client-common": "^2.7", + "php-http/discovery": "^1.19", + "psr/http-client": "^1.0", + "psr/http-client-implementation": "*", + "psr/http-factory-implementation": "*", + "psr/http-message": "^2.0", + "ramsey/collection": "^2.0", + "symfony/http-client": "^7.0" }, "require-dev": { - "nyholm/psr7": "^1.4", - "pestphp/pest": "^1.21", - "symfony/http-client": "^v6.0", - "symfony/var-dumper": "^v6.0" + "laravel/pint": "^1.13", + "php-http/mock-client": "^1.6", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^10.5", + "rector/rector": "^0.18.13", + "roave/security-advisories": "dev-latest" + }, + "provide": { + "psr-discovery/http-client-implementations": "^1.0" + }, + "suggest": { + }, "autoload": { "psr-4": { - "JustSteveKing\\PhpSdk\\": "src/" + "JustSteveKing\\Sdk\\": "src/" } }, "autoload-dev": { "psr-4": { - "JustSteveKing\\PhpSdk\\Tests\\": "tests/", - "Demo\\": "demo/" + "JustSteveKing\\Sdk\\Tests\\": "tests/" } }, - "scripts": { - "test": "./vendor/bin/pest" - }, - "minimum-stability": "dev", + "minimum-stability": "stable", "prefer-stable": true, "config": { - "sort-packages": true, - "preferred-install": "dist", - "optimize-autoloader": true, "allow-plugins": { - "pestphp/pest-plugin": true - } + "php-http/discovery": true + }, + "optimize-autoloader": true, + "sort-packages": true, + "classmap-authoritative": true + }, + "scripts": { + "pint": [ + "./vendor/bin/pint" + ], + "refactor": [ + "./vendor/bin/rector --debug" + ], + "stan": [ + "./vendor/bin/phpstan analyse --memory-limit=3g" + ], + "test": [ + "./vendor/bin/phpunit --testdox" + ] + }, + "scripts-descriptions": { + "pint": "Run Laravel Pint on the codebase.", + "refactor": "Run Rector on the codebase.", + "stan": "Run PhpStan on the codebase.", + "test": "Run PhpUnit on the testsuite." } } diff --git a/demo/Forge.php b/demo/Forge.php deleted file mode 100644 index 556f345..0000000 --- a/demo/Forge.php +++ /dev/null @@ -1,48 +0,0 @@ -add( - name: Server::name(), - resource: Server::class, - ); - - return $client; - } -} diff --git a/demo/Resources/Post.php b/demo/Resources/Post.php deleted file mode 100644 index eb4f329..0000000 --- a/demo/Resources/Post.php +++ /dev/null @@ -1,21 +0,0 @@ -with( - with: ['databases'], - ); - - if (!is_null($identifier)) { - $this->load( - identifier: $identifier, - ); - } - - return $this; - } - - /** - * @return string - */ - public static function name(): string - { - return 'servers'; - } -} diff --git a/examples/client.php b/examples/client.php deleted file mode 100644 index edff79d..0000000 --- a/examples/client.php +++ /dev/null @@ -1,32 +0,0 @@ -add( - name: Post::name(), - resource: Post::class, -); - -$response = $sdk->posts->get(); // Http Response (psr-7) diff --git a/examples/forge.php b/examples/forge.php deleted file mode 100644 index c4b432d..0000000 --- a/examples/forge.php +++ /dev/null @@ -1,15 +0,0 @@ -servers->with(['databases'])->load('418503')->find("407126")->getBody()->getContents(); - -// Shortcut implementation -$databases = $forge->servers->databases('418503')->find("407126")->getBody()->getContents(); diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 0000000..a328a75 --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,9 @@ +parameters: + paths: + - src/ + + level: 9 + + checkGenericClassInNonGenericObjectType: false + + ignoreErrors: diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 7adcf61..a680ad0 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,29 +1,24 @@ - - - - ./src - - - - ./tests + + tests + + + + src + + diff --git a/pint.json b/pint.json new file mode 100644 index 0000000..4a66495 --- /dev/null +++ b/pint.json @@ -0,0 +1,81 @@ +{ + "preset": "per", + "rules": { + "align_multiline_comment": true, + "array_indentation": true, + "array_syntax": true, + "blank_line_after_namespace": true, + "blank_line_after_opening_tag": true, + "combine_consecutive_issets": true, + "combine_consecutive_unsets": true, + "concat_space": { + "spacing": "one" + }, + "declare_parentheses": true, + "declare_strict_types": true, + "explicit_string_variable": true, + "final_class": false, + "fully_qualified_strict_types": true, + "global_namespace_import": { + "import_classes": true, + "import_constants": true, + "import_functions": true + }, + "is_null": true, + "lambda_not_used_import": true, + "logical_operators": true, + "mb_str_functions": true, + "method_chaining_indentation": true, + "modernize_strpos": true, + "new_with_braces": true, + "no_empty_comment": true, + "not_operator_with_space": true, + "ordered_traits": true, + "protected_to_private": true, + "simplified_if_return": true, + "strict_comparison": true, + "ternary_to_null_coalescing": true, + "trim_array_spaces": true, + "use_arrow_functions": true, + "void_return": true, + "yoda_style": true, + "array_push": true, + "assign_null_coalescing_to_coalesce_equal": true, + "explicit_indirect_variable": true, + "method_argument_space": { + "on_multiline": "ensure_fully_multiline" + }, + "modernize_types_casting": true, + "no_superfluous_elseif": true, + "no_useless_else": true, + "nullable_type_declaration_for_default_null_value": true, + "ordered_imports": { + "sort_algorithm": "alpha" + }, + "ordered_class_elements": { + "order": [ + "use_trait", + "case", + "constant", + "constant_public", + "constant_protected", + "constant_private", + "property_public", + "property_protected", + "property_private", + "construct", + "destruct", + "magic", + "phpunit", + "method_abstract", + "method_public_static", + "method_public", + "method_protected_static", + "method_protected", + "method_private_static", + "method_private" + ], + "sort_algorithm": "none" + } + } +} diff --git a/rector.php b/rector.php new file mode 100644 index 0000000..5c49137 --- /dev/null +++ b/rector.php @@ -0,0 +1,27 @@ +paths([ + __DIR__ . '/src', + ]); + + $rectorConfig->rules([ + InlineConstructorDefaultToPropertyRector::class, + ]); + + $rectorConfig->sets([ + LevelSetList::UP_TO_PHP_83, + SetList::CODE_QUALITY, + SetList::DEAD_CODE, + SetList::EARLY_RETURN, + SetList::TYPE_DECLARATION, + SetList::PRIVATIZATION, + ]); +}; diff --git a/src/Client.php b/src/Client.php new file mode 100644 index 0000000..c1a5bbd --- /dev/null +++ b/src/Client.php @@ -0,0 +1,84 @@ + $plugins + */ + public function setup(array $plugins = []): ClientContract + { + $this->http = new PluginClient( + client: Psr18ClientDiscovery::find(), + plugins: $plugins, + ); + + return $this; + } + + /** + * Return the URL for the API. + */ + public function url(): string + { + return $this->url; + } + + /** + * Set the HTTP Client for the SDK. + */ + public function client(ClientInterface $client): ClientContract + { + $this->http = $client; + + return $this; + } + + /** + * Send an API Request. + * + * @throws ClientSetupException|ClientExceptionInterface + */ + public function send(RequestInterface $request): ResponseInterface + { + if ( ! $this->http instanceof ClientInterface) { + throw new ClientSetupException( + message: 'You have not setup the client correctly, you need to set the HTTP Client using `setup` or `client`.', + ); + } + + return $this->http->sendRequest( + request: $request, + ); + } +} diff --git a/src/Concerns/DataObjects/CanCreateInstances.php b/src/Concerns/DataObjects/CanCreateInstances.php new file mode 100644 index 0000000..d8af57d --- /dev/null +++ b/src/Concerns/DataObjects/CanCreateInstances.php @@ -0,0 +1,23 @@ + $class + * @param array $payload + */ + public static function create(string $class, array $payload): DataObjectContract + { + return (new ObjectMapperUsingReflection())->hydrateObject( + className: $class, + payload: $payload, + ); + } +} diff --git a/src/Concerns/Resources/CanAccessClient.php b/src/Concerns/Resources/CanAccessClient.php new file mode 100644 index 0000000..e1b504f --- /dev/null +++ b/src/Concerns/Resources/CanAccessClient.php @@ -0,0 +1,19 @@ + $class + */ + public function make(string $class, string $body): DataObjectContract + { + return $this->serializer->deserialize( + serialized: $body, + from: 'json', + to: $class, + ); + } +} diff --git a/src/Concerns/Resources/CanCreateRequests.php b/src/Concerns/Resources/CanCreateRequests.php new file mode 100644 index 0000000..8980bab --- /dev/null +++ b/src/Concerns/Resources/CanCreateRequests.php @@ -0,0 +1,38 @@ +createRequest( + method: $method->value, + uri: "{$this->client->url()}{$uri}", + ); + } + + /** + * Create a new Stream for sending data to the API using PSR-17 discovery. + */ + public function payload(string $payload): StreamInterface + { + return Psr17FactoryDiscovery::findStreamFactory()->createStream( + content: $payload, + ); + } +} diff --git a/src/Contracts/ClientContract.php b/src/Contracts/ClientContract.php new file mode 100644 index 0000000..da3b8e1 --- /dev/null +++ b/src/Contracts/ClientContract.php @@ -0,0 +1,40 @@ + $plugins + */ + public function setup(array $plugins = []): ClientContract; + /** + * Return the URL for the API. + */ + public function url(): string; + + /** + * Set the HTTP Client for the SDK Client. + */ + public function client(ClientInterface $client): ClientContract; + + /** + * Send an API Request. + * + * @throws ClientSetupException|ClientExceptionInterface + */ + public function send(RequestInterface $request): ResponseInterface; +} diff --git a/src/Contracts/DataObjectContract.php b/src/Contracts/DataObjectContract.php new file mode 100644 index 0000000..840e7b9 --- /dev/null +++ b/src/Contracts/DataObjectContract.php @@ -0,0 +1,24 @@ + $class + * @param array $payload + */ + public static function create(string $class, array $payload): DataObjectContract; + + /** + * Create a new DataObject. + * + * @param array $data + */ + public static function make(array $data): DataObjectContract; +} diff --git a/src/Contracts/ResourceContract.php b/src/Contracts/ResourceContract.php deleted file mode 100644 index 82db4e2..0000000 --- a/src/Contracts/ResourceContract.php +++ /dev/null @@ -1,13 +0,0 @@ -sdk; - } - - /** - * @return array - */ - public function getWith(): array - { - return $this->with; - } - - /** - * @param array $with - * @return $this - */ - public function with(array $with): self - { - if ($this->strictRelations) { - foreach ($with as $resource) { - if (! in_array($resource, $this->relations)) { - throw new RuntimeException( - message: "Cannot sideload {$resource} as it has not been registered as an available resource", - ); - } - } - } - - $this->with = $with; - - return $this; - } - - /** - * @return string|null - */ - public function getLoad(): string|null - { - return $this->load; - } - - /** - * @param string|int $identifier - * @return $this - */ - public function load(string|int $identifier): self - { - $this->load = (string) $identifier; - - return $this; - } - - /** - * @param Uri $uri - * - * @return $this - */ - public function uri(Uri $uri): self - { - $this->sdk()->uri = $uri; - - return $this; - } - - /** - * @param HttpClient $http - * - * @return $this - */ - public function client(HttpClient $http): self - { - $this->sdk()->client = $http; - - return $this; - } - - /** - * @param StrategyInterface $strategy - * - * @return $this - */ - public function strategy(StrategyInterface $strategy): self - { - $this->sdk()->strategy = $strategy; - - return $this; - } - - /** - * @return $this - */ - public function loadPath(): self - { - $this->sdk()->uri()->addPath( - path: $this->path, - ); - - return $this; - } - - /** - * @return ResponseInterface - * @throws \Psr\Http\Client\ClientExceptionInterface - * @throws \Throwable - */ - public function get(): ResponseInterface - { - return $this->loadPath()->sdk()->client()->get( - uri: $this->sdk()->uri()->toString(), - headers: $this->sdk()->strategy()->getHeader( - prefix: $this->authHeader, - ) - ); - } - - /** - * @param string|int $identifier - * @return ResponseInterface - * @throws \Psr\Http\Client\ClientExceptionInterface - * @throws \Throwable - */ - public function find(string|int $identifier): ResponseInterface - { - $this->loadPath()->sdk()->uri()->addPath( - path: "{$this->sdk()->uri()->path()}/{$identifier}", - ); - - if (! is_null($this->with)) { - $this->sdk()->uri()->addPath( - path: "{$this->sdk()->uri()->path()}/" . implode("/", $this->with), - ); - } - - if (! is_null($this->load)) { - $this->sdk()->uri()->addPath( - path: "{$this->sdk()->uri()->path()}/{$this->load}", - ); - } - - return $this->sdk()->client()->get( - uri: $this->sdk()->uri()->toString(), - headers: $this->sdk()->strategy()->getHeader( - prefix: $this->authHeader), - ); - } - - /** - * @param array $data - * @return ResponseInterface - * @throws \Psr\Http\Client\ClientExceptionInterface - * @throws \Throwable - */ - public function create(array $data): ResponseInterface - { - $this->loadPath(); - - if (! is_null($this->with)) { - $this->sdk()->uri()->addPath( - path: "{$this->sdk()->uri()->path()}/" . implode("/", $this->with), - ); - } - - return $this->sdk()->client()->post( - uri: $this->sdk()->uri()->toString(), - body: $data, - headers: $this->sdk()->strategy()->getHeader( - prefix: $this->authHeader, - ), - ); - } - - /** - * @param $identifier - * @param array $data - * @param string $method - * @return ResponseInterface - */ - public function update($identifier, array $data, string $method = 'patch'): ResponseInterface - { - $this->loadPath()->sdk()->uri()->addPath( - path: "{$this->sdk()->uri()->path()}/{$identifier}", - ); - - if (! is_null($this->with)) { - $this->sdk()->uri()->addPath( - path: "{$this->sdk()->uri()->path()}/" . implode("/", $this->with), - ); - } - - return $this->sdk()->client()->{$method}( - uri: $this->sdk()->uri()->toString(), - data: $data, - headers: $this->sdk()->strategy()->getHeader( - prefix: $this->authHeader, - ), - ); - } - - /** - * @param string|int $identifier - * @return ResponseInterface - * @throws \Psr\Http\Client\ClientExceptionInterface - * @throws \Throwable - */ - public function delete(string|int $identifier): ResponseInterface - { - $this->loadPath()->sdk()->uri()->addPath( - path: "{$this->sdk()->uri()->path()}/{$identifier}" - ); - - if (! is_null($this->with)) { - $this->sdk()->uri()->addPath( - path: "{$this->sdk()->uri()->path()}/" . implode("/", $this->with) - ); - } - - return $this->sdk()->client()->delete( - uri: $this->sdk()->uri()->toString(), - headers: $this->sdk()->strategy()->getHeader( - prefix: $this->authHeader, - ) - ); - } - - /** - * @param string $key - * @param mixed $value - * @return $this - */ - public function where(string $key, $value): self - { - $this->sdk()->uri()->addQueryParam( - key: $key, - value: $value - ); - - return $this; - } -} diff --git a/src/SDK.php b/src/SDK.php deleted file mode 100644 index 5a8eb96..0000000 --- a/src/SDK.php +++ /dev/null @@ -1,125 +0,0 @@ -implementsInterface(interface: ResourceContract::class) && - ! $reflection->isSubclassOf(class: AbstractResource::class) - ) { - throw new InvalidArgumentException( - message: "[$resource] needs to implement ResourceContract and extend the AbstractResource class.", - ); - } - } catch (Throwable) {} - - $this->container()->bind( - abstract: $name, - concrete: fn() => new $resource( - sdk: $this, - ), - ); - - return $this; - } - - /** - * @return Uri - */ - public function uri(): Uri - { - return $this->uri; - } - - /** - * @return HttpClient - */ - public function client(): HttpClient - { - return $this->client; - } - - /** - * @return StrategyInterface - */ - public function strategy(): StrategyInterface - { - return $this->strategy; - } - - /** - * @return Container - */ - public function container(): Container - { - return $this->container; - } - - /** - * @param string $name - * @return ResourceContract - * @throws BindingResolutionException|InvalidArgumentException - */ - public function __get(string $name): ResourceContract - { - if (! $this->container()->contains(abstract: $name)) { - throw new InvalidArgumentException( - message: "Resource [$name] has not been registered on the SDK.", - ); - } - - return $this->container()->make( - abstract: $name, - ); - } -} diff --git a/tests/Feature/ClientTest.php b/tests/Feature/ClientTest.php new file mode 100644 index 0000000..336d2b6 --- /dev/null +++ b/tests/Feature/ClientTest.php @@ -0,0 +1,39 @@ +createMockClient( + fixture: 'tests/list', + ); + + + $response = $client->tests()->list(); + + $this->assertInstanceOf( + expected: Collection::class, + actual: $response, + ); + + foreach ($response->toArray() as $engine) { + $this->assertInstanceOf( + expected: TestDataObject::class, + actual: $engine, + ); + } + } +} diff --git a/tests/Fixtures/tests/list.json b/tests/Fixtures/tests/list.json new file mode 100644 index 0000000..547291e --- /dev/null +++ b/tests/Fixtures/tests/list.json @@ -0,0 +1,10 @@ +[ + { + "id": "1234-1234-1234-1234", + "number": 1234 + }, + { + "id": "2345-2345-2345-2345", + "number": 2345 + } +] diff --git a/tests/PackageTestCase.php b/tests/PackageTestCase.php new file mode 100644 index 0000000..2efc967 --- /dev/null +++ b/tests/PackageTestCase.php @@ -0,0 +1,73 @@ +addResponse( + response: new Response( + status: 200, + headers: [], + body: json_encode( + value: $this->fixture( + path: $fixture, + ), + ), + ) + ); + + return (new MockSDK( + apiToken: '1234', + url: 'https://api.mock-server.com' + ))->client( + client: $mockClient, + ); + } + + /** + * @throws JsonException|InvalidArgumentException + */ + protected function fixture(string $path): array + { + $filename = __DIR__ . "/Fixtures/{$path}.json"; + + if ( ! file_exists(filename: $filename)) { + throw new InvalidArgumentException( + message: 'Failed to fetch fixture.', + ); + } + + return json_decode( + json: file_get_contents( + filename: $filename, + ), + associative: true, + flags: JSON_THROW_ON_ERROR, + ); + } +} diff --git a/tests/Pest.php b/tests/Pest.php deleted file mode 100644 index 5949c61..0000000 --- a/tests/Pest.php +++ /dev/null @@ -1,45 +0,0 @@ -in('Feature'); - -/* -|-------------------------------------------------------------------------- -| Expectations -|-------------------------------------------------------------------------- -| -| When you're writing tests, you often need to check that values meet certain conditions. The -| "expect()" function gives you access to a set of "expectations" methods that you can use -| to assert different things. Of course, you may extend the Expectation API at any time. -| -*/ - -expect()->extend('toBeOne', function () { - return $this->toBe(1); -}); - -/* -|-------------------------------------------------------------------------- -| Functions -|-------------------------------------------------------------------------- -| -| While Pest is very powerful out-of-the-box, you may have some testing code specific to your -| project that you don't want to repeat in every file. Here you can also expose helpers as -| global functions to help you to reduce the number of lines of code in your test files. -| -*/ - -function something() -{ - // .. -} diff --git a/tests/SDKTest.php b/tests/SDKTest.php deleted file mode 100644 index 5aebb15..0000000 --- a/tests/SDKTest.php +++ /dev/null @@ -1,86 +0,0 @@ -toBeInstanceOf(SDK::class); -}); - -it('can build an sdk instance using the constructor', function () { - expect( - new SDK( - uri: Uri::fromString('https://www.juststeveking.uk'), - client: HttpClient::build(), - container: Container::getInstance(), - strategy: new BasicStrategy( - authString: 'test', - ) - ) - )->toBeInstanceOf(SDK::class); -}); - -it('can access properties of the sdk', function () { - $sdk = SDK::build( - uri: 'https://www.juststeveking.uk' - ); - - expect( - $sdk->container() - )->toBeInstanceOf(Container::class); - - expect( - $sdk->strategy() - )->toBeInstanceOf(StrategyInterface::class); - - expect( - $sdk->uri() - )->toBeInstanceOf(Uri::class); - - expect( - $sdk->client() - )->toBeInstanceOf(HttpClient::class); -}); - -it('can add resources to the SDK', function () { - $sdk = SDK::build( - uri: 'https://www.juststeveking.uk', - ); - - $sdk->add( - name: ProjectResource::name(), - resource: ProjectResource::class - ); - - expect( - $sdk->container()->make( - abstract: ProjectResource::name(), - ), - )->toBeInstanceOf(ProjectResource::class); -}); - -it('can forward calls through to attached resource', function () { - $sdk = SDK::build( - uri: 'https://www.juststeveking.uk', - ); - - $sdk->add( - name: ProjectResource::name(), - resource: ProjectResource::class - ); - - expect( - $sdk->projects - )->toBeInstanceOf(ProjectResource::class); -}); diff --git a/tests/Stubs/DataObjects/TestDataObject.php b/tests/Stubs/DataObjects/TestDataObject.php new file mode 100644 index 0000000..6c2fe8a --- /dev/null +++ b/tests/Stubs/DataObjects/TestDataObject.php @@ -0,0 +1,26 @@ +request( + method: Method::GET, + uri: '/test', + ); + + 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: TestDataObject::class, + data: array_map( + callback: static fn(array $data): TestDataObject => TestDataObject::make( + data: $data, + ), + array: (array) json_decode( + json: $response->getBody()->getContents(), + associative: true, + flags: JSON_THROW_ON_ERROR, + ), + ), + )); + } +} diff --git a/tests/Stubs/ToDoResource.php b/tests/Stubs/ToDoResource.php deleted file mode 100644 index f0ac5ed..0000000 --- a/tests/Stubs/ToDoResource.php +++ /dev/null @@ -1,12 +0,0 @@ -assertInstanceOf( + expected: ClientContract::class, + actual: $this->newClient(), + ); + } + + #[Test] + public function it_can_setup_the_http_client(): void + { + $client = $this->newClient(); + + $this->assertNull( + actual: $client->http, + ); + + $client->setup(); + + $this->assertInstanceOf( + expected: PluginClient::class, + actual: $client->http, + ); + } + + #[Test] + public function it_can_set_a_new_http_client(): void + { + $client = $this->newClient()->setup(); + + $this->assertInstanceOf( + expected: PluginClient::class, + actual: $client->http, + ); + + $this->assertInstanceOf( + expected: MockClient::class, + actual: $client->client( + client: new MockClient(), + )->http, + ); + } +} From 2824e9c35e20e9fd3e28dd5f598c8f5b20775d07 Mon Sep 17 00:00:00 2001 From: Steve McDougall Date: Fri, 22 Dec 2023 14:43:41 +0000 Subject: [PATCH 2/4] Updating workflows, composer, and client --- .github/workflows/tests.yml | 12 ++++++------ composer.json | 21 ++++++++++----------- src/Client.php | 3 ++- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 938918c..2d016e6 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -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, json, libxml, mbstring, zip, http tools: composer:v2 coverage: none @@ -37,7 +37,7 @@ jobs: strategy: fail-fast: true matrix: - php: [ 8.0, 8.1 ] + php: [ 8.3 ] name: PHP ${{ matrix.php }} on Windows @@ -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, json, libxml, mbstring, zip, http tools: composer:v2 coverage: none ini-values: memory_limit=512M diff --git a/composer.json b/composer.json index 560a265..447bb51 100644 --- a/composer.json +++ b/composer.json @@ -25,13 +25,14 @@ ], "require": { "php": "^8.3", - "crell/serde": "^1.0", + "ext-json": "*", + "crell/serde": "^1.0.1", "juststeveking/sdk-tools": "^0.0.5", "league/object-mapper": "dev-main", - "nyholm/psr7": "^1.8", - "php-http/client-common": "^2.7", - "php-http/discovery": "^1.19", - "psr/http-client": "^1.0", + "nyholm/psr7": "^1.8.1", + "php-http/client-common": "^2.7.1", + "php-http/discovery": "^1.19.2", + "psr/http-client": "^1.0.3", "psr/http-client-implementation": "*", "psr/http-factory-implementation": "*", "psr/http-message": "^2.0", @@ -39,19 +40,17 @@ "symfony/http-client": "^7.0" }, "require-dev": { - "laravel/pint": "^1.13", + "laravel/pint": "^1.13.7", "php-http/mock-client": "^1.6", - "phpstan/phpstan": "^1.10", - "phpunit/phpunit": "^10.5", + "phpstan/phpstan": "^1.10.50", + "phpunit/phpunit": "^10.5.3", "rector/rector": "^0.18.13", "roave/security-advisories": "dev-latest" }, "provide": { "psr-discovery/http-client-implementations": "^1.0" }, - "suggest": { - - }, + "suggest": {}, "autoload": { "psr-4": { "JustSteveKing\\Sdk\\": "src/" diff --git a/src/Client.php b/src/Client.php index c1a5bbd..dbf03e0 100644 --- a/src/Client.php +++ b/src/Client.php @@ -27,7 +27,8 @@ abstract class Client implements ClientContract public function __construct( protected readonly string $apiToken, protected readonly string $url, - ) {} + ) { + } /** * Set up the client using PSR-18 discovery, passing in plugins. From 466fb0be7809a098a9b735194ab3c1f3fafd8132 Mon Sep 17 00:00:00 2001 From: Steve McDougall Date: Fri, 22 Dec 2023 17:21:17 +0000 Subject: [PATCH 3/4] Adding ext-fileinfo to composer --- composer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/composer.json b/composer.json index 447bb51..b3440f5 100644 --- a/composer.json +++ b/composer.json @@ -26,6 +26,7 @@ "require": { "php": "^8.3", "ext-json": "*", + "ext-fileinfo": "*", "crell/serde": "^1.0.1", "juststeveking/sdk-tools": "^0.0.5", "league/object-mapper": "dev-main", From 575760504bf4f02fd68192bd6825313bb2601baf Mon Sep 17 00:00:00 2001 From: Steve McDougall Date: Fri, 22 Dec 2023 17:21:47 +0000 Subject: [PATCH 4/4] Updating github action --- .github/workflows/tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 2d016e6..c8a32fb 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -22,7 +22,7 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} - extensions: dom, curl, json, libxml, mbstring, zip, http + extensions: dom, curl, fileinfo, json, libxml, mbstring, zip, http tools: composer:v2 coverage: none @@ -54,7 +54,7 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} - extensions: dom, curl, json, libxml, mbstring, zip, http + extensions: dom, curl, fileinfo, json, libxml, mbstring, zip, http tools: composer:v2 coverage: none ini-values: memory_limit=512M