feat(client): implement ArcadiaApiClient entry point, services, factories, logging and observability

This commit is contained in:
Jérémy Munsch 2026-09-14 19:26:26 +02:00
parent 0d9e89f919
commit d2ff816b0c
Signed by: forge-joniko
GPG key ID: D7EFE2F5CB6CC0E4
11 changed files with 1026 additions and 208 deletions

41
src/ArcadiaApiClient.php Normal file
View file

@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace Uwil\ArcadiaApiPhpClient;
use Uwil\ArcadiaApiPhpClient\Factory\ApiClientFactory;
use Uwil\ArcadiaApiPhpClient\Factory\ServiceFactory;
use Uwil\ArcadiaApiPhpClient\Lib\ApiConfig;
use Uwil\ArcadiaApiPhpClient\Service\BrandService;
use Uwil\ArcadiaApiPhpClient\Service\EmployeeService;
use Uwil\ArcadiaApiPhpClient\Service\IncoService;
class ArcadiaApiClient
{
private readonly ServiceFactory $serviceFactory;
public function __construct(
private readonly ApiConfig $apiConfig
) {
$apiClientFactory = new ApiClientFactory($this->apiConfig);
$apiClient = $apiClientFactory->createClient();
$this->serviceFactory = new ServiceFactory($apiClient);
}
public function employees(): EmployeeService
{
return $this->serviceFactory->createEmployeeService();
}
public function incoSheets(): IncoService
{
return $this->serviceFactory->createIncoService();
}
public function brands(): BrandService
{
return $this->serviceFactory->createBrandService();
}
}

View file

@ -0,0 +1,93 @@
<?php
declare(strict_types=1);
namespace Uwil\ArcadiaApiPhpClient\Factory;
use GuzzleHttp\Client;
use Microsoft\Kiota\Bundle\DefaultRequestAdapter;
use Microsoft\Kiota\Http\KiotaClientFactory;
use Microsoft\Kiota\Http\Middleware\KiotaMiddleware;
use Microsoft\Kiota\Http\Middleware\Options\RetryOption;
use Microsoft\Kiota\Http\Middleware\Options\UserAgentHandlerOption;
use Microsoft\Kiota\Http\Middleware\RetryHandler;
use Microsoft\Kiota\Http\Middleware\UserAgentHandler;
use Uwil\ArcadiaApiPhpClient\Auth\ApiMandatoryHeadersMiddleware;
use Uwil\ArcadiaApiPhpClient\Lib\ApiConfig;
use Uwil\ArcadiaApiPhpClient\Lib\LibProperties;
use Uwil\ArcadiaApiPhpClient\Logger\ApiLoggerMiddleware;
use Uwil\ArcadiaApiPhpClient\Observability\ObservabilityOption;
use Uwil\ArcadiaKiotaOpenApiClient\ApiClient;
/**
* @internal
*/
final class ApiClientFactory
{
public function __construct(
private ApiConfig $apiConfig
) {
}
public function createClient(): ApiClient
{
$libProperties = new LibProperties();
$uaOptions = new UserAgentHandlerOption();
$uaOptions->setProductName($libProperties->productName);
$uaOptions->setProductVersion($libProperties->productVersion);
$handlerStack = KiotaClientFactory::getDefaultHandlerStack();
if ($this->apiConfig->logger !== null) {
$handlerStack->push(
ApiLoggerMiddleware::log($this->apiConfig->logger),
ApiLoggerMiddleware::HANDLER_NAME
);
}
$handlerStack->push(
ApiMandatoryHeadersMiddleware::signRequest(
apiKey: $this->apiConfig->apiKey,
withSecret: $this->apiConfig->secret
),
ApiMandatoryHeadersMiddleware::HANDLER_NAME
);
$handlerStack->push(
KiotaMiddleware::userAgent($uaOptions),
UserAgentHandler::HANDLER_NAME
);
$handlerStack->push(
KiotaMiddleware::retry(
new RetryOption(
maxRetries: $this->apiConfig->maxRetries,
delaySeconds: $this->apiConfig->retryDelaySeconds
)
),
RetryHandler::HANDLER_NAME
);
$guzzleClient = new Client([
'base_uri' => $this->apiConfig->endpoint,
'timeout' => $this->apiConfig->curlTimeOut,
'connect_timeout' => min(5.0, $this->apiConfig->curlTimeOut),
'handler' => $handlerStack,
'verify' => ! $this->apiConfig->ignoreSelfSignedCert,
]);
$observabilityOption = new ObservabilityOption(
$libProperties,
$this->apiConfig->tracer
);
$adapter = DefaultRequestAdapter::builder()
->withGuzzleClient($guzzleClient)
->withObservabilityOption($observabilityOption)
->build();
$adapter->setBaseUrl($this->apiConfig->endpoint->__toString());
return new ApiClient($adapter);
}
}

View file

