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; // retrieve providers from provider manager return $this->providerManager->providers(ProviderInterface::TYPE_PEOPLE, $filter); } /** * Confirm which providers are available * * @param SourceSelector|null $sources 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 { // determine which providers are available $providersResolved = $this->providerList($tenantId, $userId, $sources); $providersAvailable = array_keys($providersResolved); $providersUnavailable = array_diff($sources->identifiers(), $providersAvailable); // construct response data $responseData = array_merge( array_fill_keys($providersAvailable, true), array_fill_keys($providersUnavailable, false) ); return $responseData; } /** * Retrieve specific provider for specific user * * @param string $tenantId tenant identifier * @param string $userId user identifier * @param string $provider provider identifier * * @return IProviderBase * @throws InvalidArgumentException */ public function providerFetch(string $tenantId, string $userId, string $provider): IProviderBase { // retrieve provider $providers = $this->providerList($tenantId, $userId, new SourceSelector([$provider => true])); if (!isset($providers[$provider])) { throw new InvalidArgumentException("Provider '$provider' not found"); } return $providers[$provider]; } /** * Retrieve available services for specific user * * @param string $tenantId tenant identifier * @param string $userId user identifier * @param SourceSelector|null $sources 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 { // retrieve providers $providers = $this->providerList($tenantId, $userId, $sources); // retrieve services for each provider $responseData = []; foreach ($providers as $provider) { $serviceFilter = $sources[$provider->id()] instanceof ServiceSelector ? $sources[$provider->id()]->identifiers() : []; $services = $provider->serviceList($tenantId, $userId, $serviceFilter); $responseData[$provider->id()] = $services; } return $responseData; } /** * Confirm which services are available * * @param string $tenantId tenant identifier * @param string $userId user identifier * @param SourceSelector|null $sources 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(); $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->id()]; $serviceAvailability = $provider->serviceExtant($tenantId, $userId, ...$serviceSelector->identifiers()); $responseData[$provider->id()] = $serviceAvailability; } return $responseData; } /** * Retrieve service for specific user * * @param string $tenantId tenant identifier * @param string $userId user identifier * @param string $providerId provider identifier * @param string|int $serviceId service identifier * * @return IServiceBase * @throws InvalidArgumentException */ public function serviceFetch(string $tenantId, string $userId, string $providerId, string|int $serviceId): IServiceBase { // retrieve provider and service $service = $this->providerFetch($tenantId, $userId, $providerId)->serviceFetch($tenantId, $userId, $serviceId); if ($service === null) { throw new InvalidArgumentException("Service '$serviceId' not found for provider '$providerId'"); } // retrieve services return $service; } /** * Retrieve available collections for specific user * * @param string $tenantId tenant identifier * @param string $userId user identifier * @param SourceSelector $sources list of provider and service identifiers * * @return array> collections of available services e.g. ['provider1' => ['service1' => [ICollectionBase], 'service2' => [ICollectionBase]]] */ public function collectionList(string $tenantId, string $userId, ?SourceSelector $sources = null, ?array $filter = null, ?array $sort = null): array { // confirm that sources are provided if ($sources === null) { $sources = new SourceSelector([]); } // retrieve providers $providers = $this->providerList($tenantId, $userId, $sources); // retrieve services for each provider $responseData = []; foreach ($providers as $provider) { $serviceFilter = $sources[$provider->id()] instanceof ServiceSelector ? $sources[$provider->id()]->identifiers() : []; $services = $provider->serviceList($tenantId, $userId, $serviceFilter); // retrieve collections for each service foreach ($services as $service) { // construct filter for collections $collectionFilter = null; if ($filter !== null && $filter !== []) { $collectionFilter = $service->collectionListFilter(); foreach ($filter as $attribute => $value) { $collectionFilter->condition($attribute, $value); } } // construct sort for collections $collectionSort = null; if ($sort !== null && $sort !== []) { $collectionSort = $service->collectionListSort(); foreach ($sort as $attribute => $direction) { $collectionSort->condition($attribute, $direction); } } $collections = $service->collectionList($collectionFilter, $collectionSort); if ($collections !== []) { $responseData[$provider->id()][$service->id()] = $collections; } } } return $responseData; } /** * Confirm which collections are available * * @param string $tenantId tenant identifier * @param string $userId user identifier * @param SourceSelector $sources collection of provider and service identifiers to confirm * * @return array collection of providers and their availability status e.g. ['provider1' => ['service1' => ['collection1' => true, 'collection2' => false]]]] */ 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) { $serviceSelector = $sources[$provider->id()]; $servicesRequested = $serviceSelector->identifiers(); $servicesAvailable = $provider->serviceList($tenantId, $userId, $servicesRequested); $servicesUnavailable = array_diff($servicesRequested, array_keys($servicesAvailable)); // mark unavailable services as false if ($servicesUnavailable !== []) { $responseData[$provider->id()] = array_fill_keys($servicesUnavailable, false); } // confirm collections for each available service foreach ($servicesAvailable as $service) { $collectionSelector = $serviceSelector[$service->id()]; $collectionsRequested = $collectionSelector->identifiers(); if ($collectionsRequested === []) { continue; } // check each requested collection foreach ($collectionsRequested as $collectionId) { $responseData[$provider->id()][$service->id()][$collectionId] = $service->collectionExtant((string)$collectionId); } } } return $responseData; } /** * Retrieve collection for specific user * * @param string $tenantId tenant identifier * @param string $userId user identifier * @param string $providerId provider identifier * @param string|int $serviceId service identifier * @param string|int $collectionId collection identifier * * @return ICollectionBase * @throws InvalidArgumentException */ public function collectionFetch(string $tenantId, string $userId, string $providerId, string|int $serviceId, string|int $collectionId): ICollectionBase { // retrieve services $service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId); // retrieve collection return $service->collectionFetch($collectionId); } /** * Create a new collection for a specific user * * @param string $tenantId tenant identifier * @param string $userId user identifier * @param string $providerId provider identifier * @param string|int $serviceId service identifier * @param ICollectionBase|array $collection collection to create * @param array $options additional options for creation * * @return ICollectionBase * @throws InvalidArgumentException */ public function collectionCreate(string $tenantId, string $userId, string $providerId, string|int $serviceId, ICollectionBase|array $collection, array $options = []): ICollectionBase { // retrieve service $service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId); // Check if service supports collection creation if (!($service instanceof IServiceCollectionMutable)) { throw new InvalidArgumentException("Service does not support collection mutations"); } if (!$service->capable(IServiceCollectionMutable::CAPABILITY_COLLECTION_CREATE)) { throw new InvalidArgumentException("Service is not capable of creating collections"); } if (is_array($collection)) { $collection = $service->collectionFresh()->jsonDeserialize($collection); } // Create collection (location is empty string for root) return $service->collectionCreate('', $collection, $options); } /** * Modify an existing collection for a specific user * * @param string $tenantId tenant identifier * @param string $userId user identifier * @param string $providerId provider identifier * @param string|int $serviceId service identifier * @param string|int $collectionId collection identifier * @param ICollectionBase $collectionData collection with modifications * * @return ICollectionBase * @throws InvalidArgumentException */ public function collectionModify(string $tenantId, string $userId, string $providerId, string|int $serviceId, string|int $collectionId, ICollectionBase|array $collectionData): ICollectionBase { // retrieve service $service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId); // Check if service supports collection modification if (!($service instanceof IServiceCollectionMutable)) { throw new InvalidArgumentException("Service does not support collection mutations"); } if (!$service->capable(IServiceCollectionMutable::CAPABILITY_COLLECTION_MODIFY)) { throw new InvalidArgumentException("Service is not capable of modifying collections"); } if (is_array($collectionData)) { $collectionData = $service->collectionFresh()->jsonDeserialize($collectionData); } // Modify collection return $service->collectionModify($collectionId, $collectionData); } /** * Delete a collection for a specific user * * @param string $tenantId tenant identifier * @param string $userId user identifier * @param string $providerId provider identifier * @param string|int $serviceId service identifier * @param string|int $collectionId collection identifier * * @return bool * @throws InvalidArgumentException */ public function collectionDestroy(string $tenantId, string $userId, string $providerId, string|int $serviceId, string|int $collectionId): bool { // retrieve service $service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId); // Check if service supports collection destruction if (!($service instanceof IServiceCollectionMutable)) { throw new InvalidArgumentException("Service does not support collection mutations"); } if (!$service->capable(IServiceCollectionMutable::CAPABILITY_COLLECTION_DESTROY)) { throw new InvalidArgumentException("Service is not capable of destroying collections"); } // Destroy collection and cast result to bool return (bool)$service->collectionDestroy($collectionId); } /** * Retrieve available entities for specific user * * @param string $tenantId tenant identifier * @param string $userId user identifier * @param SourceSelector $sources list of provider and service identifiers * * @return array> collections of store enteties e.g. ['provider1' => ['service1' => [ICollectionBase], 'service2' => [ICollectionBase]]] */ public function entityList(string $tenantId, string $userId, ?SourceSelector $sources = null, ?array $filter = null, ?array $sort = null, ?array $range = null): array { // confirm that sources are provided if ($sources === null) { $sources = new SourceSelector([]); } // retrieve providers $providers = $this->providerList($tenantId, $userId, $sources); // retrieve services for each provider $responseData = []; foreach ($providers as $provider) { // retrieve services for each provider $serviceSelector = $sources[$provider->id()]; $servicesSelected = $provider->serviceList($tenantId,$userId, $serviceSelector->identifiers()); foreach ($servicesSelected as $service) { // retrieve collections for each service $collectionSelector = $serviceSelector[$service->id()]; $collectionSelected = $collectionSelector instanceof CollectionSelector ? $collectionSelector->identifiers() : []; if ($collectionSelected === []) { $collections = $service->collectionList(); $collectionSelected = array_map( fn($collection) => $collection->id(), $collections ); } if ($collectionSelected === []) { continue; } // construct filter for entities $entityFilter = null; if ($filter !== null && $filter !== []) { $entityFilter = $service->entityListFilter(); foreach ($filter as $attribute => $value) { $entityFilter->condition($attribute, $value); } } // construct sort for entities $entitySort = null; if ($sort !== null && $sort !== []) { $entitySort = $service->entityListSort(); foreach ($sort as $attribute => $direction) { $entitySort->condition($attribute, $direction); } } // construct range for entities $entityRange = null; if ($range !== null && $range !== [] && isset($range['type'])) { $entityRange = $service->entityListRange(RangeType::from($range['type'])); // Cast to IRangeTally if the range type is TALLY if ($entityRange->type() === RangeType::TALLY) { /** @var IRangeTally $entityRange */ if (isset($range['anchor'])) { $entityRange->setAnchor(RangeAnchorType::from($range['anchor'])); } if (isset($range['position'])) { $entityRange->setPosition($range['position']); } if (isset($range['tally'])) { $entityRange->setTally($range['tally']); } } } // retrieve entities for each collection foreach ($collectionSelected as $collectionId) { $entities = $service->entityList($collectionId, $entityFilter, $entitySort, $entityRange, null); // skip collections with no entities if ($entities === []) { continue; } $responseData[$provider->id()][$service->id()][$collectionId] = $entities; } } } return $responseData; } /** * Confirm which entities are available * * @param string $tenantId tenant identifier * @param string $userId user identifier * @param array $sources collection of provider and service identifiers to confirm * * @return array collection of providers and their availability status */ 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 $responseData = array_fill_keys($providersUnavailable, false); // iterate through available providers foreach ($providers as $provider) { $serviceSelector = $sources[$provider->id()]; $servicesRequested = $serviceSelector instanceof ServiceSelector ? $serviceSelector->identifiers() : []; $services = $provider->serviceList($tenantId, $userId, $servicesRequested); $servicesUnavailable = array_diff($servicesRequested, array_keys($services)); if ($servicesUnavailable !== []) { $responseData[$provider->id()] = array_fill_keys($servicesUnavailable, false); } // iterate through available services foreach ($services as $service) { $collectionSelector = $serviceSelector[$service->id()]; $collectionsRequested = $collectionSelector instanceof CollectionSelector ? $collectionSelector->identifiers() : []; if ($collectionsRequested === []) { $responseData[$provider->id()][$service->id()] = false; continue; } foreach ($collectionsRequested as $collection) { $entitySelector = $collectionSelector[$collection] ?? null; $responseData[$provider->id()][$service->id()][$collection] = $service->entityDelta($collection, $entitySelector); } } } return $responseData; } /** * Confirm which entities are available * * @param string $tenantId tenant identifier * @param string $userId user identifier * @param SourceSelector $sources collection of provider and service identifiers to confirm * * @return array collection of providers and their availability status e.g. ['provider1' => ['service1' => ['collection1' => ['entity1' => true, 'entity2' => false]]]] */ 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 $responseData = array_fill_keys($providersUnavailable, false); // check services, collections, and entities for each available provider foreach ($providers as $provider) { $serviceSelector = $sources[$provider->id()]; $servicesRequested = $serviceSelector->identifiers(); $servicesAvailable = $provider->serviceList($tenantId, $userId, $servicesRequested); $servicesUnavailable = array_diff($servicesRequested, array_keys($servicesAvailable)); // mark unavailable services as false if ($servicesUnavailable !== []) { $responseData[$provider->id()] = array_fill_keys($servicesUnavailable, false); } // check collections and entities for each available service foreach ($servicesAvailable as $service) { $collectionSelector = $serviceSelector[$service->id()]; $collectionsRequested = $collectionSelector instanceof CollectionSelector ? $collectionSelector->identifiers() : []; if ($collectionsRequested === []) { continue; } // check entities for each requested collection foreach ($collectionsRequested as $collectionId) { // first check if collection exists $collectionExists = $service->collectionExtant((string)$collectionId); if (!$collectionExists) { // collection doesn't exist, mark as false $responseData[$provider->id()][$service->id()][$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->id()][$service->id()][$collectionId] = $service->entityExtant($collectionId, ...$entitySelector->identifiers()); } elseif ($entitySelector === true) { // just checking if collection exists (already confirmed above) $responseData[$provider->id()][$service->id()][$collectionId] = true; } } } } return $responseData; } /** * Retrieve entity for specific user and collection * * @param string $tenantId tenant identifier * @param string $userId user identifier * @param string $providerId provider identifier * @param string|int $serviceId service identifier * @param string|int $collectionId collection identifier * @param array $identifiers entity identifiers * * @return array */ public function entityFetch(string $tenantId, string $userId, string $providerId, string|int $serviceId, string|int $collectionId, array $identifiers): array { // retrieve services $service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId); // retrieve collection return $service->entityFetch($collectionId, ...$identifiers); } /** * Create a new entity in a specific collection for a specific user * * @param string $tenantId tenant identifier * @param string $userId user identifier * @param string $providerId provider identifier * @param string|int $serviceId service identifier * @param string|int $collectionId collection identifier * @param IEntityBase|array $entity entity to create * @param array $options additional options for creation * * @return IEntityBase * @throws InvalidArgumentException */ public function entityCreate(string $tenantId, string $userId, string $providerId, string|int $serviceId, string|int $collectionId, IEntityBase|array $entity, array $options = []): IEntityBase { // retrieve service $service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId); // Check if service supports entity creation if (!($service instanceof IServiceEntityMutable)) { throw new InvalidArgumentException("Service does not support entity mutations"); } if (!$service->capable(IServiceEntityMutable::CAPABILITY_ENTITY_CREATE)) { throw new InvalidArgumentException("Service is not capable of creating entities"); } if (is_array($entity)) { $entityInstance = $service->entityFresh(); $entityInstance->jsonDeserialize($entity); } else { $entityInstance = $entity; } // Create entity return $service->entityCreate($collectionId, $entityInstance, $options); } /** * Modify an existing entity in a collection for a specific user * * @param string $tenantId tenant identifier * @param string $userId user identifier * @param string $providerId provider identifier * @param string|int $serviceId service identifier * @param string|int $collectionId collection identifier * @param string|int $identifier entity identifier * @param IEntityBase|array $entity entity with modifications * * @return IEntityBase * @throws InvalidArgumentException */ public function entityModify(string $tenantId, string $userId, string $providerId, string|int $serviceId, string|int $collectionId, string|int $identifier, IEntityBase|array $entity): IEntityBase { // retrieve service $service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId); // Check if service supports entity modification if (!($service instanceof IServiceEntityMutable)) { throw new InvalidArgumentException("Service does not support entity mutations"); } if (!$service->capable(IServiceEntityMutable::CAPABILITY_ENTITY_MODIFY)) { throw new InvalidArgumentException("Service is not capable of modifying entities"); } if (is_array($entity)) { $entityInstance = $service->entityFresh(); $entityInstance->jsonDeserialize($entity); } else { $entityInstance = $entity; } // Modify entity return $service->entityModify($collectionId, $identifier, $entityInstance); } /** * Delete an entity from a collection for a specific user * * @param string $tenantId tenant identifier * @param string $userId user identifier * @param string $providerId provider identifier * @param string|int $serviceId service identifier * @param string|int $collectionId collection identifier * @param string|int $identifier entity identifier * * @return bool * @throws InvalidArgumentException */ public function entityDestroy(string $tenantId, string $userId, string $providerId, string|int $serviceId, string|int $collectionId, string|int $identifier): bool { // retrieve service $service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId); // Check if service supports entity destruction if (!($service instanceof IServiceEntityMutable)) { throw new InvalidArgumentException("Service does not support entity mutations"); } if (!$service->capable(IServiceEntityMutable::CAPABILITY_ENTITY_DESTROY)) { throw new InvalidArgumentException("Service is not capable of destroying entities"); } // Destroy entity and cast result to bool return (bool)$service->entityDestroy($collectionId, $identifier); } }