Skip to content

Commit

Permalink
add Bpost and PostNL support
Browse files Browse the repository at this point in the history
  • Loading branch information
kenny-inventis committed May 23, 2022
1 parent 99b8fda commit 157d34e
Show file tree
Hide file tree
Showing 35 changed files with 1,646 additions and 0 deletions.
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"require": {
"php": ">=7.4",
"ext-mbstring": "*",
"ext-simplexml": "*",
"behat/transliterator": "^1.3",
"doctrine/event-manager": "^1.1",
"doctrine/orm": "^2.7",
Expand Down
92 changes: 92 additions & 0 deletions src/Client/Bpost/Client.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
<?php

declare(strict_types=1);

namespace Setono\SyliusPickupPointPlugin\Client\Bpost;

use Psr\Http\Client\ClientExceptionInterface;
use Psr\Http\Client\ClientInterface as HttpClientInterface;
use Psr\Http\Message\RequestFactoryInterface;
use Psr\Http\Message\StreamFactoryInterface;
use Safe\Exceptions\JsonException;
use Setono\SyliusPickupPointPlugin\Client\ClientInterface;
use Setono\SyliusPickupPointPlugin\Exception\RequestFailedException;
use Setono\SyliusPickupPointPlugin\Model\Query\ServicePointQueryInterface;
use function Safe\json_decode;
use function Safe\json_encode;
use const PHP_QUERY_RFC3986;

final class Client implements ClientInterface
{
private HttpClientInterface $httpClient;

private RequestFactoryInterface $requestFactory;

private StreamFactoryInterface $streamFactory;

private string $baseUrl;

public function __construct(
HttpClientInterface $httpClient,
RequestFactoryInterface $requestFactory,
StreamFactoryInterface $streamFactory,
string $baseUrl = 'https://pudo.bpost.be'
) {
$this->httpClient = $httpClient;
$this->requestFactory = $requestFactory;
$this->streamFactory = $streamFactory;
$this->baseUrl = $baseUrl;
}

/**
* @throws ClientExceptionInterface
* @throws JsonException
*/
private function get(string $endpoint, array $params = []): array
{
return $this->sendRequest('GET', $endpoint, $params);
}

/**
* @throws ClientExceptionInterface|JsonException
*/
private function sendRequest(string $method, string $endpoint, array $params = [], array $body = []): array
{
$url = $this->baseUrl . '/' . ltrim($endpoint, '/') . '?' . http_build_query($params, '', '&', PHP_QUERY_RFC3986);

$request = $this->requestFactory->createRequest($method, $url);

if (count($body) > 0) {
$request = $request->withBody($this->streamFactory->createStream(json_encode($body)));
}

$response = $this->httpClient->sendRequest($request);


if (200 !== $response->getStatusCode()) {
throw new RequestFailedException($request, $response, $response->getStatusCode());
}

$xml = simplexml_load_string($response->getBody()->getContents());

$data = [];
$poiList = $xml->PoiList->Poi ?? $xml->PickupPointList->Point ?? $xml->Poi;

if ($poiList !== null) {
foreach ($poiList as $poi) {
$poiData = json_decode(json_encode($poi), true);
$data[] = $poiData['Record'] ?? $poiData;
}
}

return $data;
}

/**
* @throws ClientExceptionInterface|JsonException
*/
public function locate(ServicePointQueryInterface $servicePointQuery): iterable
{
return $this->get($servicePointQuery->getEndPoint(), $servicePointQuery->toArray());
}
}
12 changes: 12 additions & 0 deletions src/Client/ClientInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace Setono\SyliusPickupPointPlugin\Client;

use Setono\SyliusPickupPointPlugin\Model\Query\ServicePointQueryInterface;

interface ClientInterface
{
public function locate(ServicePointQueryInterface $servicePointQuery): iterable;
}
95 changes: 95 additions & 0 deletions src/Client/PostNL/Client.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<?php

declare(strict_types=1);

namespace Setono\SyliusPickupPointPlugin\Client\PostNL;

use Psr\Http\Client\ClientExceptionInterface;
use Psr\Http\Client\ClientInterface as HttpClientInterface;
use Psr\Http\Message\RequestFactoryInterface;
use Psr\Http\Message\StreamFactoryInterface;
use Safe\Exceptions\JsonException;
use Setono\SyliusPickupPointPlugin\Client\ClientInterface;
use Setono\SyliusPickupPointPlugin\Exception\RequestFailedException;
use Setono\SyliusPickupPointPlugin\Model\Query\ServicePointQueryInterface;
use function Safe\json_decode;
use function Safe\json_encode;
use const PHP_QUERY_RFC3986;

final class Client implements ClientInterface
{
private HttpClientInterface $httpClient;

private RequestFactoryInterface $requestFactory;

private StreamFactoryInterface $streamFactory;

private string $baseUrl;

private string $apiKey;

public function __construct(
HttpClientInterface $httpClient,
RequestFactoryInterface $requestFactory,
StreamFactoryInterface $streamFactory,
string $apiKey,
string $baseUrl = 'https://api-sandbox.postnl.nl'
) {
$this->httpClient = $httpClient;
$this->requestFactory = $requestFactory;
$this->streamFactory = $streamFactory;
$this->baseUrl = $baseUrl;
$this->apiKey = $apiKey;
}

/**
* @throws ClientExceptionInterface
* @throws JsonException
*/
private function get(string $endpoint, array $params = []): array
{
return $this->sendRequest('GET', $endpoint, $params);
}

/**
* @throws ClientExceptionInterface|JsonException
*/
private function sendRequest(string $method, string $endpoint, array $params = [], array $body = []): array
{
$url = $this->baseUrl . '/' . ltrim($endpoint, '/') . '?' . http_build_query($params, '', '&', PHP_QUERY_RFC3986);

$request = $this->requestFactory->createRequest($method, $url);

if (count($body) > 0) {
$request = $request->withBody($this->streamFactory->createStream(json_encode($body)));
}

$request = $request->withHeader('apikey', $this->apiKey);
$request = $request->withHeader('accept', 'application/json');

$response = $this->httpClient->sendRequest($request);

if (200 !== $response->getStatusCode()) {
throw new RequestFailedException($request, $response, $response->getStatusCode());
}

$data = json_decode($response->getBody()->getContents(), true);

if (!isset($data['GetLocationsResult']['ResponseLocation']))
{
throw new \UnexpectedValueException(
"Expected field '['GetLocationsResult']['ResponseLocation']' to be set."
);
}

return $data['GetLocationsResult']['ResponseLocation'];
}

/**
* @throws ClientExceptionInterface|JsonException
*/
public function locate(ServicePointQueryInterface $servicePointQuery): iterable
{
return $this->get($servicePointQuery->getEndPoint(), $servicePointQuery->toArray());
}
}
74 changes: 74 additions & 0 deletions src/DependencyInjection/Compiler/RegisterFactoriesPass.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<?php

declare(strict_types=1);

namespace Setono\SyliusPickupPointPlugin\DependencyInjection\Compiler;

use Nyholm\Psr7\Factory\Psr17Factory;
use Psr\Http\Message\RequestFactoryInterface;
use Psr\Http\Message\StreamFactoryInterface;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;

final class RegisterFactoriesPass implements CompilerPassInterface
{
private const REQUEST_FACTORY_SUFFIX = 'request_factory';
private const STREAM_FACTORY_SUFFIX = 'stream_factory';
private const PSR17_FACTORY_SUFFIX = 'psr17_factory';

private const SERVICE_ID_FORMAT = '%s.%s';

private const SETONO_BPOST_PROVIDER = 'setono_bpost';
private const SETONO_POSTNL_PROVIDER = 'setono_postnl';

private const PROVIDERS = [
self::SETONO_BPOST_PROVIDER,
self::SETONO_POSTNL_PROVIDER,
];

public function process(ContainerBuilder $container): void
{
foreach (self::PROVIDERS as $PROVIDER) {
if (class_exists(Psr17Factory::class)) {
// this service is used later if the Psr17Factory exists. Else it will be automatically removed by Symfony
$container->register(sprintf(self::SERVICE_ID_FORMAT, $PROVIDER, self::PSR17_FACTORY_SUFFIX), Psr17Factory::class);
}

$factoryId = sprintf(self::SERVICE_ID_FORMAT, $PROVIDER, self::PSR17_FACTORY_SUFFIX);
$requestFactoryAlias = sprintf(self::SERVICE_ID_FORMAT, $PROVIDER, self::REQUEST_FACTORY_SUFFIX);
$this->registerFactory(
$container,
$requestFactoryAlias,
$requestFactoryAlias,
$factoryId,
RequestFactoryInterface::class
);

$streamFactoryAlias = sprintf(self::SERVICE_ID_FORMAT, $PROVIDER, self::STREAM_FACTORY_SUFFIX);
$this->registerFactory($container,
$streamFactoryAlias,
$streamFactoryAlias,
$factoryId,
StreamFactoryInterface::class
);
}
}

private function registerFactory(ContainerBuilder $container, string $parameter, string $service, string $factoryId, string $factoryInterface): void
{
if ($container->hasParameter($parameter)) {
if (!$container->has($container->getParameter($parameter))) {
throw new ServiceNotFoundException($container->getParameter($parameter));
}

$container->setAlias($service, $container->getParameter($parameter));
} elseif ($container->has($factoryInterface)) {
$container->setAlias($service, $factoryInterface);
} elseif ($container->has('nyholm.psr7.psr17_factory')) {
$container->setAlias($service, 'nyholm.psr7.psr17_factory');
} elseif (class_exists(Psr17Factory::class)) {
$container->setAlias($service, $factoryId);
}
}
}
33 changes: 33 additions & 0 deletions src/DependencyInjection/Compiler/RegisterHttpClientPass.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

declare(strict_types=1);

namespace Setono\SyliusPickupPointPlugin\DependencyInjection\Compiler;

use Buzz\Client\BuzzClientInterface;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;

final class RegisterHttpClientPass implements CompilerPassInterface
{
private const HTTP_CLIENT_PARAMETER_SERVICE_IDS = [
'setono_bpost.http_client' => 'setono_bpost.http_client',
'setono_postnl.http_client' => 'setono_postnl.http_client',
];

public function process(ContainerBuilder $container): void
{
foreach (self::HTTP_CLIENT_PARAMETER_SERVICE_IDS as $PARAMETER => $SERVICE_ID) {
if ($container->hasParameter($PARAMETER)) {
if (!$container->has($container->getParameter($PARAMETER))) {
throw new ServiceNotFoundException($container->getParameter($PARAMETER));
}

$container->setAlias($SERVICE_ID, $container->getParameter($SERVICE_ID));
} elseif ($container->has(BuzzClientInterface::class)) {
$container->setAlias($SERVICE_ID, BuzzClientInterface::class);
}
}
}
}
Loading

0 comments on commit 157d34e

Please sign in to comment.