@ -0,0 +1,93 @@
<?php
declare(strict_types=1);
namespace Uwil\ArcadiaApiPhpClient\Factory;
use Uwil\ArcadiaApiPhpClient\Hydrator\BrandHydrator;
use Uwil\ArcadiaApiPhpClient\Hydrator\EmployeeHydrator;
use Uwil\ArcadiaApiPhpClient\Hydrator\IncoCompoHydrator;
use Uwil\ArcadiaApiPhpClient\Hydrator\IncoHydrator;
use Uwil\ArcadiaApiPhpClient\Hydrator\IncoShortHydrator;
use Uwil\ArcadiaApiPhpClient\Repository\BrandApiRepository;
use Uwil\ArcadiaApiPhpClient\Repository\EmployeeApiRepository;
use Uwil\ArcadiaApiPhpClient\Repository\IncoApiRepository;
use Uwil\ArcadiaApiPhpClient\Repository\IncoCompoApiRepository;
use Uwil\ArcadiaApiPhpClient\Repository\IncoShortApiRepository;
use Uwil\ArcadiaApiPhpClient\Service\ArcadiaBrandService;
use Uwil\ArcadiaApiPhpClient\Service\ArcadiaEmployeeService;
use Uwil\ArcadiaApiPhpClient\Service\ArcadiaIncoService;
use Uwil\ArcadiaApiPhpClient\Service\BrandService;
use Uwil\ArcadiaApiPhpClient\Service\EmployeeService;
use Uwil\ArcadiaApiPhpClient\Service\IncoService;
use Uwil\ArcadiaKiotaOpenApiClient\ApiClient;
/**
* @internal
*/
final class ServiceFactory
{
private ?EmployeeService $employeeService = null;
private ?IncoService $incoService = null;
private ?BrandService $brandService = null;
public function __construct(
private readonly ApiClient $apiClient
) {
}
public function createEmployeeService(): EmployeeService
{
return $this->employeeService ??= new ArcadiaEmployeeService(
new EmployeeApiRepository(
$this->apiClient,
new EmployeeHydrator()
)
);
}
public function createIncoService(): IncoService
{
return $this->incoService ??= new ArcadiaIncoService(
new IncoApiRepository(
$this->apiClient,
new IncoHydrator()
),
new IncoShortApiRepository(
$this->apiClient,
new IncoShortHydrator()
),
new IncoCompoApiRepository(
$this->apiClient,
new IncoCompoHydrator()
)
);
}
public function createBrandService(): BrandService
{
return $this->brandService ??= new ArcadiaBrandService(
new BrandApiRepository(
$this->apiClient,
new BrandHydrator()
)
);
}
public function employeeService(): EmployeeService
{
return $this->createEmployeeService();
}
public function incoService(): IncoService
{
return $this->createIncoService();
}
public function brandService(): BrandService
{
return $this->createBrandService();
}
}

View file

@ -0,0 +1,185 @@
<?php
declare(strict_types=1);
namespace Uwil\ArcadiaApiPhpClient\Logger;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Promise\Create;
use GuzzleHttp\Promise\PromiseInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Log\LoggerInterface;
use Uwil\ArcadiaApiPhpClient\Auth\ApiMandatoryHeadersMiddleware;
use Uwil\ArcadiaApiPhpClient\Lib\LibMiddleware;
/**
* @internal
*/
final class ApiLoggerMiddleware implements LibMiddleware
{
public const string HANDLER_NAME = 'Arcadia API Logger';
public const string HEADER_AUTHORIZATION = 'Authorization';
/** @var string[] */
public const array SENSITIVE_HEADERS = [
self::HEADER_AUTHORIZATION,
ApiMandatoryHeadersMiddleware::HEADER_SIGNATURE,
ApiMandatoryHeadersMiddleware::HEADER_CLIENT_IDENTIFIER,
];
/**
* Creates the middleware handler closure.
*/
public static function log(LoggerInterface $logger): callable
{
return static function (callable $handler) use ($logger): self {
return new self($handler, $logger);
};
}
/**
* @param callable(RequestInterface, array<string, mixed>): PromiseInterface $nextHandler Next handler to invoke.
*/
public function __construct(
private $nextHandler,
private readonly LoggerInterface $logger,
) {
}
/** @param array<string, mixed> $options */
public function __invoke(RequestInterface $request, array $options): PromiseInterface
{
$fn = $this->nextHandler;
$startTime = hrtime(true);
return $fn($request, $options)->then(
function (ResponseInterface $response) use ($request, $startTime): ResponseInterface {
$durationMs = (hrtime(true) - $startTime) / 1_000_000;
$this->logResponse($request, $response, $durationMs);
return $response;
},
function (\Throwable $reason) use ($request, $startTime): PromiseInterface {
$durationMs = (hrtime(true) - $startTime) / 1_000_000;
$this->logFailure($request, $reason, $durationMs);
return Create::rejectionFor($reason);
}
);
}
private function logResponse(RequestInterface $request, ResponseInterface $response, float $durationMs): void
{
$statusCode = $response->getStatusCode();
$roundedDuration = round($durationMs, 2);
$context = [
'method' => $request->getMethod(),
'uri' => (string) $request->getUri(),
'status_code' => $statusCode,
'duration_ms' => $roundedDuration,
'headers' => $this->sanitizeHeaders($request->getHeaders()),
];
$message = sprintf(
'Arcadia API [%s] %s -> %d %s (%.2fms)',
$request->getMethod(),
(string) $request->getUri(),
$statusCode,
$response->getReasonPhrase(),
$roundedDuration
);
if ($statusCode >= 500) {
$this->logger->error($message, $context);
} elseif ($statusCode >= 400) {
$this->logger->warning($message, $context);
} else {
$this->logger->info($message, $context);
}
}
private function logFailure(RequestInterface $request, \Throwable $reason, float $durationMs): void
{
$roundedDuration = round($durationMs, 2);
$context = [
'method' => $request->getMethod(),
'uri' => (string) $request->getUri(),
'duration_ms' => $roundedDuration,
'headers' => $this->sanitizeHeaders($request->getHeaders()),
'error_class' => $reason::class,
'error_message' => $reason->getMessage(),
];
if ($reason instanceof RequestException && $reason->getResponse() !== null) {
$response = $reason->getResponse();
$statusCode = $response->getStatusCode();
$context['status_code'] = $statusCode;
$message = sprintf(
'Arcadia API [%s] %s -> %d %s (%.2fms)',
$request->getMethod(),
(string) $request->getUri(),
$statusCode,
$response->getReasonPhrase(),
$roundedDuration
);
if ($statusCode >= 500) {
$this->logger->error($message, $context);
} else {
$this->logger->warning($message, $context);
}
return;
}
$message = sprintf(
'Arcadia API [%s] %s -> Failed: %s (%.2fms)',
$request->getMethod(),
(string) $request->getUri(),
$reason->getMessage(),
$roundedDuration
);
$this->logger->error($message, $context);
}
/**
* @param array<array-key, array<string>> $headers
* @return array<string, string>
*/
private function sanitizeHeaders(array $headers): array
{
$sanitized = [];
foreach ($headers as $name => $values) {
$stringName = (string) $name;
$value = implode(', ', $values);
if ($this->isSensitiveHeader($stringName)) {
$sanitized[$stringName] = strlen($value) > 8
? substr($value, 0, 4) . '***[REDACTED]'
: '[REDACTED]';
} else {
$sanitized[$stringName] = $value;
}
}
return $sanitized;
}
private function isSensitiveHeader(string $headerName): bool
{
foreach (self::SENSITIVE_HEADERS as $sensitive) {
if (strcasecmp($headerName, $sensitive) === 0) {
return true;
}
}
return false;
}
}

