From 9845262e621ab88d96cc9904923d5a51ddb1e348 Mon Sep 17 00:00:00 2001 From: Sebastian Krupinski Date: Fri, 19 Jun 2026 23:45:41 -0400 Subject: [PATCH] refactor: remove sources selector in backend Signed-off-by: Sebastian Krupinski --- lib/Controllers/DefaultController.php | 194 ++++++-------- lib/Manager.php | 360 ++++++++++++-------------- 2 files changed, 251 insertions(+), 303 deletions(-) diff --git a/lib/Controllers/DefaultController.php b/lib/Controllers/DefaultController.php index bed3b4a..0371e24 100644 --- a/lib/Controllers/DefaultController.php +++ b/lib/Controllers/DefaultController.php @@ -24,9 +24,6 @@ use KTXF\Resource\Identifier\ResourceIdentifier; use KTXF\Resource\Identifier\ResourceIdentifiers; use KTXF\Resource\Identifier\ServiceIdentifier; use KTXF\Resource\Provider\ResourceServiceLocationInterface; -use KTXF\Resource\Selector\CollectionSelector; -use KTXF\Resource\Selector\ServiceSelector; -use KTXF\Resource\Selector\SourceSelector; use KTXF\Routing\Attributes\AuthenticatedRoute; use KTXM\MailManager\Manager; use Psr\Log\LoggerInterface; @@ -41,6 +38,7 @@ class DefaultController extends ControllerAbstract { private const ERR_MISSING_SOURCES = 'Missing parameter: sources'; private const ERR_MISSING_TARGET = 'Missing parameter: target'; private const ERR_MISSING_TARGETS = 'Missing parameter: targets'; + private const ERR_MISSING_SENDER = 'Missing parameter: sender'; private const ERR_INVALID_OPERATION = 'Invalid operation: '; private const ERR_INVALID_PROVIDER = 'Invalid parameter: provider must be a string'; private const ERR_INVALID_SERVICE = 'Invalid parameter: service must be a string'; @@ -48,6 +46,7 @@ class DefaultController extends ControllerAbstract { private const ERR_INVALID_SOURCES = 'Invalid parameter: sources must be an array'; private const ERR_INVALID_TARGET = 'Invalid parameter: target must be an array'; private const ERR_INVALID_TARGETS = 'Invalid parameter: targets must be an array'; + private const ERR_INVALID_SENDER = 'Invalid parameter: sender must be a string'; private const ERR_INVALID_DATA = 'Invalid parameter: data must be an array'; public function __construct( @@ -172,40 +171,48 @@ class DefaultController extends ControllerAbstract { private function providerList(string $tenantId, string $userId, array $data): mixed { - $sources = null; - if (isset($data['sources']) && is_array($data['sources'])) { - $sources = new SourceSelector(); - $sources->jsonDeserialize($data['sources']); - } + if (isset($data['targets'])) { + if (!is_array($data['targets'])) { + throw new InvalidArgumentException(self::ERR_INVALID_TARGETS); + } + + foreach ($data['targets'] as $target) { + if (!is_string($target)) { + throw new InvalidArgumentException(self::ERR_INVALID_TARGETS); + } + } + } - return $this->manager->providerList($tenantId, $userId, $sources); + return $this->manager->providerList($tenantId, $userId, $data['targets'] ?? []); } private function providerFetch(string $tenantId, string $userId, array $data): mixed { - if (!isset($data['identifier'])) { - throw new InvalidArgumentException(self::ERR_MISSING_IDENTIFIER); + if (!isset($data['target'])) { + throw new InvalidArgumentException(self::ERR_MISSING_TARGET); } - if (!is_string($data['identifier'])) { - throw new InvalidArgumentException(self::ERR_INVALID_IDENTIFIER); + if (!is_string($data['target'])) { + throw new InvalidArgumentException(self::ERR_INVALID_TARGET); } - return $this->manager->providerFetch($tenantId, $userId, $data['identifier']); + return $this->manager->providerFetch($tenantId, $userId, $data['target']); + } private function providerExtant(string $tenantId, string $userId, array $data): mixed { - if (!isset($data['sources'])) { - throw new InvalidArgumentException(self::ERR_MISSING_SOURCES); - } - if (!is_array($data['sources'])) { - throw new InvalidArgumentException(self::ERR_INVALID_SOURCES); - } - $sources = new SourceSelector(); - $sources->jsonDeserialize($data['sources']); + if (!isset($data['targets'])) { + throw new InvalidArgumentException(self::ERR_MISSING_TARGETS); + } + + foreach ($data['targets'] as $target) { + if (!is_string($target)) { + throw new InvalidArgumentException(self::ERR_INVALID_TARGETS); + } + } - return $this->manager->providerExtant($tenantId, $userId, $sources); + return $this->manager->providerExtant($tenantId, $userId, $data['targets']); } @@ -213,13 +220,17 @@ class DefaultController extends ControllerAbstract { private function serviceList(string $tenantId, string $userId, array $data): mixed { - $sources = null; - if (isset($data['sources']) && is_array($data['sources'])) { - $sources = new SourceSelector(); - $sources->jsonDeserialize($data['sources']); + $targets = null; + if (isset($data['targets']) && is_array($data['targets'])) { + $targets = ResourceIdentifiers::fromArray($data['targets']); + foreach ($targets as $target) { + if (!$target instanceof CollectionIdentifier && !$target instanceof ServiceIdentifier) { + throw new InvalidArgumentException('Invalid parameter: targets must contain provider:service, provider:service:collection, or provider:service:collection:entity identifiers'); + } + } } - return $this->manager->serviceList($tenantId, $userId, $sources); + return $this->manager->serviceList($tenantId, $userId, $targets); } @@ -243,16 +254,20 @@ class DefaultController extends ControllerAbstract { private function serviceExtant(string $tenantId, string $userId, array $data): mixed { - if (!isset($data['sources'])) { - throw new InvalidArgumentException(self::ERR_MISSING_SOURCES); + if (!isset($data['targets'])) { + throw new InvalidArgumentException(self::ERR_MISSING_TARGETS); } - if (!is_array($data['sources'])) { - throw new InvalidArgumentException(self::ERR_INVALID_SOURCES); + if (!is_array($data['targets'])) { + throw new InvalidArgumentException(self::ERR_INVALID_TARGETS); } - $sources = new SourceSelector(); - $sources->jsonDeserialize($data['sources']); - - return $this->manager->serviceExtant($tenantId, $userId, $sources); + $targets = ResourceIdentifiers::fromArray($data['targets']); + foreach ($targets as $target) { + if (!$target instanceof ServiceIdentifier) { + throw new InvalidArgumentException('Invalid parameter: targets must contain provider:service identifiers'); + } + } + + return $this->manager->serviceExtant($tenantId, $userId, $targets); } private function serviceCreate(string $tenantId, string $userId, array $data): mixed { @@ -404,14 +419,12 @@ class DefaultController extends ControllerAbstract { private function collectionList(string $tenantId, string $userId, array $data): mixed { $sources = null; if (isset($data['sources']) && is_array($data['sources'])) { - // TODO: Refactor to use identifiers directly $sources = ResourceIdentifiers::fromArray($data['sources']); foreach ($sources as $source) { if (!$source instanceof CollectionIdentifier && !$source instanceof ServiceIdentifier) { throw new InvalidArgumentException('Invalid parameter: sources must contain provider:service, provider:service:collection, or provider:service:collection:entity identifiers'); } } - $sources = $this->createSourceSelectorFromIdentifiers($sources); } $filter = $data['filter'] ?? null; @@ -428,7 +441,6 @@ class DefaultController extends ControllerAbstract { throw new InvalidArgumentException(self::ERR_INVALID_TARGETS); } - // TODO: Refactor to use identifiers directly $targetIdentifiers = ResourceIdentifiers::fromArray($data['targets']); foreach ($targetIdentifiers as $targetIdentifier) { if (!$targetIdentifier instanceof CollectionIdentifier) { @@ -436,16 +448,11 @@ class DefaultController extends ControllerAbstract { } } - $list = []; - foreach ($targetIdentifiers as $targetIdentifier) { - $list[(string)$targetIdentifier] = $this->manager->collectionFetch( - $tenantId, - $userId, - $targetIdentifier->provider(), - $targetIdentifier->service(), - $targetIdentifier->collection() - ); - } + $list = $this->manager->collectionFetch( + $tenantId, + $userId, + $targetIdentifier + ); return $list; } @@ -457,14 +464,12 @@ class DefaultController extends ControllerAbstract { throw new InvalidArgumentException(self::ERR_INVALID_TARGETS); } - // TODO: Refactor to use identifiers directly $sources = ResourceIdentifiers::fromArray($data['targets']); foreach ($sources as $source) { if (!$source instanceof CollectionIdentifier) { throw new InvalidArgumentException('Invalid parameter: targets must contain provider:service, provider:service:collection, or provider:service:collection:entity identifiers'); } } - $sources = $this->createSourceSelectorFromIdentifiers($sources); return $this->manager->collectionExtant($tenantId, $userId, $sources); } @@ -611,8 +616,6 @@ class DefaultController extends ControllerAbstract { throw new InvalidArgumentException('Invalid parameter: sources must contain provider:service:collection:entity identifiers'); } } - - $sources = $this->createSourceSelectorFromIdentifiers($sources); $filter = $data['filter'] ?? null; $sort = $data['sort'] ?? null; @@ -637,8 +640,6 @@ class DefaultController extends ControllerAbstract { } } - $sources = $this->createSourceSelectorFromIdentifiers($sources); - $filter = $data['filter'] ?? null; $sort = $data['sort'] ?? null; $range = $data['range'] ?? null; @@ -696,31 +697,39 @@ class DefaultController extends ControllerAbstract { } private function entityExtant(string $tenantId, string $userId, array $data): mixed { - if (!isset($data['sources'])) { - throw new InvalidArgumentException(self::ERR_MISSING_SOURCES); + if (!isset($data['targets'])) { + throw new InvalidArgumentException(self::ERR_MISSING_TARGETS); } - if (!is_array($data['sources'])) { - throw new InvalidArgumentException(self::ERR_INVALID_SOURCES); + if (!is_array($data['targets'])) { + throw new InvalidArgumentException(self::ERR_INVALID_TARGETS); } - $sources = new SourceSelector(); - $sources->jsonDeserialize($data['sources']); - - return $this->manager->entityExtant($tenantId, $userId, $sources); + $targets = ResourceIdentifiers::fromArray($data['targets']); + foreach ($targets as $target) { + if (!$target instanceof CollectionIdentifier && !$target instanceof EntityIdentifier) { + throw new InvalidArgumentException('Invalid parameter: targets must contain provider:service:collection or provider:service:collection:entity identifiers'); + } + } + + return $this->manager->entityExtant($tenantId, $userId, $targets); } private function entityDelta(string $tenantId, string $userId, array $data): mixed { - if (!isset($data['sources'])) { - throw new InvalidArgumentException(self::ERR_MISSING_SOURCES); + if (!isset($data['targets'])) { + throw new InvalidArgumentException(self::ERR_MISSING_TARGETS); } - if (!is_array($data['sources'])) { - throw new InvalidArgumentException(self::ERR_INVALID_SOURCES); + if (!is_array($data['targets'])) { + throw new InvalidArgumentException(self::ERR_INVALID_TARGETS); } - $sources = new SourceSelector(); - $sources->jsonDeserialize($data['sources']); - - return $this->manager->entityDelta($tenantId, $userId, $sources); + $targets = ResourceIdentifiers::fromArray($data['targets']); + foreach ($targets as $target) { + if (!$target instanceof CollectionIdentifier && !$target instanceof EntityIdentifier) { + throw new InvalidArgumentException('Invalid parameter: targets must contain provider:service:collection or provider:service:collection:signature identifiers'); + } + } + + return $this->manager->entityDelta($tenantId, $userId, $targets); } private function entityPatch(string $tenantId, string $userId, array $data): mixed { @@ -891,47 +900,4 @@ class DefaultController extends ControllerAbstract { return $results; } - private function createSourceSelectorFromIdentifiers(ResourceIdentifiers $identifiers): SourceSelector { - $sources = new SourceSelector(); - - foreach ($identifiers as $identifier) { - if (!$identifier instanceof ServiceIdentifier) { - throw new InvalidArgumentException('Invalid parameter: sources must contain provider:service, provider:service:collection, or provider:service:collection:entity identifiers'); - } - - $provider = $identifier->provider(); - $service = $identifier->service(); - - if (!isset($sources[$provider])) { - $sources[$provider] = new ServiceSelector(); - } - - $serviceSelector = $sources[$provider]; - if (!$serviceSelector instanceof ServiceSelector) { - throw new InvalidArgumentException('Invalid parameter: sources must contain provider:service:collection selectors'); - } - - if ($identifier instanceof ServiceIdentifier && !$identifier instanceof CollectionIdentifier) { - $serviceSelector[$service] = true; - continue; - } - - if (isset($serviceSelector[$service]) && $serviceSelector[$service] === true) { - continue; - } - - if (!isset($serviceSelector[$service])) { - $serviceSelector[$service] = new CollectionSelector(); - } - - $collectionSelector = $serviceSelector[$service]; - if (!$collectionSelector instanceof CollectionSelector) { - throw new InvalidArgumentException('Invalid parameter: sources must contain provider:service:collection selectors'); - } - - $collectionSelector[$identifier->collection()] = true; - } - - return $sources; - } } diff --git a/lib/Manager.php b/lib/Manager.php index c8c1010..2ed5b7a 100644 --- a/lib/Manager.php +++ b/lib/Manager.php @@ -34,10 +34,6 @@ use KTXF\Resource\Provider\ResourceServiceIdentityInterface; use KTXF\Resource\Provider\ResourceServiceLocationInterface; use KTXF\Resource\Range\RangeAnchorType; use KTXF\Resource\Range\RangeType; -use KTXF\Resource\Selector\CollectionSelector; -use KTXF\Resource\Selector\EntitySelector; -use KTXF\Resource\Selector\ServiceSelector; -use KTXF\Resource\Selector\SourceSelector; use KTXF\Resource\Sort\ISort; use Psr\Log\LoggerInterface; @@ -56,15 +52,13 @@ class Manager { /** * Retrieve available providers * - * @param SourceSelector|null $sources collection of provider identifiers + * @param ResourceIdentifiers|null $sources collection of provider identifiers * * @return array collection of available providers e.g. ['provider1' => IProvider, 'provider2' => IProvider] */ - public function providerList(string $tenantId, string $userId, ?SourceSelector $sources = null): array { - // determine filter from sources - $filter = ($sources !== null && $sources->identifiers() !== []) ? $sources->identifiers() : null; + public function providerList(string $tenantId, string $userId, array|null $targets = []): array { // retrieve providers from provider manager - return $this->providerManager->providers(ProviderBaseInterface::TYPE_MAIL, $filter); + return $this->providerManager->providers(ProviderBaseInterface::TYPE_MAIL, $targets ?: null); } /** @@ -77,27 +71,27 @@ class Manager { * @return ProviderBaseInterface * @throws InvalidArgumentException */ - public function providerFetch(string $tenantId, string $userId, string $provider): ProviderBaseInterface { + public function providerFetch(string $tenantId, string $userId, string $target): ProviderBaseInterface { // retrieve provider - $providers = $this->providerList($tenantId, $userId, new SourceSelector([$provider => true])); - if (!isset($providers[$provider])) { - throw new InvalidArgumentException("Provider '$provider' not found"); + $providers = $this->providerList($tenantId, $userId, [$target]); + if (!isset($providers[$target])) { + throw new InvalidArgumentException("Provider '$target' not found"); } - return $providers[$provider]; + return $providers[$target]; } /** * Confirm which providers are available * - * @param SourceSelector|null $sources collection of provider identifiers to confirm - * + * @param array $targets collection of provider identifiers to confirm + * * @return array collection of providers and their availability status e.g. ['provider1' => true, 'provider2' => false] */ - public function providerExtant(string $tenantId, string $userId, SourceSelector $sources): array { + public function providerExtant(string $tenantId, string $userId, array $targets): array { // determine which providers are available - $providersResolved = $this->providerList($tenantId, $userId, $sources); + $providersResolved = $this->providerList($tenantId, $userId, $targets); $providersAvailable = array_keys($providersResolved); - $providersUnavailable = array_diff($sources->identifiers(), $providersAvailable); + $providersUnavailable = array_diff($targets, $providersAvailable); // construct response data $responseData = array_merge( array_fill_keys($providersAvailable, true), @@ -111,18 +105,24 @@ class Manager { * * @param string $tenantId tenant identifier * @param string $userId user identifier - * @param SourceSelector|null $sources list of provider and service identifiers + * @param ResourceIdentifiers|null $targets list of provider and service identifiers * * @return array> collections of available services e.g. ['provider1' => ['service1' => IServiceBase], 'provider2' => ['service2' => IServiceBase]] */ - public function serviceList(string $tenantId, string $userId, ?SourceSelector $sources = null): array { + public function serviceList(string $tenantId, string $userId, ?ResourceIdentifiers $targets = null): array { // retrieve providers - $providers = $this->providerList($tenantId, $userId, $sources); + $providerFilter = $targets !== null ? $targets->providers() : null; + $providers = $this->providerList($tenantId, $userId, $providerFilter); // retrieve services for each provider $responseData = []; foreach ($providers as $provider) { - $serviceFilter = $sources[$provider->identifier()] instanceof ServiceSelector ? $sources[$provider->identifier()]->identifiers() : []; - $services = $provider->serviceList($tenantId, $userId, $serviceFilter); + if ($targets !== null) { + $servicesSelected = $targets?->byProvider($provider->identifier()) ?: []; + $servicesFilter = $servicesSelected->services(); + } else { + $servicesFilter = []; + } + $services = $provider->serviceList($tenantId, $userId, $servicesFilter); $responseData[$provider->identifier()] = $services; } return $responseData; @@ -154,24 +154,23 @@ class Manager { * * @param string $tenantId tenant identifier * @param string $userId user identifier - * @param SourceSelector|null $sources collection of provider and service identifiers to confirm - * + * @param ResourceIdentifiers $targets collection of provider and service identifiers to confirm + * * @return array collection of providers and their availability status e.g. ['provider1' => ['service1' => false], 'provider2' => ['service2' => true, 'service3' => true]] */ - public function serviceExtant(string $tenantId, string $userId, SourceSelector $sources): array { - // retrieve providers - $providers = $this->providerList($tenantId, $userId, $sources); - $providersRequested = $sources->identifiers(); + public function serviceExtant(string $tenantId, string $userId, ResourceIdentifiers $targets): array { + // retrieve available providers + $providersRequested = $targets->providers(); + $providers = $this->providerList($tenantId, $userId, $providersRequested); $providersUnavailable = array_diff($providersRequested, array_keys($providers)); - + // initialize response with unavailable providers $responseData = array_fill_keys($providersUnavailable, false); - + // retrieve services for each available provider - foreach ($providers as $provider) { - $serviceSelector = $sources[$provider->identifier()]; - $serviceAvailability = $provider->serviceExtant($tenantId, $userId, ...$serviceSelector->identifiers()); - $responseData[$provider->identifier()] = $serviceAvailability; + foreach ($providers as $providerId => $provider) { + $servicesRequested = $targets->byProvider($providerId)->services(); + $responseData[$providerId] = $provider->serviceExtant($tenantId, $userId, ...$servicesRequested); } return $responseData; } @@ -342,7 +341,7 @@ class Manager { string|null $location = null, string|null $secret = null ): \Generator { - $providers = $this->providerList($tenantId, $userId, $providerId !== null ? new SourceSelector([$providerId => true]) : null); + $providers = $this->providerList($tenantId, $userId, $providerId !== null ? [$providerId] : null); foreach ($providers as $currentProviderId => $provider) { if ($provider instanceof ProviderServiceDiscoverInterface === false) { @@ -459,26 +458,23 @@ class Manager { * * @param string $tenantId Tenant identifier * @param string|null $userId User identifier for context - * @param SourceSelector|null $sources Provider/service sources + * @param ResourceIdentifiers|null $targets Provider/service sources * @param IFilter|null $filter Collection filter * @param ISort|null $sort Collection sort * * @return array>> Collections grouped by provider/service */ - public function collectionList(string $tenantId, ?string $userId, ?SourceSelector $sources = null, ?IFilter $filter = null, ?ISort $sort = null): array { + public function collectionList(string $tenantId, ?string $userId, ?ResourceIdentifiers $targets = null, ?IFilter $filter = null, ?ISort $sort = null): array { // confirm that sources are provided - if ($sources === null) { - $sources = new SourceSelector([]); + if ($targets === null) { + $targets = new ResourceIdentifiers([]); } // retrieve providers - $providers = $this->providerList($tenantId, $userId, $sources); + $aggregateServices = $this->serviceList($tenantId, $userId, $targets); // retrieve services for each provider $responseData = []; - foreach ($providers as $provider) { - $serviceFilter = $sources[$provider->identifier()] instanceof ServiceSelector ? $sources[$provider->identifier()]->identifiers() : []; - /** @var ServiceBaseInterface[] $services */ - $services = $provider->serviceList($tenantId, $userId, $serviceFilter); - // retrieve collections for each service + // retrieve collections for each service + foreach ($aggregateServices as $services) { foreach ($services as $service) { if ($service->getEnabled() === false) { continue; @@ -499,67 +495,18 @@ class Manager { $collectionSort->condition($attribute, $direction); } } - $collections = $service->collectionList('', $collectionFilter, $collectionSort); - if ($collections !== []) { - $responseData[$provider->identifier()][$service->identifier()] = $collections; - } - } - } - return $responseData; - } - /** - * Check if collections exist - * - * @since 2025.05.01 - * - * @param string $tenantId Tenant identifier - * @param string|null $userId User identifier for context - * @param SourceSelector $sources Collection sources with identifiers - * - * @return array>> Existence map grouped by provider/service - */ - public function collectionExtant(string $tenantId, ?string $userId, SourceSelector $sources): array { - // retrieve available providers - $providers = $this->providerList($tenantId, $userId, $sources); - $providersRequested = $sources->identifiers(); - $providersUnavailable = array_diff($providersRequested, array_keys($providers)); - - // initialize response with unavailable providers - $responseData = array_fill_keys($providersUnavailable, false); - - // check services and collections for each available provider - foreach ($providers as $provider) { - // extract services for this provider - $serviceSelector = $sources[$provider->identifier()]; - $servicesRequested = $serviceSelector->identifiers(); - /** @var ServiceBaseInterface[] $servicesAvailable */ - $servicesAvailable = $provider->serviceList($tenantId, $userId, $servicesRequested); - $servicesUnavailable = array_diff($servicesRequested, array_keys($servicesAvailable)); - // mark unavailable services as false - if ($servicesUnavailable !== []) { - $responseData[$provider->identifier()] = array_fill_keys($servicesUnavailable, false); - } - // confirm collections for each available service - foreach ($servicesAvailable as $service) { - // omit disabled services - if ($service->getEnabled() === false) { - $responseData[$provider->identifier()][$service->identifier()] = false; - continue; + $collectionIdentifiers = $targets->byProvider($service->provider())->byService($service->identifier())->collections(); + if ($collectionIdentifiers !== []) { + foreach ($collectionIdentifiers as $collectionIdentifier) { + $collections = array_merge($collections ?? [], $service->collectionList($collectionIdentifier, $collectionFilter, $collectionSort)); + } + } else { + $collections = $service->collectionList('', $collectionFilter, $collectionSort); } - // extract collections requested for this service - $collectionSelector = $serviceSelector[$service->identifier()]; - $collectionsRequested = $collectionSelector instanceof CollectionSelector ? $collectionSelector->identifiers() : []; - if ($collectionsRequested === []) { - continue; + if ($collections !== []) { + $responseData[$service->provider()][$service->identifier()] = $collections; } - // check each requested collection - $collectionsAvailable = $service->collectionExtant(...$collectionsRequested); - $collectionsUnavailable = array_diff($collectionsRequested, array_keys($collectionsAvailable)); - $responseData[$provider->identifier()][$service->identifier()] = array_merge( - $collectionsAvailable, - array_fill_keys($collectionsUnavailable, false) - ); } } return $responseData; @@ -578,14 +525,62 @@ class Manager { * * @return CollectionBaseInterface|null */ - public function collectionFetch(string $tenantId, ?string $userId, string $providerId, string|int $serviceId, string|int $collectionId): ?CollectionBaseInterface { + public function collectionFetch(string $tenantId, ?string $userId, CollectionIdentifier $target): ?CollectionBaseInterface { // retrieve service - $service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId); - if ($service === null || $service->getEnabled() === false) { - return null; - } + $service = $this->serviceFetch($tenantId, $userId, $target->provider(), $target->service()); // retrieve collection - return $service->collectionFetch($collectionId); + return $service->collectionFetch($target->collection()); + } + + /** + * Check if collections exist + * + * @since 2025.05.01 + * + * @param string $tenantId Tenant identifier + * @param string|null $userId User identifier for context + * @param ResourceIdentifiers $targets Collection sources with identifiers + * + * @return array>> Existence map grouped by provider/service + */ + public function collectionExtant(string $tenantId, ?string $userId, ResourceIdentifiers $targets): array { + // retrieve available services grouped by provider + $aggregateServices = $this->serviceList($tenantId, $userId, $targets); + // initialize response with unavailable providers marked as false + $providersRequested = $targets->providers(); + $providersUnavailable = array_diff($providersRequested, array_keys($aggregateServices)); + $responseData = array_fill_keys($providersUnavailable, false); + + // check services and collections for each available provider + foreach ($aggregateServices as $providerId => $services) { + // mark unavailable services as false + $servicesRequested = $targets->byProvider($providerId)->services(); + $servicesUnavailable = array_diff($servicesRequested, array_keys($services)); + if ($servicesUnavailable !== []) { + $responseData[$providerId] = array_fill_keys($servicesUnavailable, false); + } + // confirm collections for each available service + foreach ($services as $service) { + // omit disabled services + if ($service->getEnabled() === false) { + $responseData[$providerId][$service->identifier()] = false; + continue; + } + // extract collections requested for this service + $collectionsRequested = $targets->byProvider($providerId)->byService($service->identifier())->collections(); + if ($collectionsRequested === []) { + continue; + } + // check each requested collection + $collectionsAvailable = $service->collectionExtant(...$collectionsRequested); + $collectionsUnavailable = array_diff($collectionsRequested, array_keys($collectionsAvailable)); + $responseData[$providerId][$service->identifier()] = array_merge( + $collectionsAvailable, + array_fill_keys($collectionsUnavailable, false) + ); + } + } + return $responseData; } /** @@ -730,31 +725,31 @@ class Manager { * * @param string $tenantId Tenant identifier * @param string $userId User identifier - * @param SourceSelector $sources Message sources with collection identifiers + * @param ResourceIdentifiers|null $targets Message sources with collection identifiers * @param array|null $filter Message filter * @param array|null $sort Message sort * @param array|null $range Message range/pagination * * @return array>>> Messages grouped by provider/service/collection */ - public function entityListBulk(string $tenantId, string $userId, SourceSelector $sources, array|null $filter = null, array|null $sort = null, array|null $range = null): array { - // retrieve providers - $providers = $this->providerList($tenantId, $userId, $sources); + public function entityListBulk(string $tenantId, string $userId, ?ResourceIdentifiers $targets = null, array|null $filter = null, array|null $sort = null, array|null $range = null): array { + // confirm that sources are provided + if ($targets === null) { + $targets = new ResourceIdentifiers([]); + } // retrieve services for each provider + $aggregateServices = $this->serviceList($tenantId, $userId, $targets); + // retrieve entities for each service $responseData = []; - foreach ($providers as $provider) { - // retrieve services for each provider - $serviceSelector = $sources[$provider->identifier()]; - $servicesSelected = $provider->serviceList($tenantId,$userId, $serviceSelector->identifiers()); + foreach ($aggregateServices as $services) { /** @var ServiceBaseInterface $service */ - foreach ($servicesSelected as $service) { + foreach ($services as $service) { // omit disabled services if ($service->getEnabled() === false) { continue; } // retrieve collections for each service - $collectionSelector = $serviceSelector[$service->identifier()]; - $collectionSelected = $collectionSelector instanceof CollectionSelector ? $collectionSelector->identifiers() : []; + $collectionSelected = $targets->byProvider($service->provider())->byService($service->identifier())->collections(); if ($collectionSelected === []) { $collections = $service->collectionList(''); $collectionSelected = array_map( @@ -806,7 +801,7 @@ class Manager { if ($entities === []) { continue; } - $responseData[$provider->identifier()][$service->identifier()][$collectionId] = $entities; + $responseData[$service->provider()][$service->identifier()][$collectionId] = $entities; } } } @@ -821,29 +816,29 @@ class Manager { * * @param string $tenantId Tenant identifier * @param string $userId User identifier - * @param SourceSelector $sources Message sources with collection identifiers + * @param ResourceIdentifiers|null $targets Message sources with collection identifiers * @param array|null $filter Message filter * @param array|null $sort Message sort * @param array|null $range Message range/pagination * * @return \Generator Yields each entity as it is retrieved */ - public function entityListStream(string $tenantId, string $userId, SourceSelector $sources, array|null $filter = null, array|null $sort = null, array|null $range = null): \Generator { - // retrieve providers - $providers = $this->providerList($tenantId, $userId, $sources); + public function entityListStream(string $tenantId, string $userId, ?ResourceIdentifiers $targets = null, array|null $filter = null, array|null $sort = null, array|null $range = null): \Generator { + // confirm that sources are provided + if ($targets === null) { + $targets = new ResourceIdentifiers([]); + } // retrieve services for each provider - foreach ($providers as $provider) { - $serviceSelector = $sources[$provider->identifier()]; - $servicesSelected = $provider->serviceList($tenantId, $userId, $serviceSelector->identifiers()); + $aggregateServices = $this->serviceList($tenantId, $userId, $targets); + foreach ($aggregateServices as $services) { /** @var ServiceBaseInterface $service */ - foreach ($servicesSelected as $service) { + foreach ($services as $service) { // omit disabled services if ($service->getEnabled() === false) { continue; } // retrieve collections for each service - $collectionSelector = $serviceSelector[$service->identifier()]; - $collectionSelected = $collectionSelector instanceof CollectionSelector ? $collectionSelector->identifiers() : []; + $collectionSelected = $targets->byProvider($service->provider())->byService($service->identifier())->collections(); if ($collectionSelected === []) { $collections = $service->collectionList(''); $collectionSelected = array_map( @@ -971,43 +966,36 @@ class Manager { * * @param string $tenantId Tenant identifier * @param string|null $userId User identifier for context - * @param SourceSelector $sources Message sources with identifiers + * @param ResourceIdentifiers $targets Message sources with identifiers * * @return array>>> Existence map grouped by provider/service/collection */ - public function entityExtant(string $tenantId, string $userId, SourceSelector $sources): array { - // confirm that sources are provided - if ($sources === null) { - $sources = new SourceSelector([]); - } - // retrieve available providers - $providers = $this->providerList($tenantId, $userId, $sources); - $providersRequested = $sources->identifiers(); - $providersUnavailable = array_diff($providersRequested, array_keys($providers)); - // initialize response with unavailable providers + public function entityExtant(string $tenantId, string $userId, ResourceIdentifiers $targets): array { + // retrieve available services grouped by provider + $aggregateServices = $this->serviceList($tenantId, $userId, $targets); + // initialize response with unavailable providers marked as false + $providersRequested = $targets->providers(); + $providersUnavailable = array_diff($providersRequested, array_keys($aggregateServices)); $responseData = array_fill_keys($providersUnavailable, false); + // check services, collections, and entities for each available provider - foreach ($providers as $provider) { - // extract services requested for this provider - $serviceSelector = $sources[$provider->identifier()]; - $servicesRequested = $serviceSelector->identifiers(); - /** @var ServiceBaseInterface[] $servicesAvailable */ - $servicesAvailable = $provider->serviceList($tenantId, $userId, $servicesRequested); - $servicesUnavailable = array_diff($servicesRequested, array_keys($servicesAvailable)); + foreach ($aggregateServices as $providerId => $services) { // mark unavailable services as false + $servicesRequested = $targets->byProvider($providerId)->services(); + $servicesUnavailable = array_diff($servicesRequested, array_keys($services)); if ($servicesUnavailable !== []) { - $responseData[$provider->identifier()] = array_fill_keys($servicesUnavailable, false); + $responseData[$providerId] = array_fill_keys($servicesUnavailable, false); } // check collections and entities for each available service - foreach ($servicesAvailable as $service) { + foreach ($services as $service) { // omit disabled services if ($service->getEnabled() === false) { - $responseData[$provider->identifier()][$service->identifier()] = false; + $responseData[$providerId][$service->identifier()] = false; continue; } // extract collections requested for this service - $collectionSelector = $serviceSelector[$service->identifier()]; - $collectionsRequested = $collectionSelector instanceof CollectionSelector ? $collectionSelector->identifiers() : []; + $serviceTargets = $targets->byProvider($providerId)->byService($service->identifier()); + $collectionsRequested = $serviceTargets->collections(); if ($collectionsRequested === []) { continue; } @@ -1017,19 +1005,18 @@ class Manager { $collectionExists = $service->collectionExtant((string)$collectionId); if (!$collectionExists) { // collection doesn't exist, mark as false - $responseData[$provider->identifier()][$service->identifier()][$collectionId] = false; + $responseData[$providerId][$service->identifier()][$collectionId] = false; continue; } - // extract entity identifiers from collection selector - $entitySelector = $collectionSelector[$collectionId]; - // handle both array of entity IDs and boolean true (meaning check if collection exists) - if ($entitySelector instanceof EntitySelector) { - // check specific entities within the collection - $responseData[$provider->identifier()][$service->identifier()][$collectionId] = $service->entityExtant($collectionId, ...$entitySelector->identifiers()); - } elseif ($entitySelector === true) { + // extract entity identifiers requested for this collection + $entitiesRequested = $serviceTargets->byCollection($collectionId)->entities(); + if ($entitiesRequested === []) { // just checking if collection exists (already confirmed above) - $responseData[$provider->identifier()][$service->identifier()][$collectionId] = true; + $responseData[$providerId][$service->identifier()][$collectionId] = true; + continue; } + // check specific entities within the collection + $responseData[$providerId][$service->identifier()][$collectionId] = $service->entityExtant($collectionId, ...$entitiesRequested); } } } @@ -1043,50 +1030,45 @@ class Manager { * * @param string $tenantId Tenant identifier * @param string|null $userId User identifier for context - * @param SourceSelector $sources Message sources with signatures + * @param ResourceIdentifiers $targets Message sources with signatures * * @return array>> Delta grouped by provider/service/collection */ - public function entityDelta(string $tenantId, string $userId, SourceSelector $sources): array { - // confirm that sources are provided - if ($sources === null) { - $sources = new SourceSelector([]); - } - // retrieve providers - $providers = $this->providerList($tenantId, $userId, $sources); - $providersRequested = $sources->identifiers(); - $providersUnavailable = array_diff($providersRequested, array_keys($providers)); - // initialize response with unavailable providers + public function entityDelta(string $tenantId, string $userId, ResourceIdentifiers $targets): array { + // retrieve available services grouped by provider + $aggregateServices = $this->serviceList($tenantId, $userId, $targets); + // initialize response with unavailable providers marked as false + $providersRequested = $targets->providers(); + $providersUnavailable = array_diff($providersRequested, array_keys($aggregateServices)); $responseData = array_fill_keys($providersUnavailable, false); + // iterate through available providers - foreach ($providers as $provider) { - // extract services requested for this provider - $serviceSelector = $sources[$provider->identifier()]; - $servicesRequested = $serviceSelector instanceof ServiceSelector ? $serviceSelector->identifiers() : []; - /** @var ServiceBaseInterface[] $services */ - $services = $provider->serviceList($tenantId, $userId, $servicesRequested); + foreach ($aggregateServices as $providerId => $services) { + // mark unavailable services as false + $servicesRequested = $targets->byProvider($providerId)->services(); $servicesUnavailable = array_diff($servicesRequested, array_keys($services)); if ($servicesUnavailable !== []) { - $responseData[$provider->identifier()] = array_fill_keys($servicesUnavailable, false); + $responseData[$providerId] = array_fill_keys($servicesUnavailable, false); } // iterate through available services foreach ($services as $service) { // omit disabled services if ($service->getEnabled() === false) { - $responseData[$provider->identifier()][$service->identifier()] = false; + $responseData[$providerId][$service->identifier()] = false; continue; } // extract collections requested for this service - $collectionSelector = $serviceSelector[$service->identifier()]; - $collectionsRequested = $collectionSelector instanceof CollectionSelector ? $collectionSelector->identifiers() : []; + $serviceTargets = $targets->byProvider($providerId)->byService($service->identifier()); + $collectionsRequested = $serviceTargets->collections(); if ($collectionsRequested === []) { - $responseData[$provider->identifier()][$service->identifier()] = false; + $responseData[$providerId][$service->identifier()] = false; continue; } // check delta for each requested collection foreach ($collectionsRequested as $collection) { - $entitySelector = $collectionSelector[$collection] ?? null; - $responseData[$provider->identifier()][$service->identifier()][$collection] = $service->entityDelta($collection, $entitySelector); + // signature for the collection is carried in the entity slot of the identifier + $signature = $serviceTargets->byCollection($collection)->entities()[0] ?? ''; + $responseData[$providerId][$service->identifier()][$collection] = $service->entityDelta($collection, $signature); } } }