Сustom methods support
This commit is contained in:
parent
f0a1adfb3d
commit
75fd10d40e
@ -1,12 +1,13 @@
|
||||
## Customization
|
||||
|
||||
* [Using different PSR-18, PSR-17 and PSR-7 implementations](different_psr_implementations.md)
|
||||
* [Controlling HTTP abstraction layer](different_psr_implementations.md)
|
||||
* [Customizing request and response processing](pipelines/implementing_a_handler.md)
|
||||
+ [Using a predefined handler](pipelines/using_a_predefined_handler.md)
|
||||
+ [Built-in handlers](pipelines/using_a_predefined_handler.md#built-in-handlers)
|
||||
+ [Modifying the default pipeline](pipelines/using_a_predefined_handler.md#modifying-the-default-pipeline)
|
||||
+ [Constructing the pipeline from scratch](pipelines/using_a_predefined_handler.md#constructing-the-pipeline-from-scratch)
|
||||
+ [Implementing a handler](pipelines/implementing_a_handler.md)
|
||||
* [Implementing custom API methods](implementing_custom_api_methods.md)
|
||||
|
||||
Both `ClientFactory` and `ClientBuilder` provide the necessary functionality to replace PSR dependencies with any other compatible implementation.
|
||||
By default, those dependencies will be detected via service discovery. But service discovery supports a limited amount of implementation.
|
||||
|
@ -0,0 +1,28 @@
|
||||
# Custom API methods with DTO
|
||||
|
||||
This example demonstrates how you can use your custom serializer with custom DTOs to implement API methods.
|
||||
|
||||
## How to run the project
|
||||
|
||||
1. Open `app.php` and change credentials and the site to your data.
|
||||
2. Run these commands:
|
||||
```sh
|
||||
composer install
|
||||
php app.php
|
||||
```
|
||||
|
||||
You will see something like this:
|
||||
```sh
|
||||
Created customer using custom methods. ID: 5633
|
||||
```
|
||||
|
||||
This means that the project works as expected.
|
||||
|
||||
## Navigation
|
||||
|
||||
- [`app.php`](app.php) - entrypoint, calls the custom method and outputs the response data.
|
||||
- [`src/Component/Adapter/SymfonyToLiipAdapter.php`](src/Component/Adapter/SymfonyToLiipAdapter.php) - adapter for using `symfony/serializer` inside `FormEncoder` component.
|
||||
- [`src/Component/CustomApiMethod.php`](src/Component/CustomApiMethod.php) - `CustomApiMethod` that uses `SerializerInterface` from `liip/serializer` and `FormEncoder`. This component will handle marshaling.
|
||||
- [`src/Dto`](src/Dto) - data models used in the project.
|
||||
- [`src/Factory/SerializerFactory.php`](src/Factory/SerializerFactory.php) - builds `symfony/serializer`'s serializer and wraps it into the `SymfonyToLiipAdapter`.
|
||||
- [`src/Factory/ClientFactory.php`](src/Factory/ClientFactory.php) - custom client factory that register the custom API method.
|
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
use RetailCrm\Api\Builder\FormEncoderBuilder;
|
||||
use RetailCrm\Examples\CustomMethodsDto\Dto\Customer;
|
||||
use RetailCrm\Examples\CustomMethodsDto\Dto\Request\CustomersCreateRequest;
|
||||
use RetailCrm\Examples\CustomMethodsDto\Factory\ClientFactory;
|
||||
use RetailCrm\Examples\CustomMethodsDto\Factory\SerializerFactory;
|
||||
|
||||
require __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
// Three lines below will be usually called during DI container building or hidden by other means.
|
||||
$serializer = SerializerFactory::create();
|
||||
$encoder = (new FormEncoderBuilder())->setSerializer($serializer)->build();
|
||||
$clientFactory = (new ClientFactory())->setCustomEncoder($encoder);
|
||||
|
||||
// Replace API URL and API key with your data.
|
||||
$client = $clientFactory->createClient('https://test.simla.com', 'apiKey');
|
||||
|
||||
$request = new CustomersCreateRequest();
|
||||
$request->customer = new Customer();
|
||||
$request->customer->firstName = 'Tester';
|
||||
$request->customer->lastName = 'User';
|
||||
$request->customer->patronymic = 'Patronymic';
|
||||
$request->site = 'site'; // Replace site with your data.
|
||||
|
||||
/** @var \Retailcrm\Examples\CustomMethodsDto\Dto\Response\CustomersCreateResponse $response */
|
||||
$response = $client->customMethods->createCustomer($request);
|
||||
|
||||
echo 'Created customer using custom methods. ID: ' . $response->id;
|
@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "retailcrm/custom-api-methods-with-dto-example",
|
||||
"description": "This project demonstrates DTO usage with the custom methods.",
|
||||
"type": "project",
|
||||
"require": {
|
||||
"retailcrm/api-client-php": "^6",
|
||||
"symfony/serializer": "^5.3",
|
||||
"symfony/property-access": "^5.3"
|
||||
},
|
||||
"license": "MIT",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"RetailCrm\\Examples\\CustomMethodsDto\\": "src/"
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.3
|
||||
*
|
||||
* @category SymfonyToLiipAdapter
|
||||
* @package Retailcrm\Examples\CustomMethodsDto\Component\Adapter
|
||||
*/
|
||||
|
||||
namespace Retailcrm\Examples\CustomMethodsDto\Component\Adapter;
|
||||
|
||||
use Liip\Serializer\Context;
|
||||
use Liip\Serializer\SerializerInterface;
|
||||
use Symfony\Component\Serializer\Serializer as SymfonySerializer;
|
||||
|
||||
/**
|
||||
* Class SymfonyToLiipAdapter
|
||||
*
|
||||
* @category SymfonyToLiipAdapter
|
||||
* @package Retailcrm\Examples\CustomMethodsDto\Component\Adapter
|
||||
*/
|
||||
class SymfonyToLiipAdapter implements SerializerInterface
|
||||
{
|
||||
/** @var SymfonySerializer */
|
||||
private $serializer;
|
||||
|
||||
public function __construct(SymfonySerializer $serializer)
|
||||
{
|
||||
$this->serializer = $serializer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function serialize($data, string $format, Context $context = null): string
|
||||
{
|
||||
return $this->serializer->serialize($data, $format);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function deserialize(string $data, string $type, string $format, Context $context = null)
|
||||
{
|
||||
return $this->serializer->deserialize($data, $type, $format);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @throws \Symfony\Component\Serializer\Exception\ExceptionInterface
|
||||
*/
|
||||
public function toArray($data, Context $context = null): array
|
||||
{
|
||||
return $this->serializer->normalize($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @throws \Symfony\Component\Serializer\Exception\ExceptionInterface
|
||||
*/
|
||||
public function fromArray(array $data, string $type, Context $context = null)
|
||||
{
|
||||
return $this->serializer->denormalize($data, $type);
|
||||
}
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.3
|
||||
*
|
||||
* @category CustomApiMethod
|
||||
* @package Retailcrm\Examples\CustomMethodsDto\Component
|
||||
*/
|
||||
|
||||
namespace Retailcrm\Examples\CustomMethodsDto\Component;
|
||||
|
||||
use RetailCrm\Api\Exception\Client\HandlerException;
|
||||
use RetailCrm\Api\Interfaces\FormEncoderInterface;
|
||||
use RetailCrm\Api\Interfaces\RequestSenderInterface;
|
||||
|
||||
/**
|
||||
* Class CustomApiMethod
|
||||
*
|
||||
* @category CustomApiMethod
|
||||
* @package Retailcrm\Examples\CustomMethodsDto\Component
|
||||
*/
|
||||
class CustomApiMethod extends \RetailCrm\Api\Component\CustomApiMethod
|
||||
{
|
||||
/** @var string */
|
||||
private $responseFqn;
|
||||
|
||||
/** @var FormEncoderInterface */
|
||||
private $encoder;
|
||||
|
||||
public function __construct(string $method, string $route, string $responseFqn, FormEncoderInterface $encoder)
|
||||
{
|
||||
parent::__construct($method, $route);
|
||||
|
||||
$this->responseFqn = $responseFqn;
|
||||
$this->encoder = $encoder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the request, returns the response.
|
||||
*
|
||||
* @param \RetailCrm\Api\Interfaces\RequestSenderInterface $sender
|
||||
* @param array<int|string, mixed>|object $data
|
||||
*
|
||||
* @return object
|
||||
* @throws \RetailCrm\Api\Exception\ApiException
|
||||
* @throws \RetailCrm\Api\Exception\ClientException
|
||||
* @throws \RetailCrm\Api\Exception\Client\HandlerException
|
||||
* @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface
|
||||
*/
|
||||
public function __invoke(RequestSenderInterface $sender, $data = [])
|
||||
{
|
||||
if (is_object($data)) {
|
||||
$data = $this->encoder->encodeArray($data);
|
||||
}
|
||||
|
||||
$result = parent::__invoke($sender, $data);
|
||||
|
||||
try {
|
||||
return $this->encoder->getSerializer()->fromArray($result, $this->responseFqn);
|
||||
} catch (\Throwable $throwable) {
|
||||
throw new HandlerException(
|
||||
'Cannot deserialize body: ' . $throwable->getMessage(),
|
||||
0,
|
||||
$throwable
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.3
|
||||
*
|
||||
* @category Customer
|
||||
* @package Retailcrm\Examples\CustomMethodsDto\Dto
|
||||
*/
|
||||
|
||||
namespace Retailcrm\Examples\CustomMethodsDto\Dto;
|
||||
|
||||
/**
|
||||
* Class Customer
|
||||
*
|
||||
* @category Customer
|
||||
* @package Retailcrm\Examples\CustomMethodsDto\Dto
|
||||
*/
|
||||
class Customer
|
||||
{
|
||||
/** @var string */
|
||||
public $firstName;
|
||||
|
||||
/** @var string */
|
||||
public $lastName;
|
||||
|
||||
/** @var string */
|
||||
public $patronymic;
|
||||
|
||||
/** @var string */
|
||||
public $email;
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.3
|
||||
*
|
||||
* @category CustomersCreateRequest
|
||||
* @package Retailcrm\Examples\CustomMethodsDto\Dto\Request
|
||||
*/
|
||||
|
||||
namespace Retailcrm\Examples\CustomMethodsDto\Dto\Request;
|
||||
|
||||
use RetailCrm\Api\Component\FormData\Mapping as Form;
|
||||
|
||||
/**
|
||||
* Class CustomersCreateRequest
|
||||
*
|
||||
* @category CustomersCreateRequest
|
||||
* @package Retailcrm\Examples\CustomMethodsDto\Dto\Request
|
||||
*/
|
||||
class CustomersCreateRequest
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @Form\Type("string")
|
||||
* @Form\SerializedName("site")
|
||||
*/
|
||||
public $site;
|
||||
|
||||
/**
|
||||
* @var \Retailcrm\Examples\CustomMethodsDto\Dto\Customer
|
||||
*
|
||||
* @Form\Type("Retailcrm\Examples\CustomMethodsDto\Dto\Customer")
|
||||
* @Form\SerializedName("customer")
|
||||
* @Form\JsonField()
|
||||
*/
|
||||
public $customer;
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.3
|
||||
*
|
||||
* @category CustomersCreateResponse
|
||||
* @package Retailcrm\Examples\CustomMethodsDto\Dto\Response
|
||||
*/
|
||||
|
||||
namespace Retailcrm\Examples\CustomMethodsDto\Dto\Response;
|
||||
|
||||
/**
|
||||
* Class CustomersCreateResponse
|
||||
*
|
||||
* @category CustomersCreateResponse
|
||||
* @package Retailcrm\Examples\CustomMethodsDto\Dto\Response
|
||||
*/
|
||||
class CustomersCreateResponse
|
||||
{
|
||||
/** @var int */
|
||||
public $id;
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.3
|
||||
*
|
||||
* @category ClientFactory
|
||||
* @package RetailCrm\Examples\CustomMethodsDto\Factory
|
||||
*/
|
||||
|
||||
namespace RetailCrm\Examples\CustomMethodsDto\Factory;
|
||||
|
||||
use RetailCrm\Api\Client;
|
||||
use RetailCrm\Api\Enum\RequestMethod;
|
||||
use RetailCrm\Api\Factory\ClientFactory as Base;
|
||||
use RetailCrm\Api\Interfaces\FormEncoderInterface;
|
||||
use RetailCrm\Examples\CustomMethodsDto\Component\CustomApiMethod;
|
||||
use RetailCrm\Examples\CustomMethodsDto\Dto\Response\CustomersCreateResponse;
|
||||
|
||||
/**
|
||||
* Class ClientFactory
|
||||
*
|
||||
* @category ClientFactory
|
||||
* @package RetailCrm\Examples\CustomMethodsDto\Factory
|
||||
*/
|
||||
class ClientFactory extends Base
|
||||
{
|
||||
/** @var \RetailCrm\Api\Interfaces\FormEncoderInterface */
|
||||
private $customEncoder;
|
||||
|
||||
public function setCustomEncoder(FormEncoderInterface $customEncoder): ClientFactory
|
||||
{
|
||||
$this->customEncoder = $customEncoder;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function createClient(string $apiUrl, string $apiKey): Client
|
||||
{
|
||||
$client = parent::createClient($apiUrl, $apiKey);
|
||||
$client->customMethods->register(
|
||||
'createCustomer',
|
||||
$this->method(
|
||||
RequestMethod::POST,
|
||||
'customers/create',
|
||||
CustomersCreateResponse::class
|
||||
)
|
||||
);
|
||||
|
||||
return $client;
|
||||
}
|
||||
|
||||
private function method(string $method, string $route, string $responseFqn): CustomApiMethod
|
||||
{
|
||||
return new CustomApiMethod($method, $route, $responseFqn, $this->customEncoder);
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.3
|
||||
*
|
||||
* @category SerializerFactory
|
||||
* @package Retailcrm\Examples\CustomMethodsDto\Factory
|
||||
*/
|
||||
|
||||
namespace Retailcrm\Examples\CustomMethodsDto\Factory;
|
||||
|
||||
use Liip\Serializer\SerializerInterface;
|
||||
use RetailCrm\Examples\CustomMethodsDto\Component\Adapter\SymfonyToLiipAdapter;
|
||||
use Symfony\Component\Serializer\Serializer;
|
||||
use Symfony\Component\Serializer\Encoder\JsonEncoder;
|
||||
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
|
||||
|
||||
/**
|
||||
* Class SerializerFactory
|
||||
*
|
||||
* @category SerializerFactory
|
||||
* @package Retailcrm\Examples\CustomMethodsDto\Factory
|
||||
*/
|
||||
class SerializerFactory
|
||||
{
|
||||
public static function create(): SerializerInterface
|
||||
{
|
||||
return new SymfonyToLiipAdapter(new Serializer([new ObjectNormalizer()], [new JsonEncoder()]));
|
||||
}
|
||||
}
|
106
doc/customization/implementing_custom_api_methods.md
Normal file
106
doc/customization/implementing_custom_api_methods.md
Normal file
@ -0,0 +1,106 @@
|
||||
## Implementing custom API methods
|
||||
|
||||
You can use this feature if you need to group multiple API calls, or return something custom - not the API response as-is,
|
||||
or, for example, to use new API methods without waiting for the implementation. For all of those cases, we provide a special
|
||||
resource group in the client that will make it easy to implement a custom API method.
|
||||
|
||||
The main limitation of this mechanism is the lack of DTO. You will be limited to arrays inside custom methods. It is
|
||||
_possible_ to use custom DTO's for the custom methods, but it is a little tricky - check the bottom of this page for the example.
|
||||
|
||||
Let's imagine that we have a special method with this route: `/api/v5/dialogs`. This method returns a list of the dialogs present in the system.
|
||||
We cannot use it directly because it is not implemented in the client. However, we can implement it by ourselves using custom methods.
|
||||
It will look like this:
|
||||
|
||||
```php
|
||||
use RetailCrm\Api\Component\CustomApiMethod;
|
||||
use RetailCrm\Api\Enum\RequestMethod;
|
||||
use RetailCrm\Api\Factory\SimpleClientFactory;
|
||||
use RetailCrm\Api\Interfaces\ApiExceptionInterface;
|
||||
|
||||
$client = SimpleClientFactory::createClient('https://test.simla.io', 'key');
|
||||
$client->customMethods->register('dialogs', new CustomApiMethod(RequestMethod::GET, 'dialogs'));
|
||||
|
||||
try {
|
||||
$dialogs = $client->customMethods->call('dialogs');
|
||||
} catch (ApiExceptionInterface $exception) {
|
||||
echo sprintf(
|
||||
'Error from RetailCRM API (status code: %d): %s',
|
||||
$exception->getStatusCode(),
|
||||
$exception->getMessage()
|
||||
);
|
||||
|
||||
if (count($exception->getErrorResponse()->errors) > 0) {
|
||||
echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
echo 'Dialogs: ' . print_r($dialogs['dialogs'], true);
|
||||
```
|
||||
|
||||
First, we need to register the custom method in the client. To do this we must give our method a name and an implementation.
|
||||
The name will be used to call the method later using `call()` and the implementation is just a callable with the three
|
||||
parameters: `RequestSenderInterface`, data array, and the context. Let's take a look at all three.
|
||||
|
||||
1. `RequestSenderInterface` is implemented by the `RequestSender` and contains three methods: `send`, `route` and `host`.
|
||||
`send` is used to send the request, `route` will append the base URL to your method name, and the latter, `host`, will return
|
||||
hostname from the base URL.
|
||||
2. The data array contains any data that has been provided to the callable during the method call. It will be encoded as
|
||||
a query string and stored either as the URL params or as the form body (query string is used for GET and DELETE requests).
|
||||
3. The context contains any information provided to the callable during the method call. It is not checked by the client.
|
||||
However, it is always must be of array type.
|
||||
|
||||
As you can see, we're using `CustomApiMethod` as a callable in the example. The `CustomApiMethod` is a simple invokable
|
||||
wrapper that can be used to simplify the registration of the new methods. The boilerplate code in the previous example is
|
||||
already implemented inside the `CustomApiMethod` component.
|
||||
|
||||
It is easier to understand how the callable should work and how the `CustomApiMethod` works by looking at the registration example.
|
||||
It works exactly like the previous example, but without the `CustomApiMethod` component:
|
||||
|
||||
```php
|
||||
use RetailCrm\Api\Enum\RequestMethod;
|
||||
use RetailCrm\Api\Interfaces\RequestSenderInterface;
|
||||
|
||||
$client->customMethods->register('dialogs', function (RequestSenderInterface $sender, $data, array $context) {
|
||||
return $sender->send(RequestMethod::GET, $sender->route('dialogs'), $data);
|
||||
});
|
||||
```
|
||||
|
||||
The data here is not defined as array because it can be anything you like. You can use any serializer to serialize the data
|
||||
and deserialize the response. `CustomApiMethod` only supports array out-of-box, but your handlers can utilize any
|
||||
data types.
|
||||
|
||||
The base URL inside the client is always represented as a URL with the version suffix. It should be always kept in mind
|
||||
while using the `route` method:
|
||||
|
||||
```php
|
||||
// This code is being executed inside the custom method callable.
|
||||
// Base URL is http://test.simla.io/api/v5
|
||||
|
||||
$settingsRoute = $sender->route('settings');
|
||||
$host = $sender->host();
|
||||
|
||||
echo $settingsRoute; // prints "https://test.simla.io/api/v5/settings" - the slash is inserted by the route() method.
|
||||
echo $host; // prints "test.simla.io"
|
||||
```
|
||||
|
||||
Any registered custom method can be called using the `__call` magic method:
|
||||
|
||||
```php
|
||||
// Both calls below works the same.
|
||||
|
||||
$client->customMethods->call('dialogs', ['param' => 'value'], ['contextParam' => 'contextValue']);
|
||||
$client->customMethods->dialogs(['param' => 'value'], ['contextParam' => 'contextValue']);
|
||||
```
|
||||
|
||||
The last notable difference from the regular methods lies inside events. You won't be able to get request or response models
|
||||
from the events, but you can extract the response array using the `getResponseArray()` method.
|
||||
|
||||
## Using DTOs with the custom methods
|
||||
|
||||
You can use DTOs with the custom methods. To do that you just need to serialize the request models inside the method callback
|
||||
before sending the request and deserialize the response before returning it. We already made a great example project that
|
||||
demonstrates the DTO usage alongside custom methods.
|
||||
|
||||
[**DTOs with the custom methods - sample project**](examples/custom-api-methods-with-dto)
|
@ -26,4 +26,6 @@
|
||||
+ [Modifying the default pipeline](customization/pipelines/using_a_predefined_handler.md#modifying-the-default-pipeline)
|
||||
+ [Constructing the pipeline from scratch](customization/pipelines/using_a_predefined_handler.md#constructing-the-pipeline-from-scratch)
|
||||
+ [Implementing a handler](customization/pipelines/implementing_a_handler.md)
|
||||
+ [Implementing custom API methods](customization/implementing_custom_api_methods.md)
|
||||
* [Troubleshooting](troubleshooting.md)
|
||||
* [PHPDoc](https://retailcrm.github.io/api-client-php/)
|
||||
|
@ -11,6 +11,7 @@ namespace RetailCrm\Api\Builder;
|
||||
|
||||
use Doctrine\Common\Annotations\AnnotationReader;
|
||||
use Doctrine\Common\Annotations\PsrCachedReader;
|
||||
use Liip\Serializer\SerializerInterface;
|
||||
use Psr\Cache\CacheItemPoolInterface;
|
||||
use RetailCrm\Api\Component\FormData\FormEncoder;
|
||||
use RetailCrm\Api\Factory\SerializerFactory;
|
||||
@ -36,6 +37,9 @@ class FormEncoderBuilder implements BuilderInterface
|
||||
/** @var \RetailCrm\Api\Builder\FilesystemCacheBuilder */
|
||||
private $fsCacheBuilder;
|
||||
|
||||
/** @var \Liip\Serializer\SerializerInterface */
|
||||
private $serializer;
|
||||
|
||||
/**
|
||||
* FormEncoderBuilder constructor.
|
||||
*/
|
||||
@ -76,6 +80,21 @@ class FormEncoderBuilder implements BuilderInterface
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets serializer implementation.
|
||||
*
|
||||
* This serializer implementation will be used by FormEncoder component.
|
||||
*
|
||||
* @param \Liip\Serializer\SerializerInterface $serializer
|
||||
*
|
||||
* @return FormEncoderBuilder
|
||||
*/
|
||||
public function setSerializer(SerializerInterface $serializer): FormEncoderBuilder
|
||||
{
|
||||
$this->serializer = $serializer;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds FormEncoder.
|
||||
*
|
||||
@ -88,10 +107,9 @@ class FormEncoderBuilder implements BuilderInterface
|
||||
{
|
||||
$this->buildCache();
|
||||
$this->buildAnnotationReader();
|
||||
$this->buildSerializer();
|
||||
|
||||
$serializer = SerializerFactory::create();
|
||||
|
||||
return new FormEncoder($serializer, $this->annotationReader);
|
||||
return new FormEncoder($this->serializer, $this->annotationReader);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -117,4 +135,14 @@ class FormEncoderBuilder implements BuilderInterface
|
||||
$this->annotationReader = new PsrCachedReader(new AnnotationReader(), $this->cache);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds serializer.
|
||||
*/
|
||||
private function buildSerializer(): void
|
||||
{
|
||||
if (null === $this->serializer) {
|
||||
$this->serializer = SerializerFactory::create();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ use RetailCrm\Api\ResourceGroup\Costs;
|
||||
use RetailCrm\Api\ResourceGroup\Customers;
|
||||
use RetailCrm\Api\ResourceGroup\CustomersCorporate;
|
||||
use RetailCrm\Api\ResourceGroup\CustomFields;
|
||||
use RetailCrm\Api\ResourceGroup\CustomMethods;
|
||||
use RetailCrm\Api\ResourceGroup\Delivery;
|
||||
use RetailCrm\Api\ResourceGroup\Files;
|
||||
use RetailCrm\Api\ResourceGroup\Integration;
|
||||
@ -117,6 +118,9 @@ class Client
|
||||
/** @var \RetailCrm\Api\ResourceGroup\Statistics */
|
||||
public $statistics;
|
||||
|
||||
/** @var \RetailCrm\Api\ResourceGroup\CustomMethods */
|
||||
public $customMethods;
|
||||
|
||||
/** @var StreamFactoryInterface */
|
||||
private $streamFactory;
|
||||
|
||||
@ -315,6 +319,14 @@ class Client
|
||||
$eventDispatcher,
|
||||
$logger
|
||||
);
|
||||
$this->customMethods = new CustomMethods(
|
||||
$url,
|
||||
$httpClient,
|
||||
$requestTransformer,
|
||||
$responseTransformer,
|
||||
$eventDispatcher,
|
||||
$logger
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
80
src/Component/CustomApiMethod.php
Normal file
80
src/Component/CustomApiMethod.php
Normal file
@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.3
|
||||
*
|
||||
* @category CustomApiMethod
|
||||
* @package RetailCrm\Api\Component
|
||||
*/
|
||||
|
||||
namespace RetailCrm\Api\Component;
|
||||
|
||||
use RetailCrm\Api\Exception\Client\HandlerException;
|
||||
use RetailCrm\Api\Interfaces\RequestSenderInterface;
|
||||
|
||||
/**
|
||||
* Class CustomApiMethod
|
||||
*
|
||||
* This class can be used to implement custom methods without any hassle. It is useful if you don't want to
|
||||
* do anything besides sending the request and reading the response.
|
||||
*
|
||||
* @see \RetailCrm\Api\ResourceGroup\CustomMethods::register() for the usage example.
|
||||
*
|
||||
* @category CustomApiMethod
|
||||
* @package RetailCrm\Api\Component
|
||||
*/
|
||||
class CustomApiMethod
|
||||
{
|
||||
/** @var string */
|
||||
protected $method;
|
||||
|
||||
/** @var string */
|
||||
protected $route;
|
||||
|
||||
/** @var bool */
|
||||
protected $rawRouteUri;
|
||||
|
||||
/**
|
||||
* Instantiates new instance of the CustomApiMethod.
|
||||
*
|
||||
* @param string $method
|
||||
* @param string $route
|
||||
*/
|
||||
public function __construct(string $method, string $route)
|
||||
{
|
||||
$this->method = $method;
|
||||
$this->route = $route;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use provided route as if it was full URL.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function useRouteAsUri(): self
|
||||
{
|
||||
$this->rawRouteUri = true;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the request, returns the response.
|
||||
*
|
||||
* @param \RetailCrm\Api\Interfaces\RequestSenderInterface $sender
|
||||
* @param array<int|string, mixed>|object $data
|
||||
*
|
||||
* @return array<int|string, mixed>|mixed
|
||||
* @throws \RetailCrm\Api\Exception\ApiException
|
||||
* @throws \RetailCrm\Api\Exception\ClientException
|
||||
* @throws \RetailCrm\Api\Exception\Client\HandlerException
|
||||
* @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface
|
||||
*/
|
||||
public function __invoke(RequestSenderInterface $sender, $data = [])
|
||||
{
|
||||
if (!is_array($data)) {
|
||||
throw new HandlerException(__CLASS__ . ' only supports array data');
|
||||
}
|
||||
|
||||
return $sender->send($this->method, $this->rawRouteUri ? $this->route : $sender->route($this->route), $data);
|
||||
}
|
||||
}
|
106
src/Component/RequestSender.php
Normal file
106
src/Component/RequestSender.php
Normal file
@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.3
|
||||
*
|
||||
* @category RequestSender
|
||||
* @package RetailCrm\Api\Component
|
||||
*/
|
||||
|
||||
namespace RetailCrm\Api\Component;
|
||||
|
||||
use Psr\Http\Client\ClientExceptionInterface;
|
||||
use Psr\Http\Client\NetworkExceptionInterface;
|
||||
use RetailCrm\Api\Interfaces\RequestSenderInterface;
|
||||
use RetailCrm\Api\ResourceGroup\AbstractApiResourceGroup;
|
||||
use RetailCrm\Api\Traits\BaseUrlAwareTrait;
|
||||
|
||||
/**
|
||||
* Class RequestSender
|
||||
*
|
||||
* @category RequestSender
|
||||
* @package RetailCrm\Api\Component
|
||||
*/
|
||||
class RequestSender extends AbstractApiResourceGroup implements RequestSenderInterface
|
||||
{
|
||||
use BaseUrlAwareTrait {
|
||||
route as private makeRoute;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \RetailCrm\Api\ResourceGroup\AbstractApiResourceGroup $resourceGroup
|
||||
*/
|
||||
public function __construct(AbstractApiResourceGroup $resourceGroup)
|
||||
{
|
||||
parent::__construct(
|
||||
$resourceGroup->baseUrl,
|
||||
$resourceGroup->httpClient,
|
||||
$resourceGroup->requestTransformer,
|
||||
$resourceGroup->responseTransformer,
|
||||
$resourceGroup->eventDispatcher,
|
||||
$resourceGroup->logger
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends custom request to provided route with provided method and body, returns array response.
|
||||
* Request will be put into GET parameters or into POST form-data (depends on method).
|
||||
*
|
||||
* Note: do not remove "useless" exceptions which are marked as "never thrown" by IDE.
|
||||
* PSR-18's ClientInterface doesn't have them in the DocBlock, but, according to PSR-18,
|
||||
* they can be thrown by clients, and therefore should be present here.
|
||||
*
|
||||
* @see https://www.php-fig.org/psr/psr-18/#error-handling
|
||||
*
|
||||
* @param string $method
|
||||
* @param string $route
|
||||
* @param array<int|string, mixed> $requestForm
|
||||
*
|
||||
* @return array<int|string, mixed>
|
||||
* @throws \RetailCrm\Api\Exception\ApiException
|
||||
* @throws \RetailCrm\Api\Exception\ClientException
|
||||
* @throws \RetailCrm\Api\Exception\Client\HandlerException
|
||||
* @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface
|
||||
* @SuppressWarnings(PHPMD.ElseExpression)
|
||||
*/
|
||||
public function send(
|
||||
string $method,
|
||||
string $route,
|
||||
array $requestForm = []
|
||||
): array {
|
||||
$method = strtoupper($method);
|
||||
$psrRequest = $this->requestTransformer->createCustomPsrRequest($method, $route, $requestForm);
|
||||
|
||||
$this->logPsr7Request($psrRequest);
|
||||
|
||||
try {
|
||||
$psrResponse = $this->httpClient->sendRequest($psrRequest);
|
||||
} catch (ClientExceptionInterface | NetworkExceptionInterface $exception) {
|
||||
$this->processPsr18Exception($psrRequest, $exception);
|
||||
}
|
||||
|
||||
if (isset($psrResponse)) {
|
||||
$this->logPsr7Response($psrResponse);
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $this->responseTransformer->createCustomResponse($this->baseUrl, $psrRequest, $psrResponse);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function route(string $route): string
|
||||
{
|
||||
return $this->makeRoute($route);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function host(): string
|
||||
{
|
||||
return (string) parse_url($this->baseUrl, PHP_URL_HOST);
|
||||
}
|
||||
}
|
@ -49,7 +49,7 @@ class RequestTransformer implements RequestTransformerInterface
|
||||
* @param \RetailCrm\Api\Interfaces\RequestInterface|null $request
|
||||
*
|
||||
* @return \Psr\Http\Message\RequestInterface
|
||||
* @throws \RetailCrm\Api\Exception\Client\HandlerException
|
||||
* @throws \RetailCrm\Api\Exception\Client\HandlerException|\RetailCrm\Api\Interfaces\ApiExceptionInterface
|
||||
*/
|
||||
public function createPsrRequest(
|
||||
string $method,
|
||||
@ -59,11 +59,28 @@ class RequestTransformer implements RequestTransformerInterface
|
||||
$requestData = new RequestData($method, $uri, $request);
|
||||
$this->handler->handle($requestData);
|
||||
|
||||
if (null === $requestData->request) {
|
||||
throw new HandlerException('Handlers should instantiate request in the ResponseData.');
|
||||
return $this->returnRequest($requestData);
|
||||
}
|
||||
|
||||
return $requestData->request;
|
||||
/**
|
||||
* Transforms provided request data into PSR-7 request model.
|
||||
*
|
||||
* You can alter the results by providing your chain of handlers.
|
||||
*
|
||||
* @param string $method
|
||||
* @param string $uri
|
||||
* @param array<int|string, mixed> $requestForm
|
||||
*
|
||||
* @return \Psr\Http\Message\RequestInterface
|
||||
* @throws \RetailCrm\Api\Exception\Client\HandlerException
|
||||
* @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface
|
||||
*/
|
||||
public function createCustomPsrRequest(string $method, string $uri, array $requestForm = []): PsrRequestInterface
|
||||
{
|
||||
$requestData = new RequestData($method, $uri, null, $requestForm);
|
||||
$this->handler->handle($requestData);
|
||||
|
||||
return $this->returnRequest($requestData);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -73,4 +90,19 @@ class RequestTransformer implements RequestTransformerInterface
|
||||
{
|
||||
return $this->handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \RetailCrm\Api\Model\RequestData $requestData
|
||||
*
|
||||
* @return \Psr\Http\Message\RequestInterface
|
||||
* @throws \RetailCrm\Api\Exception\Client\HandlerException
|
||||
*/
|
||||
private function returnRequest(RequestData $requestData): PsrRequestInterface
|
||||
{
|
||||
if (null === $requestData->request) {
|
||||
throw new HandlerException('Handlers should instantiate request in the ResponseData.');
|
||||
}
|
||||
|
||||
return $requestData->request;
|
||||
}
|
||||
}
|
||||
|
@ -59,12 +59,36 @@ class ResponseTransformer implements ResponseTransformerInterface
|
||||
ResponseInterface $response,
|
||||
string $type
|
||||
): RetailCrmResponse {
|
||||
$responseData = new ResponseData($baseUrl, $request, $response, $type);
|
||||
$responseData = new ResponseData($baseUrl, $request, $response, $type, false);
|
||||
$this->handler->handle($responseData);
|
||||
|
||||
return $responseData->responseModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms PSR-7 response into response array.
|
||||
*
|
||||
* You can alter the results by providing your chain of handlers.
|
||||
*
|
||||
* @param string $baseUrl
|
||||
* @param \Psr\Http\Message\RequestInterface $request
|
||||
* @param \Psr\Http\Message\ResponseInterface $response
|
||||
*
|
||||
* @return array<int|string, mixed>
|
||||
* @throws \RetailCrm\Api\Exception\Client\HandlerException
|
||||
* @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface
|
||||
*/
|
||||
public function createCustomResponse(
|
||||
string $baseUrl,
|
||||
RequestInterface $request,
|
||||
ResponseInterface $response
|
||||
): array {
|
||||
$responseData = new ResponseData($baseUrl, $request, $response, '', true);
|
||||
$this->handler->handle($responseData);
|
||||
|
||||
return $responseData->responseArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
@ -35,19 +35,28 @@ abstract class AbstractRequestEvent
|
||||
/** @var \Psr\Http\Message\ResponseInterface|null */
|
||||
private $response;
|
||||
|
||||
/** @var array<int|string, mixed> */
|
||||
private $responseArray;
|
||||
|
||||
/**
|
||||
* AbstractRequestEvent constructor.
|
||||
*
|
||||
* @param string $baseUrl
|
||||
* @param \Psr\Http\Message\RequestInterface $request
|
||||
* @param \Psr\Http\Message\ResponseInterface|null $response
|
||||
* @param array<int|string, mixed> $responseArray
|
||||
*/
|
||||
public function __construct(string $baseUrl, RequestInterface $request, ?ResponseInterface $response)
|
||||
{
|
||||
public function __construct(
|
||||
string $baseUrl,
|
||||
RequestInterface $request,
|
||||
?ResponseInterface $response,
|
||||
array $responseArray = []
|
||||
) {
|
||||
$this->requestScheme = (string) parse_url($baseUrl, PHP_URL_SCHEME);
|
||||
$this->crmDomain = (string) parse_url($baseUrl, PHP_URL_HOST);
|
||||
$this->request = $request;
|
||||
$this->response = $response;
|
||||
$this->responseArray = $responseArray;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -70,6 +79,16 @@ abstract class AbstractRequestEvent
|
||||
return $this->response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a response array. It will be present only if custom request was used.
|
||||
*
|
||||
* @return array<int|string, mixed>
|
||||
*/
|
||||
public function getResponseArray(): array
|
||||
{
|
||||
return $this->responseArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns RetailCRM domain for request.
|
||||
*
|
||||
|
@ -35,10 +35,16 @@ class FailureRequestEvent extends AbstractRequestEvent
|
||||
* @param \Psr\Http\Message\RequestInterface $request
|
||||
* @param \Psr\Http\Message\ResponseInterface|null $response
|
||||
* @param ApiException|ClientException $exception
|
||||
* @param array<int|string, mixed> $responseArray
|
||||
*/
|
||||
public function __construct(string $baseUrl, RequestInterface $request, ?ResponseInterface $response, $exception)
|
||||
{
|
||||
parent::__construct($baseUrl, $request, $response);
|
||||
public function __construct(
|
||||
string $baseUrl,
|
||||
RequestInterface $request,
|
||||
?ResponseInterface $response,
|
||||
$exception,
|
||||
array $responseArray = []
|
||||
) {
|
||||
parent::__construct($baseUrl, $request, $response, $responseArray);
|
||||
|
||||
$this->exception = $exception;
|
||||
}
|
||||
|
@ -21,32 +21,34 @@ use RetailCrm\Api\Interfaces\ResponseInterface;
|
||||
*/
|
||||
class SuccessRequestEvent extends AbstractRequestEvent
|
||||
{
|
||||
/** @var \RetailCrm\Api\Interfaces\ResponseInterface */
|
||||
/** @var \RetailCrm\Api\Interfaces\ResponseInterface|null */
|
||||
private $responseModel;
|
||||
|
||||
/**
|
||||
* FailureRequestEvent constructor.
|
||||
* SuccessRequestEvent constructor.
|
||||
*
|
||||
* @param string $baseUrl
|
||||
* @param \Psr\Http\Message\RequestInterface $request
|
||||
* @param \Psr\Http\Message\ResponseInterface $response
|
||||
* @param \RetailCrm\Api\Interfaces\ResponseInterface $responseModel
|
||||
* @param \RetailCrm\Api\Interfaces\ResponseInterface|null $responseModel
|
||||
* @param array<int|string, mixed> $responseArray
|
||||
*/
|
||||
public function __construct(
|
||||
string $baseUrl,
|
||||
RequestInterface $request,
|
||||
PsrResponseInterface $response,
|
||||
ResponseInterface $responseModel
|
||||
?ResponseInterface $responseModel,
|
||||
array $responseArray = []
|
||||
) {
|
||||
parent::__construct($baseUrl, $request, $response);
|
||||
parent::__construct($baseUrl, $request, $response, $responseArray);
|
||||
|
||||
$this->responseModel = $responseModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \RetailCrm\Api\Interfaces\ResponseInterface
|
||||
* @return \RetailCrm\Api\Interfaces\ResponseInterface|null
|
||||
*/
|
||||
public function getResponseModel(): ResponseInterface
|
||||
public function getResponseModel(): ?ResponseInterface
|
||||
{
|
||||
return $this->responseModel;
|
||||
}
|
||||
|
@ -54,6 +54,8 @@ class ApiExceptionFactory
|
||||
Throwable $previous = null
|
||||
): ApiException {
|
||||
$response = $errorResponse instanceof ErrorResponse ? $errorResponse : new ErrorResponse();
|
||||
$response->errorMsg = $response->errorMsg ?? '';
|
||||
$response->errors = $response->errors ?? [];
|
||||
$errorFqn = self::getErrorClassByMessage($response->errorMsg ?? '');
|
||||
|
||||
if (class_exists($errorFqn) && is_subclass_of($errorFqn, ApiException::class)) {
|
||||
|
@ -9,6 +9,7 @@
|
||||
|
||||
namespace RetailCrm\Api\Handler\Request;
|
||||
|
||||
use JsonException;
|
||||
use Psr\Http\Message\StreamFactoryInterface;
|
||||
use RetailCrm\Api\Enum\RequestMethod;
|
||||
use RetailCrm\Api\Exception\Client\HandlerException;
|
||||
@ -59,18 +60,23 @@ class RequestDataHandler extends AbstractHandler
|
||||
*/
|
||||
public function handle($item)
|
||||
{
|
||||
if ($item instanceof RequestData && null !== $item->requestModel && null !== $item->request) {
|
||||
if ($item instanceof RequestData && null !== $item->request) {
|
||||
$formData = '';
|
||||
|
||||
if (null !== $item->requestModel) {
|
||||
try {
|
||||
$formData = $this->formEncoder->encode($item->requestModel);
|
||||
} catch (Throwable $throwable) {
|
||||
throw new HandlerException(
|
||||
sprintf('Cannot encode request: %s', $throwable->getMessage()),
|
||||
$throwable->getCode(),
|
||||
$throwable
|
||||
);
|
||||
static::throwEncodeException($throwable);
|
||||
}
|
||||
}
|
||||
|
||||
if (in_array(strtoupper($item->request->getMethod()), [RequestMethod::GET, RequestMethod::DELETE], true)) {
|
||||
if (!empty($item->requestForm)) {
|
||||
$formData = http_build_query($item->requestForm);
|
||||
}
|
||||
|
||||
if ('' !== $formData) {
|
||||
if (static::queryShouldBeUsed($item->request->getMethod())) {
|
||||
$item->request = $item->request->withUri(
|
||||
$item->request->getUri()->withQuery($formData)
|
||||
);
|
||||
@ -80,7 +86,38 @@ class RequestDataHandler extends AbstractHandler
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return parent::handle($item);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Throwable $throwable
|
||||
*
|
||||
* @throws \RetailCrm\Api\Exception\Client\HandlerException
|
||||
*/
|
||||
private static function throwEncodeException(Throwable $throwable): void
|
||||
{
|
||||
throw new HandlerException(
|
||||
sprintf('Cannot encode request: %s', $throwable->getMessage()),
|
||||
$throwable->getCode(),
|
||||
$throwable
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if query params should be used instead of JSON.
|
||||
*
|
||||
* @param string $method
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private static function queryShouldBeUsed(string $method): bool
|
||||
{
|
||||
return in_array(
|
||||
strtoupper($method),
|
||||
[RequestMethod::GET, RequestMethod::DELETE],
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@
|
||||
|
||||
namespace RetailCrm\Api\Handler\Response;
|
||||
|
||||
use JsonException;
|
||||
use Liip\Serializer\SerializerInterface;
|
||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
@ -96,8 +97,27 @@ abstract class AbstractResponseHandler extends AbstractHandler implements
|
||||
try {
|
||||
return $this->serializer->deserialize(Utils::getBodyContents($response->getBody()), $type, 'json');
|
||||
} catch (Throwable $throwable) {
|
||||
throw new HandlerException('Cannot deserialize body: ' . $throwable->getMessage(), 0, $throwable);
|
||||
static::throwUnmarshalError($throwable);
|
||||
}
|
||||
|
||||
return new $type();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Psr\Http\Message\ResponseInterface $response
|
||||
*
|
||||
* @return array<int|string, mixed>
|
||||
* @throws \RetailCrm\Api\Exception\Client\HandlerException
|
||||
*/
|
||||
protected function unmarshalBodyArray(ResponseInterface $response): array
|
||||
{
|
||||
try {
|
||||
return json_decode(Utils::getBodyContents($response->getBody()), true, 512, JSON_THROW_ON_ERROR);
|
||||
} catch (JsonException $exception) {
|
||||
static::throwUnmarshalError($exception);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -111,4 +131,18 @@ abstract class AbstractResponseHandler extends AbstractHandler implements
|
||||
* @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface
|
||||
*/
|
||||
abstract protected function handleResponse(ResponseData $responseData);
|
||||
|
||||
/**
|
||||
* @param \Throwable $throwable
|
||||
*
|
||||
* @throws \RetailCrm\Api\Exception\Client\HandlerException
|
||||
*/
|
||||
private static function throwUnmarshalError(Throwable $throwable): void
|
||||
{
|
||||
throw new HandlerException(
|
||||
'Cannot deserialize body: ' . $throwable->getMessage(),
|
||||
0,
|
||||
$throwable
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -37,6 +37,7 @@ class AccountNotFoundHandler extends AbstractResponseHandler
|
||||
) {
|
||||
$errorResponse = new ErrorResponse();
|
||||
$errorResponse->errorMsg = 'Account does not exist.';
|
||||
$errorResponse->errors = [];
|
||||
|
||||
$event = new FailureRequestEvent(
|
||||
$responseData->baseUrl,
|
||||
|
@ -17,6 +17,7 @@ use RetailCrm\Api\Model\ResponseData;
|
||||
*
|
||||
* @category UnmarshalResponseHandler
|
||||
* @package RetailCrm\Api\Handler\Response
|
||||
* @SuppressWarnings(PHPMD.ElseExpression)
|
||||
*/
|
||||
class UnmarshalResponseHandler extends AbstractResponseHandler
|
||||
{
|
||||
@ -25,12 +26,18 @@ class UnmarshalResponseHandler extends AbstractResponseHandler
|
||||
*/
|
||||
protected function handleResponse(ResponseData $responseData)
|
||||
{
|
||||
if ($responseData->custom) {
|
||||
$responseData->responseArray = $this->unmarshalBodyArray($responseData->response);
|
||||
} else {
|
||||
$responseData->responseModel = $this->unmarshalBody($responseData->response, $responseData->type);
|
||||
}
|
||||
|
||||
$this->dispatch(new SuccessRequestEvent(
|
||||
$responseData->baseUrl,
|
||||
$responseData->request,
|
||||
$responseData->response,
|
||||
$responseData->responseModel
|
||||
$responseData->responseModel,
|
||||
$responseData->responseArray ?? []
|
||||
));
|
||||
}
|
||||
}
|
||||
|
61
src/Interfaces/RequestSenderInterface.php
Normal file
61
src/Interfaces/RequestSenderInterface.php
Normal file
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.3
|
||||
*
|
||||
* @category RequestTransformerInterface
|
||||
* @package RetailCrm\Api\Interfaces
|
||||
*/
|
||||
|
||||
namespace RetailCrm\Api\Interfaces;
|
||||
|
||||
/**
|
||||
* Interface RequestSenderInterface
|
||||
*
|
||||
* @category RequestTransformerInterface
|
||||
* @package RetailCrm\Api\Interfaces
|
||||
*/
|
||||
interface RequestSenderInterface
|
||||
{
|
||||
/**
|
||||
* Sends request to provided route with provided method and body, returns array response.
|
||||
* Request will be put into GET parameters or into POST form-data (depends on method).
|
||||
*
|
||||
* Note: do not remove "useless" exceptions which are marked as "never thrown" by IDE.
|
||||
* PSR-18's ClientInterface doesn't have them in the DocBlock, but, according to PSR-18,
|
||||
* they can be thrown by clients, and therefore should be present here.
|
||||
*
|
||||
* @see https://www.php-fig.org/psr/psr-18/#error-handling
|
||||
*
|
||||
* @param string $method
|
||||
* @param string $route
|
||||
* @param array<int|string, mixed> $requestForm
|
||||
*
|
||||
* @return array<int|string, mixed>
|
||||
* @throws \RetailCrm\Api\Exception\ApiException
|
||||
* @throws \RetailCrm\Api\Exception\ClientException
|
||||
* @throws \RetailCrm\Api\Exception\Client\HandlerException
|
||||
* @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface
|
||||
*/
|
||||
public function send(
|
||||
string $method,
|
||||
string $route,
|
||||
array $requestForm = []
|
||||
): array;
|
||||
|
||||
/**
|
||||
* Returns API routes with base URI prepended.
|
||||
*
|
||||
* @param string $route
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function route(string $route): string;
|
||||
|
||||
/**
|
||||
* Returns host from the base URL.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function host(): string;
|
||||
}
|
@ -39,6 +39,26 @@ interface RequestTransformerInterface
|
||||
?RequestInterface $request = null
|
||||
): PsrRequestInterface;
|
||||
|
||||
/**
|
||||
* Transforms provided custom request data into PSR-7 request model.
|
||||
*
|
||||
* This method should perform the following operations:
|
||||
* - Transform request model into PSR-7 request.
|
||||
* - Throw `HandlerException` instance if necessary.
|
||||
*
|
||||
* @param string $method
|
||||
* @param string $uri
|
||||
* @param array<int|string, mixed> $requestForm
|
||||
*
|
||||
* @return \Psr\Http\Message\RequestInterface
|
||||
* @throws \RetailCrm\Api\Exception\Client\HandlerException
|
||||
*/
|
||||
public function createCustomPsrRequest(
|
||||
string $method,
|
||||
string $uri,
|
||||
array $requestForm = []
|
||||
): PsrRequestInterface;
|
||||
|
||||
/**
|
||||
* Returns HandlerInterface.
|
||||
*
|
||||
|
@ -11,7 +11,6 @@ namespace RetailCrm\Api\Interfaces;
|
||||
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\UriInterface;
|
||||
use RetailCrm\Api\Interfaces\ResponseInterface as RetailCrmResponse;
|
||||
|
||||
/**
|
||||
@ -46,6 +45,30 @@ interface ResponseTransformerInterface
|
||||
string $type
|
||||
): RetailCrmResponse;
|
||||
|
||||
/**
|
||||
* Transforms PSR-7 response into response array.
|
||||
*
|
||||
* This method should do the following operations:
|
||||
* - It should convert PSR-7 response object into the response model of provided type.
|
||||
* - It should automatically detect an API error and throw an `ApiExceptionInterface` instance.
|
||||
* - It can throw a `HandlerException` instance if necessary.
|
||||
*
|
||||
* This method should be used only for custom requests.
|
||||
*
|
||||
* @param string $baseUrl
|
||||
* @param \Psr\Http\Message\RequestInterface $request
|
||||
* @param \Psr\Http\Message\ResponseInterface $response
|
||||
*
|
||||
* @return array<int|string, mixed>
|
||||
* @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface
|
||||
* @throws \RetailCrm\Api\Exception\Client\HandlerException
|
||||
*/
|
||||
public function createCustomResponse(
|
||||
string $baseUrl,
|
||||
RequestInterface $request,
|
||||
ResponseInterface $response
|
||||
): array;
|
||||
|
||||
/**
|
||||
* Returns HandlerInterface.
|
||||
*
|
||||
|
@ -28,6 +28,9 @@ class RequestData
|
||||
/** @var \RetailCrm\Api\Interfaces\RequestInterface|null */
|
||||
public $requestModel;
|
||||
|
||||
/** @var array<int|string, mixed> */
|
||||
public $requestForm;
|
||||
|
||||
/** @var ?\Psr\Http\Message\RequestInterface */
|
||||
public $request;
|
||||
|
||||
@ -37,11 +40,13 @@ class RequestData
|
||||
* @param string $method
|
||||
* @param string $uri
|
||||
* @param \RetailCrm\Api\Interfaces\RequestInterface|null $requestModel
|
||||
* @param array<int|string, mixed> $requestForm
|
||||
*/
|
||||
public function __construct(string $method, string $uri, ?RequestModel $requestModel)
|
||||
public function __construct(string $method, string $uri, ?RequestModel $requestModel, array $requestForm = [])
|
||||
{
|
||||
$this->method = $method;
|
||||
$this->uri = $uri;
|
||||
$this->requestModel = $requestModel;
|
||||
$this->requestForm = $requestForm;
|
||||
}
|
||||
}
|
||||
|
@ -33,9 +33,15 @@ class ResponseData
|
||||
/** @var string */
|
||||
public $type;
|
||||
|
||||
/** @var bool */
|
||||
public $custom;
|
||||
|
||||
/** @var \RetailCrm\Api\Interfaces\ResponseInterface */
|
||||
public $responseModel;
|
||||
|
||||
/** @var array<int|string, mixed> */
|
||||
public $responseArray;
|
||||
|
||||
/**
|
||||
* ResponseData constructor.
|
||||
*
|
||||
@ -43,12 +49,21 @@ class ResponseData
|
||||
* @param \Psr\Http\Message\RequestInterface $request
|
||||
* @param \Psr\Http\Message\ResponseInterface $response
|
||||
* @param string $type
|
||||
* @param bool $custom
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.BooleanArgumentFlag)
|
||||
*/
|
||||
public function __construct(string $baseUrl, RequestInterface $request, ResponseInterface $response, string $type)
|
||||
{
|
||||
public function __construct(
|
||||
string $baseUrl,
|
||||
RequestInterface $request,
|
||||
ResponseInterface $response,
|
||||
string $type,
|
||||
bool $custom = false
|
||||
) {
|
||||
$this->baseUrl = $baseUrl;
|
||||
$this->request = $request;
|
||||
$this->response = $response;
|
||||
$this->type = $type;
|
||||
$this->custom = $custom;
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,8 @@ use Psr\EventDispatcher\EventDispatcherInterface;
|
||||
use Psr\Http\Client\ClientExceptionInterface;
|
||||
use Psr\Http\Client\ClientInterface;
|
||||
use Psr\Http\Client\NetworkExceptionInterface;
|
||||
use Psr\Http\Message\RequestInterface as PsrRequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface as PsrResponseInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\NullLogger;
|
||||
use RetailCrm\Api\Component\Utils;
|
||||
@ -23,8 +25,8 @@ use RetailCrm\Api\Interfaces\RequestInterface;
|
||||
use RetailCrm\Api\Interfaces\RequestTransformerInterface;
|
||||
use RetailCrm\Api\Interfaces\ResponseInterface;
|
||||
use RetailCrm\Api\Interfaces\ResponseTransformerInterface;
|
||||
use RetailCrm\Api\Model\Response\SuccessResponse;
|
||||
use RetailCrm\Api\Traits\EventDispatcherAwareTrait;
|
||||
use RetailCrm\Api\Traits\BaseUrlAwareTrait;
|
||||
|
||||
/**
|
||||
* Class AbstractApiResourceGroup
|
||||
@ -33,15 +35,14 @@ use RetailCrm\Api\Traits\EventDispatcherAwareTrait;
|
||||
* @package RetailCrm\Api\Modules
|
||||
* @internal
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.ElseExpression)
|
||||
* @SuppressWarnings(PHPMD.NumberOfChildren)
|
||||
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
|
||||
*/
|
||||
abstract class AbstractApiResourceGroup implements EventDispatcherAwareInterface
|
||||
{
|
||||
use EventDispatcherAwareTrait;
|
||||
|
||||
/** @var string */
|
||||
protected $baseUrl;
|
||||
use BaseUrlAwareTrait;
|
||||
|
||||
/** @var ClientInterface */
|
||||
protected $httpClient;
|
||||
@ -97,15 +98,10 @@ abstract class AbstractApiResourceGroup implements EventDispatcherAwareInterface
|
||||
* @param string $type
|
||||
*
|
||||
* @return \RetailCrm\Api\Interfaces\ResponseInterface
|
||||
* @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface
|
||||
* @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface
|
||||
* @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException
|
||||
* @throws \RetailCrm\Api\Exception\Api\ApiErrorException
|
||||
* @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException
|
||||
* @throws \RetailCrm\Api\Exception\Api\MissingParameterException
|
||||
* @throws \RetailCrm\Api\Exception\Api\ValidationException
|
||||
* @throws \RetailCrm\Api\Exception\ApiException
|
||||
* @throws \RetailCrm\Api\Exception\ClientException
|
||||
* @throws \RetailCrm\Api\Exception\Client\HandlerException
|
||||
* @throws \RetailCrm\Api\Exception\Client\HttpClientException
|
||||
* @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface
|
||||
*/
|
||||
protected function sendRequest(
|
||||
string $method,
|
||||
@ -120,6 +116,30 @@ abstract class AbstractApiResourceGroup implements EventDispatcherAwareInterface
|
||||
$request
|
||||
);
|
||||
|
||||
$this->logPsr7Request($psrRequest);
|
||||
|
||||
try {
|
||||
$psrResponse = $this->httpClient->sendRequest($psrRequest);
|
||||
} catch (ClientExceptionInterface | NetworkExceptionInterface $exception) {
|
||||
$this->processPsr18Exception($psrRequest, $exception);
|
||||
}
|
||||
|
||||
if (isset($psrResponse)) {
|
||||
$this->logPsr7Response($psrResponse);
|
||||
} else {
|
||||
return new $type();
|
||||
}
|
||||
|
||||
return $this->responseTransformer->createResponse($this->baseUrl, $psrRequest, $psrResponse, $type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs PSR-7 request data if possible.
|
||||
*
|
||||
* @param \Psr\Http\Message\RequestInterface $psrRequest
|
||||
*/
|
||||
protected function logPsr7Request(PsrRequestInterface $psrRequest): void
|
||||
{
|
||||
if ($this->logger instanceof LoggerInterface && !($this->logger instanceof NullLogger)) {
|
||||
$this->logger->debug(sprintf(
|
||||
'[RetailCRM API Request]: %s URL: "%s", Headers: "%s", Body: "%s"',
|
||||
@ -129,10 +149,38 @@ abstract class AbstractApiResourceGroup implements EventDispatcherAwareInterface
|
||||
Utils::getBodyContents($psrRequest->getBody())
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$psrResponse = $this->httpClient->sendRequest($psrRequest);
|
||||
} catch (ClientExceptionInterface | NetworkExceptionInterface $exception) {
|
||||
/**
|
||||
* Logs PSR-7 response data if possible.
|
||||
*
|
||||
* @param \Psr\Http\Message\ResponseInterface $psrResponse
|
||||
*/
|
||||
protected function logPsr7Response(PsrResponseInterface $psrResponse): void
|
||||
{
|
||||
if (
|
||||
$this->logger instanceof LoggerInterface &&
|
||||
!($this->logger instanceof NullLogger)
|
||||
) {
|
||||
$this->logger->debug(sprintf(
|
||||
'[RetailCRM API Response]: Status: "%d", Body: "%s"',
|
||||
$psrResponse->getStatusCode(),
|
||||
Utils::getBodyContents($psrResponse->getBody())
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes PSR-18 client exception.
|
||||
*
|
||||
* @param \Psr\Http\Message\RequestInterface $psrRequest
|
||||
* @param ClientExceptionInterface|NetworkExceptionInterface $exception
|
||||
*
|
||||
* @throws \RetailCrm\Api\Exception\ApiException
|
||||
* @throws \RetailCrm\Api\Exception\ClientException
|
||||
*/
|
||||
protected function processPsr18Exception(PsrRequestInterface $psrRequest, $exception): void
|
||||
{
|
||||
$event = new FailureRequestEvent(
|
||||
$this->baseUrl,
|
||||
$psrRequest,
|
||||
@ -150,35 +198,4 @@ abstract class AbstractApiResourceGroup implements EventDispatcherAwareInterface
|
||||
throw $event->getException();
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
$this->logger instanceof LoggerInterface &&
|
||||
!($this->logger instanceof NullLogger) &&
|
||||
isset($psrResponse)
|
||||
) {
|
||||
$this->logger->debug(sprintf(
|
||||
'[RetailCRM API Response]: Status: "%d", Body: "%s"',
|
||||
$psrResponse->getStatusCode(),
|
||||
Utils::getBodyContents($psrResponse->getBody())
|
||||
));
|
||||
}
|
||||
|
||||
if (!isset($psrResponse)) {
|
||||
return new $type();
|
||||
}
|
||||
|
||||
return $this->responseTransformer->createResponse($this->baseUrl, $psrRequest, $psrResponse, $type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns route with base URI.
|
||||
*
|
||||
* @param string $route
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function route(string $route): string
|
||||
{
|
||||
return sprintf('%s/%s', $this->baseUrl, $route);
|
||||
}
|
||||
}
|
||||
|
217
src/ResourceGroup/CustomMethods.php
Normal file
217
src/ResourceGroup/CustomMethods.php
Normal file
@ -0,0 +1,217 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.3
|
||||
*
|
||||
* @category CustomMethods
|
||||
* @package RetailCrm\Api\ResourceGroup
|
||||
*/
|
||||
|
||||
namespace RetailCrm\Api\ResourceGroup;
|
||||
|
||||
use RetailCrm\Api\Component\RequestSender;
|
||||
use RetailCrm\Api\Exception\Client\HandlerException;
|
||||
|
||||
/**
|
||||
* Class CustomMethods
|
||||
*
|
||||
* @category CustomMethods
|
||||
* @package RetailCrm\Api\ResourceGroup
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
|
||||
*/
|
||||
class CustomMethods extends AbstractApiResourceGroup
|
||||
{
|
||||
/** @var \RetailCrm\Api\Interfaces\RequestSenderInterface|null */
|
||||
private $requestSender;
|
||||
|
||||
/** @var array<string, callable|\RetailCrm\Api\Component\CustomApiMethod> */
|
||||
private $methods = [];
|
||||
|
||||
/**
|
||||
* Register custom method in the resource group. You can use invokable class for easier implementation.
|
||||
*
|
||||
* Registered callable should accept three arguments:
|
||||
* - `RequestSenderInterface`
|
||||
* - Array with the request data (may be null or empty if not provided - this is the case for get requests).
|
||||
* - Context data - may contain any data. You can use the context for anything you like.
|
||||
*
|
||||
* Example with the `CustomApiMethod` wrapper:
|
||||
*
|
||||
* ```php
|
||||
* use RetailCrm\Api\Component\CustomApiMethod;
|
||||
* use RetailCrm\Api\Enum\RequestMethod;
|
||||
* use RetailCrm\Api\Factory\SimpleClientFactory;
|
||||
* use RetailCrm\Api\Interfaces\ApiExceptionInterface;
|
||||
*
|
||||
* $client = SimpleClientFactory::createClient('https://test.simla.com', 'apiKey');
|
||||
* $client->customMethods->register('settings', new CustomApiMethod(RequestMethod::GET, 'settings'));
|
||||
* ```
|
||||
*
|
||||
* Sometimes `CustomApiMethod` may feel too simple to do certain tasks in the methods. That's why it is possible
|
||||
* to register custom callable. Let's register a custom method that returns array of available scopes:
|
||||
*
|
||||
* use RetailCrm\Api\Enum\RequestMethod;
|
||||
* use RetailCrm\Api\Factory\SimpleClientFactory;
|
||||
* use RetailCrm\Api\Interfaces\ApiExceptionInterface;
|
||||
* use RetailCrm\Api\Interfaces\RequestSenderInterface;
|
||||
*
|
||||
* ```php
|
||||
* $client = SimpleClientFactory::createClient('https://test.simla.com', 'apiKey');
|
||||
* $client->customMethods->register(
|
||||
* 'scopes',
|
||||
* static function (RequestSenderInterface $sender, $data, array $context) {
|
||||
* return $sender->send(
|
||||
* RequestMethod::GET,
|
||||
* sprintf('https://%s/api/credentials', $sender->host()),
|
||||
* $data
|
||||
* )['scopes'];
|
||||
* }
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* Check `call()` method to learn how to call registered methods.
|
||||
*
|
||||
* @see self::call
|
||||
* @see \RetailCrm\Api\Interfaces\RequestSenderInterface
|
||||
* @see RequestSender
|
||||
* @see \RetailCrm\Api\Component\CustomApiMethod
|
||||
*
|
||||
* @param string $name
|
||||
* @param callable|\RetailCrm\Api\Component\CustomApiMethod $sender
|
||||
*/
|
||||
public function register(string $name, callable $sender): void
|
||||
{
|
||||
if (null === $this->requestSender) {
|
||||
$this->requestSender = new RequestSender($this);
|
||||
}
|
||||
|
||||
$this->methods[$name] = $sender;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls custom method, returns array response.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* ```php
|
||||
* $client->customMethods->call('scopes');
|
||||
* ```
|
||||
*
|
||||
* Second parameter should be provided for POST requests or query data. Third parameter is used to pass anything
|
||||
* you like to the callable during the method call (logger, for example).
|
||||
*
|
||||
* This implementation is also used in the `__call` magic method. It works like this:
|
||||
*
|
||||
* ```php
|
||||
* $client->customMethods->scopes($data, 'any', 'other', 'params');
|
||||
* ```
|
||||
* will make this call:
|
||||
* ```php
|
||||
* $client->customMethods->call('scopes', $data, ['any', 'other', 'params']);
|
||||
* ```
|
||||
*
|
||||
* Full example for the settings method:
|
||||
*
|
||||
* ```php
|
||||
* use RetailCrm\Api\Component\CustomApiMethod;
|
||||
* use RetailCrm\Api\Enum\RequestMethod;
|
||||
* use RetailCrm\Api\Factory\SimpleClientFactory;
|
||||
* use RetailCrm\Api\Interfaces\ApiExceptionInterface;
|
||||
*
|
||||
* $client = SimpleClientFactory::createClient('https://azgalot.retailcrm.ru', '9y3e3ohX1NGqAausgg5ACMWPv5Z4iXQF');
|
||||
* $client->customMethods->register('settings', new CustomApiMethod(RequestMethod::GET, 'settings'));
|
||||
*
|
||||
* try {
|
||||
* // It will work because 'settings' method was registered before
|
||||
* $settings = $client->customMethods->settings();
|
||||
* } catch (ApiExceptionInterface $exception) {
|
||||
* echo sprintf(
|
||||
* 'Error from RetailCRM API (status code: %d): %s',
|
||||
* $exception->getStatusCode(),
|
||||
* $exception->getMessage()
|
||||
* );
|
||||
*
|
||||
* if (count($exception->getErrorResponse()->errors) > 0) {
|
||||
* echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors);
|
||||
* }
|
||||
*
|
||||
* return;
|
||||
* }
|
||||
*
|
||||
* echo 'Timezone: ' . $settings['settings']['timezone']['value'];
|
||||
* ```
|
||||
*
|
||||
* @param string $name
|
||||
* @param array<int|string, mixed>|object $data
|
||||
* @param array<int|string, mixed> $context
|
||||
*
|
||||
* @return array<int|string, mixed>|object
|
||||
* @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface
|
||||
* @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface
|
||||
* @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException
|
||||
* @throws \RetailCrm\Api\Exception\Api\ApiErrorException
|
||||
* @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException
|
||||
* @throws \RetailCrm\Api\Exception\Api\MissingParameterException
|
||||
* @throws \RetailCrm\Api\Exception\Api\ValidationException
|
||||
* @throws \RetailCrm\Api\Exception\Client\HandlerException
|
||||
* @throws \RetailCrm\Api\Exception\Client\HttpClientException
|
||||
*/
|
||||
public function call(string $name, $data = [], array $context = [])
|
||||
{
|
||||
if (null === $this->requestSender || !array_key_exists($name, $this->methods)) {
|
||||
throw new HandlerException(sprintf('Cannot find custom method with name "%s"', $name));
|
||||
}
|
||||
|
||||
return $this->methods[$name]($this->requestSender, $data, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls custom method, returns array response.
|
||||
*
|
||||
* @param string $name
|
||||
* @param array<int|string, mixed> $arguments
|
||||
*
|
||||
* @return array<int|string, mixed>|object
|
||||
* @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface
|
||||
* @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface
|
||||
* @throws \RetailCrm\Api\Exception\Api\AccountDoesNotExistException
|
||||
* @throws \RetailCrm\Api\Exception\Api\ApiErrorException
|
||||
* @throws \RetailCrm\Api\Exception\Api\MissingCredentialsException
|
||||
* @throws \RetailCrm\Api\Exception\Api\MissingParameterException
|
||||
* @throws \RetailCrm\Api\Exception\Api\ValidationException
|
||||
* @throws \RetailCrm\Api\Exception\Client\HandlerException
|
||||
* @throws \RetailCrm\Api\Exception\Client\HttpClientException
|
||||
*@see \RetailCrm\Api\ResourceGroup\CustomMethods::call()
|
||||
*
|
||||
*/
|
||||
public function __call(string $name, array $arguments)
|
||||
{
|
||||
$data = [];
|
||||
$context = [];
|
||||
|
||||
if (count($arguments) > 0) {
|
||||
if (!is_array($arguments[0]) && !is_object($arguments[0])) {
|
||||
throw new HandlerException(sprintf(
|
||||
'$data must be of type array or object, %s given',
|
||||
gettype($arguments[0])
|
||||
));
|
||||
}
|
||||
|
||||
$data = $arguments[0];
|
||||
}
|
||||
|
||||
if (count($arguments) > 1) {
|
||||
if (!is_array($arguments[1])) {
|
||||
throw new HandlerException(sprintf(
|
||||
'$context must be of type array, %s given',
|
||||
gettype($arguments[1])
|
||||
));
|
||||
}
|
||||
|
||||
$context = $arguments[1];
|
||||
}
|
||||
|
||||
return $this->call($name, $data, $context);
|
||||
}
|
||||
}
|
34
src/Traits/BaseUrlAwareTrait.php
Normal file
34
src/Traits/BaseUrlAwareTrait.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.3
|
||||
*
|
||||
* @category BaseUrlAwareTrait
|
||||
* @package RetailCrm\Api\Traits
|
||||
*/
|
||||
|
||||
namespace RetailCrm\Api\Traits;
|
||||
|
||||
/**
|
||||
* Trait BaseUrlAwareTrait
|
||||
*
|
||||
* @category BaseUrlAwareTrait
|
||||
* @package RetailCrm\Api\Traits
|
||||
*/
|
||||
trait BaseUrlAwareTrait
|
||||
{
|
||||
/** @var string */
|
||||
protected $baseUrl;
|
||||
|
||||
/**
|
||||
* Returns API routes with base URI prepended.
|
||||
*
|
||||
* @param string $route
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function route(string $route): string
|
||||
{
|
||||
return sprintf('%s/%s', $this->baseUrl, $route);
|
||||
}
|
||||
}
|
64
tests/src/Component/CustomApiMethodTest.php
Normal file
64
tests/src/Component/CustomApiMethodTest.php
Normal file
@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.3
|
||||
*
|
||||
* @category CustomApiMethodTest
|
||||
* @package RetailCrm\Tests\Component
|
||||
*/
|
||||
|
||||
namespace RetailCrm\Tests\Component;
|
||||
|
||||
use http\Env\Request;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use RetailCrm\Api\Component\CustomApiMethod;
|
||||
use RetailCrm\Api\Enum\RequestMethod;
|
||||
use RetailCrm\Api\Interfaces\RequestSenderInterface;
|
||||
|
||||
/**
|
||||
* Class CustomApiMethodTest
|
||||
*
|
||||
* @category CustomApiMethodTest
|
||||
* @package RetailCrm\Tests\Component
|
||||
*/
|
||||
class CustomApiMethodTest extends TestCase
|
||||
{
|
||||
public function testDefault(): void
|
||||
{
|
||||
$baseUrl = 'https://test.simla.io/api/v5';
|
||||
$mock = $this->getMockBuilder(RequestSenderInterface::class)
|
||||
->onlyMethods(['send', 'route', 'host'])
|
||||
->getMock();
|
||||
|
||||
$mock->method('send')
|
||||
->with(RequestMethod::GET, $baseUrl . '/method')
|
||||
->willReturn([$baseUrl]);
|
||||
|
||||
$mock->method('route')
|
||||
->withAnyParameters()
|
||||
->willReturn($baseUrl . '/method');
|
||||
|
||||
static::assertEquals([$baseUrl], (new CustomApiMethod(RequestMethod::GET, 'method'))($mock));
|
||||
}
|
||||
|
||||
public function testRawRoute(): void
|
||||
{
|
||||
$baseUrl = 'https://test.simla.io/api/v5';
|
||||
$mock = $this->getMockBuilder(RequestSenderInterface::class)
|
||||
->onlyMethods(['send', 'route', 'host'])
|
||||
->getMock();
|
||||
|
||||
$mock->method('send')
|
||||
->with(RequestMethod::GET, 'method')
|
||||
->willReturn([$baseUrl]);
|
||||
|
||||
$mock->method('route')
|
||||
->withAnyParameters()
|
||||
->willReturn('');
|
||||
|
||||
static::assertEquals(
|
||||
[$baseUrl],
|
||||
(new CustomApiMethod(RequestMethod::GET, 'method'))->useRouteAsUri()($mock)
|
||||
);
|
||||
}
|
||||
}
|
@ -18,6 +18,7 @@ use RetailCrm\Api\Exception\Api\AccessDeniedException;
|
||||
use RetailCrm\Api\Handler\Request\GetParameterAuthenticatorHandler;
|
||||
use RetailCrm\Api\Interfaces\ApiExceptionInterface;
|
||||
use RetailCrm\Api\Interfaces\ClientExceptionInterface;
|
||||
use RetailCrm\Api\Interfaces\RequestSenderInterface;
|
||||
use RetailCrm\Api\Model\Response\Api\ApiVersionsResponse;
|
||||
use RetailCrm\TestUtils\ArrayLogger;
|
||||
use RetailCrm\TestUtils\Factory\TestClientFactory;
|
||||
@ -124,6 +125,63 @@ EOF;
|
||||
self::assertEquals(TestConfig::getApiKey(), $event->getApiKey());
|
||||
}
|
||||
|
||||
public function testSuccessCustomRequestEvent(): void
|
||||
{
|
||||
/** @var SuccessRequestEvent|null $event */
|
||||
$event = null;
|
||||
$json = <<<'EOF'
|
||||
{
|
||||
"success": true,
|
||||
"versions": [
|
||||
"3.0",
|
||||
"4.0",
|
||||
"5.0"
|
||||
]
|
||||
}
|
||||
EOF;
|
||||
$dispatcher = new EventDispatcher();
|
||||
$dispatcher->subscribeTo(SuccessRequestEvent::class, static function (object $item) use (&$event) {
|
||||
$event = $item;
|
||||
});
|
||||
|
||||
$mock = static::createUnversionedApiMockBuilder('api-versions');
|
||||
$mock->matchMethod(RequestMethod::GET)
|
||||
->reply(200)
|
||||
->withBody($json);
|
||||
|
||||
$client = TestClientFactory::createClient($mock->getClient(), null, $dispatcher);
|
||||
$client->customMethods->register(
|
||||
'apiVersions',
|
||||
static function (RequestSenderInterface $sender, array $data, array $context) {
|
||||
return $sender->send(
|
||||
RequestMethod::GET,
|
||||
sprintf('https://%s/api/api-versions', $sender->host())
|
||||
)['versions'];
|
||||
}
|
||||
);
|
||||
$client->customMethods->call('apiVersions');
|
||||
|
||||
self::assertInstanceOf(SuccessRequestEvent::class, $event);
|
||||
self::assertNotNull($event->getResponse());
|
||||
self::assertNotEmpty($event->getResponse()->getBody()->__toString());
|
||||
self::assertNull($event->getResponseModel());
|
||||
self::assertEquals([
|
||||
'success' => true,
|
||||
'versions' => ["3.0", "4.0", "5.0"]
|
||||
], $event->getResponseArray());
|
||||
self::assertInstanceOf(RequestInterface::class, $event->getRequest());
|
||||
self::assertEmpty($event->getRequest()->getBody()->__toString());
|
||||
self::assertStringContainsString(
|
||||
parse_url(TestConfig::getApiUrl(), PHP_URL_HOST),
|
||||
$event->getApiUrl()
|
||||
);
|
||||
self::assertStringContainsString(
|
||||
parse_url(TestConfig::getApiUrl(), PHP_URL_HOST),
|
||||
$event->getApiDomain()
|
||||
);
|
||||
self::assertEquals(TestConfig::getApiKey(), $event->getApiKey());
|
||||
}
|
||||
|
||||
public function testFailureRequestEvent(): void
|
||||
{
|
||||
/** @var FailureRequestEvent|null $event */
|
||||
@ -209,6 +267,109 @@ EOF;
|
||||
);
|
||||
}
|
||||
|
||||
public function testFailureCustomRequestEvent(): void
|
||||
{
|
||||
/** @var FailureRequestEvent|null $event */
|
||||
$event = null;
|
||||
$json = <<<'EOF'
|
||||
{
|
||||
"errorMsg": "Access denied.",
|
||||
"success": false
|
||||
}
|
||||
EOF;
|
||||
$dispatcher = new EventDispatcher();
|
||||
$dispatcher->subscribeTo(FailureRequestEvent::class, static function (object $item) use (&$event) {
|
||||
$event = $item;
|
||||
});
|
||||
|
||||
$mock = static::createUnversionedApiMockBuilder('api-versions');
|
||||
$mock->matchMethod(RequestMethod::GET)
|
||||
->reply(403)
|
||||
->withBody($json);
|
||||
|
||||
$client = TestClientFactory::createClient($mock->getClient(), null, $dispatcher);
|
||||
$client->customMethods->register(
|
||||
'apiVersions',
|
||||
static function (RequestSenderInterface $sender, array $data, array $context) {
|
||||
return $sender->send(
|
||||
RequestMethod::GET,
|
||||
sprintf('https://%s/api/api-versions', $sender->host())
|
||||
)['versions'];
|
||||
}
|
||||
);
|
||||
|
||||
try {
|
||||
$client->customMethods->call('apiVersions');
|
||||
} catch (AccessDeniedException $exception) {
|
||||
}
|
||||
|
||||
self::assertInstanceOf(FailureRequestEvent::class, $event);
|
||||
self::assertNotNull($event->getResponse());
|
||||
self::assertNotEmpty($event->getResponse()->getBody()->__toString());
|
||||
self::assertInstanceOf(AccessDeniedException::class, $event->getException());
|
||||
self::assertEquals('Access denied.', $event->getException()->getErrorResponse()->errorMsg);
|
||||
self::assertEquals(403, $event->getException()->getStatusCode());
|
||||
self::assertStringContainsString(
|
||||
parse_url(TestConfig::getApiUrl(), PHP_URL_HOST),
|
||||
$event->getApiUrl()
|
||||
);
|
||||
self::assertStringContainsString(
|
||||
parse_url(TestConfig::getApiUrl(), PHP_URL_HOST),
|
||||
$event->getApiDomain()
|
||||
);
|
||||
self::assertEquals(TestConfig::getApiKey(), $event->getApiKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider failureRequestEventSuppressThrow
|
||||
*/
|
||||
public function testFailureRequestEventCustomSuppressThrow(bool $useClientException): void
|
||||
{
|
||||
/** @var FailureRequestEvent $event */
|
||||
$event = null;
|
||||
|
||||
$dispatcher = new EventDispatcher();
|
||||
$dispatcher->subscribeTo(
|
||||
FailureRequestEvent::class,
|
||||
static function (FailureRequestEvent $item) use (&$event) {
|
||||
$item->suppressThrow();
|
||||
$event = $item;
|
||||
}
|
||||
);
|
||||
|
||||
$mock = static::createUnversionedApiMockBuilder('api-versions');
|
||||
|
||||
if ($useClientException) {
|
||||
$mock->matchMethod(RequestMethod::GET)
|
||||
->throwClientException();
|
||||
} else {
|
||||
$mock->matchMethod(RequestMethod::GET)
|
||||
->reply(403)
|
||||
->withJson([
|
||||
'success' => false,
|
||||
'errorMsg' => 'Access denied.'
|
||||
]);
|
||||
}
|
||||
|
||||
$client = TestClientFactory::createClient($mock->getClient(), null, $dispatcher);
|
||||
$client->customMethods->register(
|
||||
'apiVersions',
|
||||
static function (RequestSenderInterface $sender, array $data, array $context) {
|
||||
return $sender->send(
|
||||
RequestMethod::GET,
|
||||
sprintf('https://%s/api/api-versions', $sender->host())
|
||||
)['versions'] ?? [];
|
||||
}
|
||||
);
|
||||
$client->customMethods->call('apiVersions');
|
||||
|
||||
self::assertInstanceOf(FailureRequestEvent::class, $event);
|
||||
self::assertInstanceOf(
|
||||
$useClientException ? ClientExceptionInterface::class : ApiExceptionInterface::class,
|
||||
$event->getException()
|
||||
);
|
||||
}
|
||||
|
||||
public function failureRequestEventSuppressThrow(): array
|
||||
{
|
||||
return [[true], [false]];
|
||||
|
294
tests/src/ResourceGroup/CustomMethodsTest.php
Normal file
294
tests/src/ResourceGroup/CustomMethodsTest.php
Normal file
@ -0,0 +1,294 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.3
|
||||
*
|
||||
* @category CustomMethodsTest
|
||||
* @package RetailCrm\Tests\ResourceGroup
|
||||
*/
|
||||
|
||||
namespace RetailCrm\Tests\ResourceGroup;
|
||||
|
||||
use Psr\Http\Client\ClientInterface;
|
||||
use RetailCrm\Api\Component\CustomApiMethod;
|
||||
use RetailCrm\Api\Enum\RequestMethod;
|
||||
use RetailCrm\Api\Exception\Client\HandlerException;
|
||||
use RetailCrm\Api\Interfaces\RequestSenderInterface;
|
||||
use RetailCrm\TestUtils\APIVersionsResponse;
|
||||
use RetailCrm\TestUtils\Factory\TestClientFactory;
|
||||
use RetailCrm\TestUtils\PockBuilder;
|
||||
use RetailCrm\TestUtils\TestCase\AbstractApiResourceGroupTestCase;
|
||||
|
||||
/**
|
||||
* Class CustomMethodsTest
|
||||
*
|
||||
* @category CustomMethodsTest
|
||||
* @package RetailCrm\Tests\ResourceGroup
|
||||
*/
|
||||
class CustomMethodsTest extends AbstractApiResourceGroupTestCase
|
||||
{
|
||||
public function testCall(): void
|
||||
{
|
||||
$json = <<<'EOF'
|
||||
{
|
||||
"success": true,
|
||||
"versions": [
|
||||
"3.0",
|
||||
"4.0",
|
||||
"5.0"
|
||||
]
|
||||
}
|
||||
EOF;
|
||||
|
||||
$mock = static::createUnversionedApiMockBuilder('api-versions');
|
||||
$mock->matchMethod(RequestMethod::GET)
|
||||
->reply(200)
|
||||
->withBody($json);
|
||||
|
||||
$client = TestClientFactory::createClient($mock->getClient());
|
||||
$client->customMethods->register(
|
||||
'apiVersions',
|
||||
static function (RequestSenderInterface $sender, array $data, array $context) {
|
||||
return $sender->send(
|
||||
RequestMethod::GET,
|
||||
sprintf('https://%s/api/api-versions', $sender->host())
|
||||
)['versions'];
|
||||
}
|
||||
);
|
||||
$apiVersions = $client->customMethods->call('apiVersions');
|
||||
|
||||
self::assertEquals(["3.0", "4.0", "5.0"], $apiVersions);
|
||||
}
|
||||
|
||||
public function testCallDtos(): void
|
||||
{
|
||||
$json = <<<'EOF'
|
||||
{
|
||||
"success": true,
|
||||
"versions": [
|
||||
"3.0",
|
||||
"4.0",
|
||||
"5.0"
|
||||
]
|
||||
}
|
||||
EOF;
|
||||
|
||||
$mock = static::createUnversionedApiMockBuilder('api-versions');
|
||||
$mock->matchMethod(RequestMethod::GET)
|
||||
->matchExactQuery(['param' => 'value'])
|
||||
->reply(200)
|
||||
->withBody($json);
|
||||
|
||||
$client = TestClientFactory::createClient($mock->getClient());
|
||||
$client->customMethods->register(
|
||||
'apiVersions',
|
||||
static function (RequestSenderInterface $sender, $data, array $context) {
|
||||
$response = $sender->send(
|
||||
RequestMethod::GET,
|
||||
sprintf('https://%s/api/api-versions', $sender->host()),
|
||||
json_decode($data, true, 512, JSON_THROW_ON_ERROR)
|
||||
);
|
||||
|
||||
return new APIVersionsResponse(
|
||||
$response['success'] ?? false,
|
||||
$response['versions'] ?? []
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
/** @var \RetailCrm\TestUtils\APIVersionsResponse $apiVersions */
|
||||
$apiVersions = $client->customMethods->call('apiVersions', '{"param": "value"}');
|
||||
|
||||
self::assertEquals(["3.0", "4.0", "5.0"], $apiVersions->versions);
|
||||
}
|
||||
|
||||
public function testCallContext(): void
|
||||
{
|
||||
$json = <<<'EOF'
|
||||
{
|
||||
"success": true,
|
||||
"versions": [
|
||||
"3.0",
|
||||
"4.0",
|
||||
"5.0"
|
||||
]
|
||||
}
|
||||
EOF;
|
||||
|
||||
$mock = static::createUnversionedApiMockBuilder('api-versions/v1');
|
||||
$mock->matchMethod(RequestMethod::GET)
|
||||
->reply(200)
|
||||
->withBody($json);
|
||||
|
||||
$client = TestClientFactory::createClient($mock->getClient());
|
||||
$client->customMethods->register(
|
||||
'apiVersions',
|
||||
static function (RequestSenderInterface $sender, array $data, array $context) {
|
||||
return $sender->send(
|
||||
RequestMethod::GET,
|
||||
sprintf('https://%s/api/api-versions/v%s', $sender->host(), $context['v'])
|
||||
)['versions'];
|
||||
}
|
||||
);
|
||||
$apiVersions = $client->customMethods->call('apiVersions', [], ['v' => '1']);
|
||||
|
||||
self::assertEquals(["3.0", "4.0", "5.0"], $apiVersions);
|
||||
}
|
||||
|
||||
public function testCallMagicContext(): void
|
||||
{
|
||||
$json = <<<'EOF'
|
||||
{
|
||||
"success": true,
|
||||
"versions": [
|
||||
"3.0",
|
||||
"4.0",
|
||||
"5.0"
|
||||
]
|
||||
}
|
||||
EOF;
|
||||
|
||||
$mock = static::createUnversionedApiMockBuilder('api-versions/v1');
|
||||
$mock->matchMethod(RequestMethod::GET)
|
||||
->reply(200)
|
||||
->withBody($json);
|
||||
|
||||
$client = TestClientFactory::createClient($mock->getClient());
|
||||
$client->customMethods->register(
|
||||
'apiVersions',
|
||||
static function (RequestSenderInterface $sender, array $data, array $context) {
|
||||
return $sender->send(
|
||||
RequestMethod::GET,
|
||||
sprintf('https://%s/api/api-versions/v%s', $sender->host(), $context['v'])
|
||||
)['versions'];
|
||||
}
|
||||
);
|
||||
$apiVersions = $client->customMethods->apiVersions([], ['v' => '1']);
|
||||
|
||||
self::assertEquals(["3.0", "4.0", "5.0"], $apiVersions);
|
||||
}
|
||||
|
||||
public function testCallWithParamsGet(): void
|
||||
{
|
||||
$json = <<<'EOF'
|
||||
{
|
||||
"success": true,
|
||||
"versions": [
|
||||
"3.0",
|
||||
"4.0",
|
||||
"5.0"
|
||||
]
|
||||
}
|
||||
EOF;
|
||||
|
||||
$mock = static::createUnversionedApiMockBuilder('api-versions');
|
||||
$mock->matchMethod(RequestMethod::GET)
|
||||
->matchExactQuery(['param' => 'value'])
|
||||
->reply(200)
|
||||
->withBody($json);
|
||||
|
||||
$client = TestClientFactory::createClient($mock->getClient());
|
||||
$client->customMethods->register(
|
||||
'apiVersions',
|
||||
static function (RequestSenderInterface $sender, array $data, array $context) {
|
||||
return $sender->send(
|
||||
RequestMethod::GET,
|
||||
sprintf('https://%s/api/api-versions', $sender->host()),
|
||||
$data
|
||||
)['versions'];
|
||||
}
|
||||
);
|
||||
$apiVersions = $client->customMethods->call('apiVersions', ['param' => 'value']);
|
||||
|
||||
self::assertEquals(["3.0", "4.0", "5.0"], $apiVersions);
|
||||
}
|
||||
|
||||
public function testCallWithParamsPost(): void
|
||||
{
|
||||
$json = <<<'EOF'
|
||||
{
|
||||
"success": true
|
||||
}
|
||||
EOF;
|
||||
|
||||
$mock = static::createApiMockBuilder('some-method');
|
||||
$mock->matchMethod(RequestMethod::POST)
|
||||
->matchExactFormData(['param' => 'value'])
|
||||
->reply(200)
|
||||
->withBody($json);
|
||||
|
||||
$client = TestClientFactory::createClient($mock->getClient());
|
||||
$client->customMethods->register('someMethod', new CustomApiMethod(RequestMethod::POST, 'some-method'));
|
||||
$response = $client->customMethods->call('someMethod', ['param' => 'value']);
|
||||
|
||||
self::assertEquals(['success' => true], $response);
|
||||
}
|
||||
|
||||
public function testCallMagic(): void
|
||||
{
|
||||
$json = <<<'EOF'
|
||||
{
|
||||
"success": true,
|
||||
"versions": [
|
||||
"3.0",
|
||||
"4.0",
|
||||
"5.0"
|
||||
]
|
||||
}
|
||||
EOF;
|
||||
|
||||
$mock = static::createUnversionedApiMockBuilder('api-versions');
|
||||
$mock->matchMethod(RequestMethod::GET)
|
||||
->reply(200)
|
||||
->withBody($json);
|
||||
|
||||
$client = TestClientFactory::createClient($mock->getClient());
|
||||
$client->customMethods->register(
|
||||
'apiVersions',
|
||||
static function (RequestSenderInterface $sender, array $data, array $context) {
|
||||
return $sender->send(
|
||||
RequestMethod::GET,
|
||||
sprintf('https://%s/api/api-versions', $sender->host())
|
||||
)['versions'];
|
||||
}
|
||||
);
|
||||
$apiVersions = $client->customMethods->apiVersions();
|
||||
|
||||
self::assertEquals(["3.0", "4.0", "5.0"], $apiVersions);
|
||||
}
|
||||
|
||||
public function testCallNoMethod(): void
|
||||
{
|
||||
$this->expectException(HandlerException::class);
|
||||
$client = TestClientFactory::createClient(static::noSendingMock());
|
||||
$client->customMethods->call('nonexistent');
|
||||
}
|
||||
|
||||
public function testCallMagicNoMethod(): void
|
||||
{
|
||||
$this->expectException(HandlerException::class);
|
||||
$client = TestClientFactory::createClient(static::noSendingMock());
|
||||
$client->customMethods->nonexistent();
|
||||
}
|
||||
|
||||
public function testCallMagicInvalidData(): void
|
||||
{
|
||||
$this->expectException(HandlerException::class);
|
||||
$client = TestClientFactory::createClient(static::noSendingMock());
|
||||
$client->customMethods->register('failure', new CustomApiMethod(RequestMethod::GET, 'failure'));
|
||||
$client->customMethods->failure(0, []);
|
||||
}
|
||||
|
||||
public function testCallMagicInvalidContext(): void
|
||||
{
|
||||
$this->expectException(HandlerException::class);
|
||||
$client = TestClientFactory::createClient(static::noSendingMock());
|
||||
$client->customMethods->register('failure', new CustomApiMethod(RequestMethod::GET, 'failure'));
|
||||
$client->customMethods->failure([], 0);
|
||||
}
|
||||
|
||||
private static function noSendingMock(): ClientInterface
|
||||
{
|
||||
return (new PockBuilder())->always()->throwClientException('No requests should be sent.')->getClient();
|
||||
}
|
||||
}
|
35
tests/utils/APIVersionsResponse.php
Normal file
35
tests/utils/APIVersionsResponse.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.3
|
||||
*
|
||||
* @category APIVersionsResponse
|
||||
* @package RetailCrm\TestUtils
|
||||
*/
|
||||
|
||||
namespace RetailCrm\TestUtils;
|
||||
|
||||
/**
|
||||
* Class APIVersionsResponse
|
||||
*
|
||||
* @category APIVersionsResponse
|
||||
* @package RetailCrm\TestUtils
|
||||
*/
|
||||
class APIVersionsResponse
|
||||
{
|
||||
/** @var bool */
|
||||
public $success;
|
||||
|
||||
/** @var string[] */
|
||||
public $versions;
|
||||
|
||||
/**
|
||||
* @param bool $success
|
||||
* @param string[] $versions
|
||||
*/
|
||||
public function __construct(bool $success, array $versions)
|
||||
{
|
||||
$this->success = $success;
|
||||
$this->versions = $versions;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user