View file

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace Uwil\ArcadiaApiPhpClient\Observability;
use Microsoft\Kiota\Http\Middleware\Options\ObservabilityOption as KiotaObservabilityOption;
use OpenTelemetry\API\Globals;
use OpenTelemetry\API\Trace\TracerInterface;
use Uwil\ArcadiaApiPhpClient\Lib\LibProperties;
final class ObservabilityOption extends KiotaObservabilityOption
{
public function __construct(
LibProperties $libProperties,
?TracerInterface $tracer = null
) {
$tracer ??= Globals::tracerProvider()->getTracer(
$libProperties->productName,
$libProperties->productVersion
);
self::setTracer($tracer);
}
public static function getTracerInstrumentationName(): string
{
return LibProperties::PACKAGE_NAME;
}
}

View file

@ -0,0 +1,51 @@
<?php
declare(strict_types=1);
namespace Uwil\ArcadiaApiPhpClient\Service;
use Uwil\ArcadiaApiPhpClient\Error\ApiError;
use Uwil\ArcadiaApiPhpClient\Lib\ApiDto\Dto\Brand;
use Uwil\ArcadiaApiPhpClient\Lib\ApiDto\Dto\PageOf;
use Uwil\ArcadiaApiPhpClient\Lib\ApiValueObject\BrandCode;
use Uwil\ArcadiaApiPhpClient\Lib\PageSize;
use Uwil\ArcadiaApiPhpClient\Lib\Pagination;
use Uwil\ArcadiaApiPhpClient\Lib\Pagination\StreamOf;
use Uwil\ArcadiaApiPhpClient\Repository\BrandRepository;
/**
* @internal
*/
final class ArcadiaBrandService implements BrandService
{
public function __construct(
private readonly BrandRepository $brandRepository
) {
}
/**
* @return PageOf<Brand>|ApiError
*/
public function getAll(
Pagination $pagination
): PageOf | ApiError {
return $this->brandRepository->selectAll($pagination);
}
/**
* @return StreamOf<Brand>
*/
public function streamAll(
PageSize $pageSize = new PageSize()
): StreamOf {
return new StreamOf(
fn (Pagination $pagination) => $this->getAll($pagination),
$pageSize,
);
}
public function findBy(BrandCode $code): Brand | null | ApiError
{
return $this->brandRepository->find($code);
}
}

View file

