collection of available providers e.g. ['provider1' => IProvider, 'provider2' => IProvider] */ public function providerList(?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_CHRONO, $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(?SourceSelector $sources = null): array { // determine which providers are available $providerFilter = $sources?->identifiers() ?? []; $providersResolved = $this->providerManager->providers(ProviderInterface::TYPE_CHRONO, $providerFilter); $providersAvailable = array_keys($providersResolved); $providersUnavailable = array_diff($providerFilter, $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 $provider provider identifier * * @return IProviderBase * @throws InvalidArgumentException */ public function providerFetch(string $provider): IProviderBase { // retrieve provider $providers = $this->providerList(new SourceSelector([$provider => true])); if (!isset($providers[$provider])) { throw new InvalidArgumentException('Provider not found: ' . $provider); } 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($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 = null): array { // confirm that sources are provided if ($sources === null) { $sources = new SourceSelector([]); } // retrieve providers $providers = $this->providerList($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 $provider provider identifier * @param string|int $service service identifier * * @return IServiceBase * @throws InvalidArgumentException */ public function serviceFetch(string $tenantId, string $userId, string $provider, string|int $service): IServiceBase { $providerInstance = $this->providerFetch($provider); $serviceInstance = $providerInstance->serviceFetch($tenantId, $userId, $service); if ($serviceInstance === null) { throw new InvalidArgumentException('Service not found: ' . $service); } return $serviceInstance; } /** * Retrieve available collections 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' => [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($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 { // confirm that sources are provided if ($sources === null) { $sources = new SourceSelector([]); } // retrieve available providers $providers = $this->providerList($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 specific 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 { $service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId); $collection = $service->collectionFetch($collectionId); if ($collection === null) { throw new InvalidArgumentException('Collection not found: ' . $collectionId); } return $collection; } /** * Create a new collection * * @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 * * @return ICollectionBase * @throws InvalidArgumentException */ public function collectionCreate(string $tenantId, string $userId, string $providerId, string|int $serviceId, ICollectionBase|array $collection, array $options = []): ICollectionBase { $service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId); if (!($service instanceof IServiceCollectionMutable)) { throw new InvalidArgumentException('Service does not support collection creation'); } // convert array to collection object if needed if (is_array($collection)) { $collectionObject = $service->collectionFresh(); $collectionObject->jsonDeserialize($collection); $collection = $collectionObject; } // create collection return $service->collectionCreate('', $collection, $options); } /** * Modify an existing 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 ICollectionBase|array $collectionData collection data * * @return ICollectionBase * @throws InvalidArgumentException */ public function collectionModify(string $tenantId, string $userId, string $providerId, string|int $serviceId, string|int $collectionId, ICollectionBase|array $collectionData): ICollectionBase { $service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId); if (!($service instanceof IServiceCollectionMutable)) { throw new InvalidArgumentException('Service does not support collection modification'); } // convert array to collection object if needed if (is_array($collectionData)) { $collectionObject = $service->collectionFresh(); $collectionObject->jsonDeserialize($collectionData); $collectionData = $collectionObject; } // modify collection return $service->collectionModify($collectionId, $collectionData); } /** * Destroy a 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 * * @return bool * @throws InvalidArgumentException */ public function collectionDestroy(string $tenantId, string $userId, string $providerId, string|int $serviceId, string|int $collectionId): bool { $service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId); if (!($service instanceof IServiceCollectionMutable)) { throw new InvalidArgumentException('Service does not support collection destruction'); } return $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($sources); // retrieve services for each provider $responseData = []; foreach ($providers as $provider) { // retrieve services for each provider $serviceSelector = $sources[$provider->id()]; $servicesSelected = $serviceSelector instanceof ServiceSelector ? $serviceSelector->identifiers() : []; $services = $provider->serviceList($tenantId,$userId, $servicesSelected); foreach ($services 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(ICollectionBase $collection): string|int => $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($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($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) { $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) { $entityIds = $entitySelector->identifiers(); foreach ($entityIds as $entityId) { $responseData[$provider->id()][$service->id()][$collectionId][$entityId] = $service->entityExtant((string)$collectionId, (string)$entityId)[$entityId] ?? false; } } } } } return $responseData; } /** * Retrieve specific entities from a collection * * @param string $tenantId tenant identifier * @param string $userId user identifier * @param string|int $providerId provider identifier * @param string|int $serviceId service identifier * @param string|int $collectionId collection identifier * @param array $identifiers entity identifiers * * @return array collection of entities */ public function entityFetch(string $tenantId, string $userId, string|int $providerId, string|int $serviceId, string|int $collectionId, array $identifiers): array { $service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId); return $service->entityFetch($collectionId, ...$identifiers); } /** * Create a new entity in a 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 IEntityBase|array $entity entity to create * @param array $options additional options * * @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 { $service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId); if (!($service instanceof IServiceEntityMutable)) { throw new InvalidArgumentException('Service does not support entity creation'); } // convert array to entity object if needed if (is_array($entity)) { $entityObject = $service->entityFresh(); $entityObject->jsonDeserialize($entity); $entity = $entityObject; } // create entity return $service->entityCreate($collectionId, $entity, $options); } /** * Modify an existing entity in a collection * * @param string $tenantId tenant identifier * @param string $userId user identifier * @param string|int $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|int $providerId, string|int $serviceId, string|int $collectionId, string|int $identifier, IEntityBase|array $entity): IEntityBase { $service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId); if (!($service instanceof IServiceEntityMutable)) { throw new InvalidArgumentException('Service does not support entity modification'); } // convert array to entity object if needed if (is_array($entity)) { $entityObject = $service->entityFresh(); $entityObject->jsonDeserialize($entity); $entity = $entityObject; } // modify entity return $service->entityModify($collectionId, $identifier, $entity); } /** * Destroy an entity from a collection * * @param string $tenantId tenant identifier * @param string $userId user identifier * @param string|int $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|int $providerId, string|int $serviceId, string|int $collectionId, string|int $identifier): bool { $service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId); if (!($service instanceof IServiceEntityMutable)) { throw new InvalidArgumentException('Service does not support entity destruction'); } $entity = $service->entityDestroy($collectionId, $identifier); return $entity !== null; } }