refactor: remove sources selector in backend

Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
This commit is contained in:
2026-06-19 23:45:41 -04:00
parent af227f35f9
commit 9845262e62
2 changed files with 251 additions and 303 deletions
+72 -106
View File
@@ -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);
}
return $this->manager->providerList($tenantId, $userId, $sources);
foreach ($data['targets'] as $target) {
if (!is_string($target)) {
throw new InvalidArgumentException(self::ERR_INVALID_TARGETS);
}
}
}
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 (!isset($data['targets'])) {
throw new InvalidArgumentException(self::ERR_MISSING_TARGETS);
}
if (!is_array($data['sources'])) {
throw new InvalidArgumentException(self::ERR_INVALID_SOURCES);
}
$sources = new SourceSelector();
$sources->jsonDeserialize($data['sources']);
return $this->manager->providerExtant($tenantId, $userId, $sources);
foreach ($data['targets'] as $target) {
if (!is_string($target)) {
throw new InvalidArgumentException(self::ERR_INVALID_TARGETS);
}
}
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['targets'])) {
throw new InvalidArgumentException(self::ERR_INVALID_TARGETS);
}
$targets = ResourceIdentifiers::fromArray($data['targets']);
foreach ($targets as $target) {
if (!$target instanceof ServiceIdentifier) {
throw new InvalidArgumentException('Invalid parameter: targets must contain provider:service identifiers');
}
if (!is_array($data['sources'])) {
throw new InvalidArgumentException(self::ERR_INVALID_SOURCES);
}
$sources = new SourceSelector();
$sources->jsonDeserialize($data['sources']);
return $this->manager->serviceExtant($tenantId, $userId, $sources);
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(
$list = $this->manager->collectionFetch(
$tenantId,
$userId,
$targetIdentifier->provider(),
$targetIdentifier->service(),
$targetIdentifier->collection()
$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);
}
@@ -612,8 +617,6 @@ class DefaultController extends ControllerAbstract {
}
}
$sources = $this->createSourceSelectorFromIdentifiers($sources);
$filter = $data['filter'] ?? null;
$sort = $data['sort'] ?? null;
$range = $data['range'] ?? 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']);
$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, $sources);
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']);
$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, $sources);
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;
}
}
+166 -184
View File
@@ -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<string,ProviderBaseInterface> 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<string> $targets collection of provider identifiers to confirm
*
* @return array<string,bool> 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<string,<string,ServiceBaseInterface>> 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<string,bool> 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<string, array<string|int, array<string|int, ICollectionBase>>> 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
foreach ($aggregateServices as $services) {
foreach ($services as $service) {
if ($service->getEnabled() === false) {
continue;
@@ -499,72 +495,23 @@ class Manager {
$collectionSort->condition($attribute, $direction);
}
}
$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);
}
if ($collections !== []) {
$responseData[$provider->identifier()][$service->identifier()] = $collections;
$responseData[$service->provider()][$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<string, array<string|int, array<string|int, bool>>> 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;
}
// extract collections requested for this service
$collectionSelector = $serviceSelector[$service->identifier()];
$collectionsRequested = $collectionSelector instanceof CollectionSelector ? $collectionSelector->identifiers() : [];
if ($collectionsRequested === []) {
continue;
}
// 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;
}
/**
* Fetch a specific collection
*
@@ -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<string, array<string|int, array<string|int, bool>>> 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<string, array<string|int, array<string|int, array<string|int, IMessageBase>>>> 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<EntityBaseInterface> 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<string, array<string|int, array<string|int, array<string|int, bool>>>> 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<string, array<string|int, array<string|int, 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);
}
}
}