@ -0,0 +1,175 @@
<?php
declare(strict_types=1);
namespace Uwil\ArcadiaApiPhpClient\Service;
use Uwil\ArcadiaApiPhpClient\Error\ApiError;
use Uwil\ArcadiaApiPhpClient\Lib\ApiDto\Dto\Employee;
use Uwil\ArcadiaApiPhpClient\Lib\ApiDto\Dto\PageOf;
use Uwil\ArcadiaApiPhpClient\Lib\ApiSorter\EmployeeSorter;
use Uwil\ArcadiaApiPhpClient\Lib\ApiValueObject\ArcadiaEmployeeId;
use Uwil\ArcadiaApiPhpClient\Lib\ApiValueObject\SiteId;
use Uwil\ArcadiaApiPhpClient\Lib\PageSize;
use Uwil\ArcadiaApiPhpClient\Lib\Pagination;
use Uwil\ArcadiaApiPhpClient\Lib\Pagination\StreamOf;
use Uwil\ArcadiaApiPhpClient\Repository\EmployeeRepository;
/**
* @internal
*/
final class ArcadiaEmployeeService implements EmployeeService
{
public function __construct(
private readonly EmployeeRepository $employeeRepository
) {
}
/** @return StreamOf<Employee> */
public function streamAll(
PageSize $pageSize = new PageSize(),
?EmployeeSorter $sorter = null,
): StreamOf {
return new StreamOf(
fn (Pagination $pagination) => $this->getAll($pagination, $sorter),
$pageSize,
);
}
/** @return StreamOf<Employee> */
public function streamAllSince(
\DateTimeInterface $since,
PageSize $pageSize = new PageSize(),
\SortDirection $sortDirection = \SortDirection::Descending,
): StreamOf {
return new StreamOf(
fn (Pagination $pagination) => $this->getAllSince($pagination, $since, $sortDirection),
$pageSize,
);
}
/**
* @param SiteId[]|SiteId $sites
* @return StreamOf<Employee>
*/
public function streamAllOfSitesSince(
array | SiteId $sites,
\DateTimeInterface $since,
PageSize $pageSize = new PageSize(),
\SortDirection $sortDirection = \SortDirection::Descending,
): StreamOf {
return new StreamOf(
fn (Pagination $pagination) => $this->getAllOfSitesSince($pagination, $sites, $since, $sortDirection),
$pageSize,
);
}
/**
* @param SiteId[]|SiteId $sites
* @return StreamOf<Employee>
*/
public function streamAllOfSites(
array | SiteId $sites,
PageSize $pageSize = new PageSize(),
?EmployeeSorter $sorter = null,
): StreamOf {
return new StreamOf(
fn (Pagination $pagination) => $this->getAllOfSites($pagination, $sites, $sorter),
$pageSize,
);
}
/** @return PageOf<Employee>|ApiError */
public function getAllSince(
Pagination $pagination,
\DateTimeInterface $since,
\SortDirection $sortDirection = \SortDirection::Descending,
): PageOf | ApiError {
return $this->employeeRepository->selectAllSince(
$pagination,
$since,
$sortDirection
);
}
/** @return PageOf<Employee>|ApiError */
public function getAll(
Pagination $pagination,
?EmployeeSorter $sorter = null,
): PageOf | ApiError {
return $this->employeeRepository->selectAll(
$pagination,
$sorter,
);
}
/**
* @param SiteId[]|SiteId $sites
* @return PageOf<Employee>|ApiError
*/
public function getAllOfSitesSince(
Pagination $pagination,
array | SiteId $sites,
\DateTimeInterface $since,
\SortDirection $sortDirection = \SortDirection::Descending,
): PageOf | ApiError {
$sites = is_array($sites) ? $sites : [$sites];
if (count($sites) < 1) {
throw new \InvalidArgumentException('At least 1 site is required');
}
foreach ($sites as $site) {
if (! ($site instanceof SiteId)) {
throw new \InvalidArgumentException('Expected instance of ' . SiteId::class);
}
}
return $this->employeeRepository->selectAllHavingSitesSince(
$pagination,
$since,
$sites,
$sortDirection,
);
}
/**
* @param SiteId[]|SiteId $sites
* @return PageOf<Employee>|ApiError
*/
public function getAllOfSites(
Pagination $pagination,
array | SiteId $sites,
?EmployeeSorter $sorter = null,
): PageOf | ApiError {
$sites = is_array($sites) ? $sites : [$sites];
if (count($sites) < 1) {
throw new \InvalidArgumentException('At least 1 site is required');
}
foreach ($sites as $site) {
if (! ($site instanceof SiteId)) {
throw new \InvalidArgumentException('Expected instance of ' . SiteId::class);
}
}
if ($sorter !== null) {
return $this->employeeRepository->selectAllHavingSites(
$pagination,
$sites,
$sorter
);
}
return $this->employeeRepository->selectAllHavingSites(
$pagination,
$sites,
);
}
public function findBy(ArcadiaEmployeeId $id): Employee | null | ApiError
{
return $this->employeeRepository->find($id);
}
}

View file

@ -0,0 +1,210 @@
<?php
declare(strict_types=1);
namespace Uwil\ArcadiaApiPhpClient\Service;
use Uwil\ArcadiaApiPhpClient\Error\ApiError;
use Uwil\ArcadiaApiPhpClient\Lib\ApiDto\Dto\Inco;
use Uwil\ArcadiaApiPhpClient\Lib\ApiDto\Dto\IncoCompo;
use Uwil\ArcadiaApiPhpClient\Lib\ApiDto\Dto\PageOf;
use Uwil\ArcadiaApiPhpClient\Lib\ApiSorter\IncoSorter;
use Uwil\ArcadiaApiPhpClient\Lib\ApiValueObject\BrandCode;
use Uwil\ArcadiaApiPhpClient\Lib\ApiValueObject\IncoCompoId;
use Uwil\ArcadiaApiPhpClient\Lib\ApiValueObject\IncoId;
use Uwil\ArcadiaApiPhpClient\Lib\PageSize;
use Uwil\ArcadiaApiPhpClient\Lib\Pagination;
use Uwil\ArcadiaApiPhpClient\Lib\Pagination\StreamOf;
use Uwil\ArcadiaApiPhpClient\Repository\IncoCompoRepository;
use Uwil\ArcadiaApiPhpClient\Repository\IncoRepository;
use Uwil\ArcadiaApiPhpClient\Repository\IncoShortRepository;
/**
* @internal
*/
final class ArcadiaIncoService implements IncoService
{
public function __construct(
private readonly IncoRepository $incoRepository,
private readonly IncoShortRepository $incoShortRepository,
private readonly IncoCompoRepository $incoCompoRepository,
) {
}
/** @return StreamOf<Inco> */
public function streamAll(
PageSize $pageSize = new PageSize(),
?IncoSorter $sorter = null,
): StreamOf {
return new StreamOf(
fn (Pagination $pagination) => $this->getAll($pagination, $sorter),
$pageSize,
);
}
/**
* @param BrandCode[]|BrandCode $brands
* @return StreamOf<Inco>
*/
public function streamAllOfBrands(
array | BrandCode $brands,
PageSize $pageSize = new PageSize(),
?IncoSorter $sorter = null,
): StreamOf {
return new StreamOf(
fn (Pagination $pagination) => $this->getAllOfBrands($pagination, $brands, $sorter),
$pageSize,
);
}
/**
* @param BrandCode[]|BrandCode $brands
* @return StreamOf<Inco>
*/
public function streamAllOfBrandsSince(
array | BrandCode $brands,
\DateTimeInterface $since,
PageSize $pageSize = new PageSize(),
\SortDirection $sortDirection = \SortDirection::Descending,
): StreamOf {
return new StreamOf(
fn (Pagination $pagination) => $this->getAllOfBrandsSince($pagination, $brands, $since, $sortDirection),
$pageSize,
);
}
/**
* @param BrandCode[]|BrandCode $brands
* @return StreamOf<\Uwil\ArcadiaApiPhpClient\Lib\ApiDto\Dto\IncoShort>
*/
public function streamAllShortOfBrands(
array | BrandCode $brands,
PageSize $pageSize = new PageSize(),
?IncoSorter $sorter = null,
): StreamOf {
return new StreamOf(
fn (Pagination $pagination) => $this->getAllShortOfBrands($pagination, $brands, $sorter),
$pageSize,
);
}
/**
* @param BrandCode[]|BrandCode $brands
* @return PageOf<Inco>|ApiError
*/
public function getAllOfBrandsSince(
Pagination $pagination,
array | BrandCode $brands,
\DateTimeInterface $since,
\SortDirection $sortDirection = \SortDirection::Descending
): PageOf | ApiError {
return $this->incoRepository->selectAllHavingBrandsSince(
$pagination,
$since,
$this->validateBrands($brands),
$sortDirection
);
}
/**
* @param BrandCode[]|BrandCode $brands
* @return PageOf<Inco>|ApiError
*/
public function getAllOfBrands(
Pagination $pagination,
array | BrandCode $brands,
?IncoSorter $sorter = null
): PageOf | ApiError {
return $this->incoRepository->selectAllHavingBrands(
$pagination,
$this->validateBrands($brands),
$sorter
);
}
/**
* @param BrandCode[]|BrandCode $brands
* @return PageOf<\Uwil\ArcadiaApiPhpClient\Lib\ApiDto\Dto\IncoShort>|ApiError
*/
public function getAllShortOfBrands(
Pagination $pagination,
array | BrandCode $brands,
?IncoSorter $sorter = null
): PageOf | ApiError {
return $this->incoShortRepository->selectAllHavingBrands(
$pagination,
$this->validateBrands($brands),
$sorter
);
}
/** @return PageOf<\Uwil\ArcadiaApiPhpClient\Lib\ApiDto\Dto\Inco>|ApiError */
public function getAll(
Pagination $pagination,
?IncoSorter $sorter = null
): PageOf | ApiError {
return $this->incoRepository->selectAll(
$pagination,
$sorter
);
}
/**
* @return PageOf<\Uwil\ArcadiaApiPhpClient\Lib\ApiDto\Dto\Inco>|ApiError
*/
public function findByIncoIdLike(string $incoIdLike, BrandCode ...$onBrands): PageOf | ApiError
{
return $this->incoRepository->findWhere(
$incoIdLike,
$this->validateBrands($onBrands),
Pagination::firstPage(50)
);
}
/**
* @return PageOf<\Uwil\ArcadiaApiPhpClient\Lib\ApiDto\Dto\IncoShort>|ApiError
*/
public function findShortByIncoIdLike(string $incoIdLike, BrandCode ...$onBrands): PageOf | ApiError
{
return $this->incoShortRepository->findWhere(
$incoIdLike,
$this->validateBrands($onBrands),
Pagination::firstPage(50)
);
}
public function findBy(IncoId $id): Inco | null | ApiError
{
return $this->incoRepository->find($id);
}
public function findCompo(IncoId | IncoCompoId $id): IncoCompo | null | ApiError
{
if ($id instanceof IncoCompoId) {
return $this->incoCompoRepository->find($id);
}
return $this->incoCompoRepository->findFor($id);
}
/**
* @param BrandCode[]|BrandCode $brands
* @return BrandCode[]
*/
private function validateBrands(array | BrandCode $brands): array
{
$list = is_array($brands) ? $brands : [$brands];
if ($list === []) {
throw new \InvalidArgumentException('At least 1 brand code is required');
}
foreach ($list as $brand) {
if (! ($brand instanceof BrandCode)) {
throw new \InvalidArgumentException('Expected instance of ' . BrandCode::class);
}
}
return $list;
}
}

View file

@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace Uwil\ArcadiaApiPhpClient\Service;
use Uwil\ArcadiaApiPhpClient\Error\ApiError;
use Uwil\ArcadiaApiPhpClient\Lib\ApiDto\Dto\Brand;
use Uwil\ArcadiaApiPhpClient\Lib\ApiDto\Dto\PageOf;
use Uwil\ArcadiaApiPhpClient\Lib\ApiValueObject\BrandCode;
use Uwil\ArcadiaApiPhpClient\Lib\PageSize;
use Uwil\ArcadiaApiPhpClient\Lib\Pagination;
use Uwil\ArcadiaApiPhpClient\Lib\Pagination\StreamOf;
interface BrandService
{
/**
* @return PageOf<Brand>|ApiError
*/
public function getAll(
Pagination $pagination
): PageOf | ApiError;
/**
* @return StreamOf<Brand>
*/
public function streamAll(
PageSize $pageSize = new PageSize()
): StreamOf;
public function findBy(BrandCode $code): Brand | null | ApiError;
}

View file

@ -4,113 +4,85 @@ declare(strict_types=1);
namespace Uwil\ArcadiaApiPhpClient\Service;
use Uwil\ArcadiaApiPhpClient\Error\ApiError;
use Uwil\ArcadiaApiPhpClient\Lib\ApiDto\Dto\Employee;
use Uwil\ArcadiaApiPhpClient\Lib\ApiDto\Dto\PageOf;
use Uwil\ArcadiaApiPhpClient\Lib\ApiQueryBuilder\Direction;
use Uwil\ArcadiaApiPhpClient\Lib\ApiSorter\EmployeeSorter;
use Uwil\ArcadiaApiPhpClient\Lib\ApiValueObject\ArcadiaEmployeeId;
use Uwil\ArcadiaApiPhpClient\Lib\ApiValueObject\EmployeeSorter;
use Uwil\ArcadiaApiPhpClient\Lib\ApiValueObject\SiteId;
use Uwil\ArcadiaApiPhpClient\Lib\PageSize;
use Uwil\ArcadiaApiPhpClient\Lib\Pagination;
use Uwil\ArcadiaApiPhpClient\Repository\EmployeeRepository;
use Uwil\ArcadiaApiPhpClient\Lib\Pagination\StreamOf;
final class EmployeeService
interface EmployeeService
{
public function __construct(
private readonly EmployeeRepository $employeeRepository
) {
}
/** @return PageOf<Employee> */
public function getAllSince(
Pagination $pagination,
\DateTimeInterface $since,
Direction $sortDirection = Direction::DESC,
): PageOf {
return $this->employeeRepository->selectAllSince(
$pagination,
$since,
$sortDirection
);
}
/** @return PageOf<Employee> */
public function getAll(
Pagination $pagination,
/** @return StreamOf<Employee> */
public function streamAll(
PageSize $pageSize = new PageSize(),
?EmployeeSorter $sorter = null,
): PageOf {
return $this->employeeRepository->selectAll(
$pagination,
$sorter,
);
}
): StreamOf;
/** @return StreamOf<Employee> */
public function streamAllSince(
\DateTimeInterface $since,
PageSize $pageSize = new PageSize(),
\SortDirection $sortDirection = \SortDirection::Descending,
): StreamOf;
/**
* @param SiteId[]|SiteId $sites
* @return PageOf<Employee>
* @return StreamOf<Employee>
*/
public function streamAllOfSitesSince(
array | SiteId $sites,
\DateTimeInterface $since,
PageSize $pageSize = new PageSize(),
\SortDirection $sortDirection = \SortDirection::Descending,
): StreamOf;
/**
* @param SiteId[]|SiteId $sites
* @return StreamOf<Employee>
*/
public function streamAllOfSites(
array | SiteId $sites,
PageSize $pageSize = new PageSize(),
?EmployeeSorter $sorter = null,
): StreamOf;
/** @return PageOf<Employee>|ApiError */
public function getAllSince(
Pagination $pagination,
\DateTimeInterface $since,
\SortDirection $sortDirection = \SortDirection::Descending,
): PageOf | ApiError;
/** @return PageOf<Employee>|ApiError */
public function getAll(
Pagination $pagination,
?EmployeeSorter $sorter = null,
): PageOf | ApiError;
/**
* @param SiteId[]|SiteId $sites
* @return PageOf<Employee>|ApiError
*/
public function getAllOfSitesSince(
Pagination $pagination,
array | SiteId $sites,
\DateTimeInterface $since,
Direction $sortDirection = Direction::DESC,
): PageOf {
$sites = is_array($sites) ? $sites : [$sites];
if (count($sites) < 1) {
throw new \InvalidArgumentException('At least 1 site is required');
}
foreach ($sites as $site) {
if (! ($site instanceof SiteId)) {
throw new \InvalidArgumentException('Expected instance of ' . SiteId::class);
}
}
return $this->employeeRepository->selectAllHavingSitesSince(
$pagination,
$since,
$sites,
$sortDirection,
);
}
\SortDirection $sortDirection = \SortDirection::Descending,
): PageOf | ApiError;
/**
* @param SiteId[]|SiteId $sites
* @return PageOf<Employee>
* @return PageOf<Employee>|ApiError
*/
public function getAllOfSites(
Pagination $pagination,
array | SiteId $sites,
?EmployeeSorter $sorter = null,
): PageOf {
$sites = is_array($sites) ? $sites : [$sites];
): PageOf | ApiError;
if (count($sites) < 1) {
throw new \InvalidArgumentException('At least 1 site is required');
}
foreach ($sites as $site) {
if (! ($site instanceof SiteId)) {
throw new \InvalidArgumentException('Expected instance of ' . SiteId::class);
}
}
if ($sorter !== null) {
return $this->employeeRepository->selectAllHavingSites(
$pagination,
$sites,
$sorter
);
}
return $this->employeeRepository->selectAllHavingSites(
$pagination,
$sites,
);
}
public function findBy(ArcadiaEmployeeId $id): ?Employee
{
return $this->employeeRepository->find($id);
}
public function findBy(ArcadiaEmployeeId $id): Employee | null | ApiError;
}

View file

@ -4,169 +4,105 @@ declare(strict_types=1);
namespace Uwil\ArcadiaApiPhpClient\Service;
use Uwil\ArcadiaApiPhpClient\Error\ApiError;
use Uwil\ArcadiaApiPhpClient\Lib\ApiDto\Dto\Inco;
use Uwil\ArcadiaApiPhpClient\Lib\ApiDto\Dto\IncoCompo;
use Uwil\ArcadiaApiPhpClient\Lib\ApiDto\Dto\PageOf;
use Uwil\ArcadiaApiPhpClient\Lib\ApiQueryBuilder\Direction;
use Uwil\ArcadiaApiPhpClient\Lib\ApiSorter\IncoSorter;
use Uwil\ArcadiaApiPhpClient\Lib\ApiValueObject\BrandCode;
use Uwil\ArcadiaApiPhpClient\Lib\ApiValueObject\IncoCompoId;
use Uwil\ArcadiaApiPhpClient\Lib\ApiValueObject\IncoId;
use Uwil\ArcadiaApiPhpClient\Lib\ApiValueObject\IncoSorter;
use Uwil\ArcadiaApiPhpClient\Lib\PageSize;
use Uwil\ArcadiaApiPhpClient\Lib\Pagination;
use Uwil\ArcadiaApiPhpClient\Repository\IncoCompoRepository;
use Uwil\ArcadiaApiPhpClient\Repository\IncoRepository;
use Uwil\ArcadiaApiPhpClient\Repository\IncoShortRepository;
use Uwil\ArcadiaApiPhpClient\Lib\Pagination\StreamOf;
final class IncoService
interface IncoService
{
public function __construct(
private readonly IncoRepository $incoRepository,
private readonly IncoShortRepository $incoShortRepository,
private readonly IncoCompoRepository $incoCompoRepository,
) {
}
/** @return StreamOf<\Uwil\ArcadiaApiPhpClient\Lib\ApiDto\Dto\Inco> */
public function streamAll(
PageSize $pageSize = new PageSize(),
?IncoSorter $sorter = null,
): StreamOf;
/**
* @param BrandCode[]|BrandCode $brands
* @return PageOf<Inco>
* @return StreamOf<\Uwil\ArcadiaApiPhpClient\Lib\ApiDto\Dto\Inco>
*/
public function streamAllOfBrands(
array | BrandCode $brands,
PageSize $pageSize = new PageSize(),
?IncoSorter $sorter = null,
): StreamOf;
/**
* @param BrandCode[]|BrandCode $brands
* @return StreamOf<\Uwil\ArcadiaApiPhpClient\Lib\ApiDto\Dto\Inco>
*/
public function streamAllOfBrandsSince(
array | BrandCode $brands,
\DateTimeInterface $since,
PageSize $pageSize = new PageSize(),
\SortDirection $sortDirection = \SortDirection::Descending,
): StreamOf;
/**
* @param BrandCode[]|BrandCode $brands
* @return StreamOf<\Uwil\ArcadiaApiPhpClient\Lib\ApiDto\Dto\IncoShort>
*/
public function streamAllShortOfBrands(
array | BrandCode $brands,
PageSize $pageSize = new PageSize(),
?IncoSorter $sorter = null,
): StreamOf;
/**
* @param BrandCode[]|BrandCode $brands
* @return PageOf<Inco>|ApiError
*/
public function getAllOfBrandsSince(
Pagination $pagination,
array | BrandCode $brands,
\DateTimeInterface $since,
Direction $sortDirection = Direction::DESC
): PageOf {
$brands = is_array($brands) ? $brands : [$brands];
if (count($brands) < 1) {
throw new \InvalidArgumentException('At least 1 brand code is required');
}
foreach ($brands as $brand) {
if (! ($brand instanceof BrandCode)) {
throw new \InvalidArgumentException('Expected instance of ' . BrandCode::class);
}
}
return $this->incoRepository->selectAllHavingBrandsSince(
$pagination,
$since,
$brands,
$sortDirection
);
}
\SortDirection $sortDirection = \SortDirection::Descending
): PageOf | ApiError;
/**
* @param BrandCode[]|BrandCode $brands
* @return PageOf<Inco>
* @return PageOf<\Uwil\ArcadiaApiPhpClient\Lib\ApiDto\Dto\Inco>|ApiError
*/
public function getAllOfBrands(
Pagination $pagination,
array | BrandCode $brands,
?IncoSorter $sorter = null
): PageOf {
$brands = is_array($brands) ? $brands : [$brands];
if (count($brands) < 1) {
throw new \InvalidArgumentException('At least 1 brand code is required');
}
foreach ($brands as $brand) {
if (! ($brand instanceof BrandCode)) {
throw new \InvalidArgumentException('Expected instance of ' . BrandCode::class);
}
}
return $this->incoRepository->selectAllHavingBrands(
$pagination,
$brands,
$sorter
);
}
): PageOf | ApiError;
/**
* @param BrandCode[]|BrandCode $brands
* @return PageOf<\Uwil\ArcadiaApiPhpClient\Lib\ApiDto\Dto\IncoShort>
* @return PageOf<\Uwil\ArcadiaApiPhpClient\Lib\ApiDto\Dto\IncoShort>|ApiError
*/
public function getAllShortOfBrands(
Pagination $pagination,
array | BrandCode $brands,
?IncoSorter $sorter = null
): PageOf {
$brands = is_array($brands) ? $brands : [$brands];
): PageOf | ApiError;
if (count($brands) < 1) {
throw new \InvalidArgumentException('At least 1 brand code is required');
}
foreach ($brands as $brand) {
if (! ($brand instanceof BrandCode)) {
throw new \InvalidArgumentException('Expected instance of ' . BrandCode::class);
}
}
return $this->incoShortRepository->selectAllHavingBrands(
$pagination,
$brands,
$sorter
);
}
/** @return PageOf<Inco> */
/** @return PageOf<\Uwil\ArcadiaApiPhpClient\Lib\ApiDto\Dto\Inco>|ApiError */
public function getAll(
Pagination $pagination,
?IncoSorter $sorter = null
): PageOf {
): PageOf | ApiError;
return $this->incoRepository->selectAll(
$pagination,
$sorter
);
}
/**
* @return PageOf<\Uwil\ArcadiaApiPhpClient\Lib\ApiDto\Dto\Inco>|ApiError
*/
public function findByIncoIdLike(string $incoIdLike, BrandCode ...$onBrands): PageOf | ApiError;
/** @return PageOf<Inco> */
public function findByIncoIdLike(string $incoIdLike, BrandCode ...$onBrands): PageOf
{
$pagination = Pagination::firstPage(50);
/**
* @return PageOf<\Uwil\ArcadiaApiPhpClient\Lib\ApiDto\Dto\IncoShort>|ApiError
*/
public function findShortByIncoIdLike(string $incoIdLike, BrandCode ...$onBrands): PageOf | ApiError;
if (count($onBrands) < 1) {
throw new \InvalidArgumentException('At least 1 brand code is required');
}
public function findBy(IncoId $id): Inco | null | ApiError;
return $this->incoRepository->findWhere(
$incoIdLike,
$onBrands,
$pagination
);
}
/** @return PageOf<\Uwil\ArcadiaApiPhpClient\Lib\ApiDto\Dto\IncoShort> */
public function findShortByIncoIdLike(string $incoIdLike, BrandCode ...$onBrands): PageOf
{
$pagination = Pagination::firstPage(50);
if (count($onBrands) < 1) {
throw new \InvalidArgumentException('At least 1 brand code is required');
}
return $this->incoShortRepository->findWhere(
$incoIdLike,
$onBrands,
$pagination
);
}
public function findBy(IncoId $id): ?Inco
{
return $this->incoRepository->find($id);
}
public function findCompo(IncoId | IncoCompoId $id): ?IncoCompo
{
if ($id instanceof IncoCompoId) {
return $this->incoCompoRepository->find($id);
}
return $this->incoCompoRepository->findFor($id);
}
public function findCompo(IncoId | IncoCompoId $id): IncoCompo | null | ApiError;
}