9.6 KiB
Instantiation
There are several ways to instantiate the Client. Let's take look at them: from the simplest one to the most complex.
- Instantiation via
SimpleClientFactory
- Instantiation via
ClientFactory
- Instantiation via
ClientBuilder
Instantiation via SimpleClientFactory
It's the easiest way to instantiate an API client. You can use this method if you just want to use API client without thinking much about integration with the app or framework.
Example:
use RetailCrm\Api\Factory\SimpleClientFactory;
$client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'key');
That's it. You can use $client
to make requests. However, even this simple method allows you to pass the CacheItemPoolInterface
instance
(symfony/cache
is being used by default) or path to the cache directory. The cache is being used by the client to store
annotations which omits the necessity to parse them each time you send a request.
Example with the CacheItemPoolInterface
instance (we're using Redis as cache here):
use Symfony\Component\Cache\Adapter\RedisAdapter;
use RetailCrm\Api\Enum\CacheDirectories;
use RetailCrm\Api\Factory\SimpleClientFactory;
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$client = SimpleClientFactory::createWithCache(
'https://test.retailcrm.pro',
'key',
new RedisAdapter($redis, CacheDirectories::DIR_NAME)
);
CacheDirectories::DIR_NAME
is optional here. You can use any other namespace you want. By default, CacheDirectories::DIR_NAME
will be used as a namespace by default if the cache instance was created by the client automatically.
It's easier to use SimpleClientFactory::createWithCacheDir
if you just want to specify your directory for cache instead of
default temporary directory:
use RetailCrm\Api\Factory\SimpleClientFactory;
$client = SimpleClientFactory::createWithCacheDir('https://test.retailcrm.pro', 'key', __DIR__ . '/cache');
You can find more details about SimpleClientFactory
in the documentation:
Instantiation via ClientFactory
The main difference between SimpleClientFactory
and ClientFactory
is the fact that the second factory is stateful.
It allows you to better integrate this client with your application. Consider the following: you may just set all global client
dependencies into the ClientFactory
during the dependency container instantiation, and use the factory after that
to instantiate any number of clients you want.
Simple example
Here's an example of ClientFactory
usage without any integration with the framework or the app:
use RetailCrm\Api\Factory\ClientFactory;
use League\Event\EventDispatcher;
$eventDispatcher = new EventDispatcher();
$factory = new ClientFactory();
$factory
->setCacheDir('/tmp/retailcrm_cache')
->setEventDispatcher($eventDispatcher);
$client = $factory->createClient('https://test.retailcrm.pro', 'key');
Doesn't look that impressive, right? That's because ClientFactory
is not supposed to be used just like that.
It won't give you any benefits in comparison to SimpleClientFactory
with such usage.
Let's take a look at the more complex example. We'll use league/container
here to demonstrate how you can integrate
ClientFactory
with the DI you're using.
Abstract example
Let's assume that you have a specific service that should instantiate a Client for some internal purposes. Here it is:
namespace App\Services;
use RetailCrm\Api\Interfaces\ClientFactoryInterface;
use Throwable;
class ClientFactoryDependentService
{
/** @var ClientFactoryInterface */
private $clientFactory;
public function __construct(ClientFactoryInterface $clientFactory)
{
$this->clientFactory = $clientFactory;
}
public function isApiAccessible(string $apiUrl, string $apiKey): bool
{
try {
$client = $this->clientFactory->createClient($apiUrl, $apiKey);
$client->api->apiVersions();
return true;
} catch (Throwable $throwable) {
return false;
}
}
}
And you have a controller which returns a JSON response with the API availability flag. It requires the service above.
namespace App\Controller;
use Some\Framework\Specific\Annotations\Route;
use Some\Framework\Specific\Controller;
use Some\Framework\Specific\Response;
class HealthCheckController extends Controller
{
/** @var ClientFactoryDependentService */
private $service;
public function __construct(ClientFactoryDependentService $service)
{
$this->service = $service;
}
/**
* @Route("/healthcheck")
*/
public function healthCheckAction(): Response
{
return $this->responseJson([
'is_api_accessible' => $this->service->isApiAccessible($_GET['apiUrl'], $_GET['apiKey'])
]);
}
}
During the container configuration you configure the container to instantiate proper ClientFactory
once. After that
you can use the factory in any service you want. Here's an example with the league/container
:
use RetailCrm\Api\Factory\ClientFactory;
use RetailCrm\Api\Interfaces\ClientFactoryInterface;
use League\Container\Container;
use League\Event\EventDispatcher;
use Psr\Cache\CacheItemPoolInterface;
use Psr\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
use App\Services\ClientFactoryDependentService;
use App\Controller\HealthCheckController;
$container = new Container();
$container->add(CacheItemPoolInterface::class, new FilesystemAdapter('test_app'));
$container->add(EventDispatcherInterface::class, EventDispatcher::class);
$container->add(ClientFactoryInterface::class, ClientFactory::class)
->addMethodCalls([
'setCache' => [CacheItemPoolInterface::class],
'setEventDispatcher' => [EventDispatcherInterface::class],
]);
$container->add(ClientFactoryDependentService::class)->addArgument(ClientFactoryInterface::class);
$container->add(HealthCheckController::class)->addArgument(ClientFactoryDependentService::class);
That's it! Now your controller is using the underlying service to form a response. The service is using ClientFactory
which is being instantiated only once by the container. ClientFactory
can be injected into other services as well, and it
will be the same instance.
Real-world example (Symfony)
You can use this exact service definition to instantiate a ClientFactory
instance:
RetailCrm\Api\Interfaces\ClientFactoryInterface:
class: 'RetailCrm\Api\Factory\ClientFactory'
calls:
- setCacheDir: ['%kernel.cache_dir%']
- setEventDispatcher: ['@event_dispatcher']
That's it. Now you can autowire ClientFactoryInterface
in your services. It also allows you to mock the factory itself
in the tests using services_test.yml
.
You can find more details about ClientFactory
in the documentation:
Instantiation via ClientBuilder
ClientBuilder
is the most powerful, and most complex method of client instantiation. It allows you to replace any client's
external dependencies with your own implementations. Here's an example of ClientBuilder
usage:
use Http\Client\Curl\Client as CurlClient;
use League\Event\EventDispatcher;
use Nyholm\Psr7\Factory\Psr17Factory;
use RetailCrm\Api\Builder\ClientBuilder;
use RetailCrm\Api\Builder\FormEncoderBuilder;
use RetailCrm\Api\Component\Transformer\RequestTransformer;
use RetailCrm\Api\Component\Transformer\ResponseTransformer;
use RetailCrm\Api\Factory\ApiExceptionFactory;
use RetailCrm\Api\Factory\RequestPipelineFactory;
use RetailCrm\Api\Factory\ResponsePipelineFactory;
use RetailCrm\Api\Handler\Request\HeaderAuthenticatorHandler;
$eventDispatcher = new EventDispatcher();
$psr17Factory = new Psr17Factory();
$httpClient = new CurlClient();
$builder = new ClientBuilder();
$formEncoder = (new FormEncoderBuilder())->setCacheDir('cache')->build();
$client = $builder->setApiUrl('https://test.retailcrm.pro')
->setAuthenticatorHandler(new HeaderAuthenticatorHandler('apiKey'))
->setFormEncoder($formEncoder)
->setHttpClient($httpClient)
->setRequestTransformer(new RequestTransformer(
RequestPipelineFactory::createDefaultPipeline(
$formEncoder,
$psr17Factory, // PSR-17 UriFactoryInterface
$psr17Factory, // PSR-17 RequestFactoryInterface
$psr17Factory // PSR-17 StreamFactoryInterface
)
))
->setResponseTransformer(new ResponseTransformer(
ResponsePipelineFactory::createDefaultPipeline(
$formEncoder->getSerializer(),
new ApiExceptionFactory(),
$eventDispatcher
)
))->build();
You can find more details about this in the documentation: