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(ProviderBaseInterface::TYPE_CHRONO, $filter); } /** * Retrieve specific provider for specific user * * @param string $tenantId tenant identifier * @param string $userId user identifier * @param string $provider provider identifier * * @return ProviderBaseInterface * @throws InvalidArgumentException */ public function providerFetch(string $tenantId, string $userId, string $provider): ProviderBaseInterface { // 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]; } /** * 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 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->identifier()] instanceof ServiceSelector ? $sources[$provider->identifier()]->identifiers() : []; $services = $provider->serviceList($tenantId, $userId, $serviceFilter); $responseData[$provider->identifier()] = $services; } 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 ServiceBaseInterface * @throws InvalidArgumentException */ public function serviceFetch(string $tenantId, string $userId, string $providerId, string|int $serviceId): ServiceBaseInterface { // 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; } /** * 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->identifier()]; $serviceAvailability = $provider->serviceExtant($tenantId, $userId, ...$serviceSelector->identifiers()); $responseData[$provider->identifier()] = $serviceAvailability; } return $responseData; } /** * Create a new service * * @since 2025.05.01 * * @param string $tenantId Tenant identifier * @param string|null $userId User identifier for context * @param string $providerId Provider identifier * @param array $data Service configuration data * * @return ServiceBaseInterface Created service * * @throws InvalidArgumentException If provider doesn't support service creation */ public function serviceCreate(string $tenantId, ?string $userId, string $providerId, array $data): ServiceBaseInterface { // retrieve provider and service $provider = $this->providerFetch($tenantId, $userId, $providerId); if ($provider instanceof ProviderServiceMutateInterface === false) { throw new InvalidArgumentException("Provider '$providerId' does not support service creation"); } // Create a fresh service instance $service = $provider->serviceFresh(); // Deserialize the data into the service $service->jsonDeserialize($data); // Create the service $serviceId = $provider->serviceCreate($tenantId, $userId, $service); // Fetch and return the created service return $provider->serviceFetch($tenantId, $userId, $serviceId); } /** * Update an existing service * * @since 2025.05.01 * * @param string $tenantId Tenant identifier * @param string $userId User identifier for context * @param string $providerId Provider identifier * @param string|int $serviceId Service identifier * @param array $data Updated service configuration data * * @return ServiceBaseInterface Updated service * * @throws InvalidArgumentException If provider doesn't support service modification or service not found */ public function serviceUpdate(string $tenantId, string $userId, string $providerId, string|int $serviceId, array $data): ServiceBaseInterface { // retrieve provider and service $provider = $this->providerFetch($tenantId, $userId, $providerId); if ($provider instanceof ProviderServiceMutateInterface === false) { throw new InvalidArgumentException("Provider '$providerId' does not support service creation"); } // Fetch existing service $service = $provider->serviceFetch($tenantId, $userId, $serviceId); if ($service === null) { throw new InvalidArgumentException("Service '$serviceId' not found"); } // Update with new data $service->jsonDeserialize($data); // Modify the service $provider->serviceModify($tenantId, $userId, $service); // Fetch and return the updated service return $provider->serviceFetch($tenantId, $userId, $serviceId); } /** * Delete a service * * @since 2025.05.01 * * @param string $tenantId Tenant identifier * @param string $userId User identifier for context * @param string $providerId Provider identifier * @param string|int $serviceId Service identifier * * @return bool True if service was deleted * * @throws InvalidArgumentException If provider doesn't support service deletion or service not found */ public function serviceDelete(string $tenantId, string $userId, string $providerId, string|int $serviceId): bool { // retrieve provider and service $provider = $this->providerFetch($tenantId, $userId, $providerId); if ($provider instanceof ProviderServiceMutateInterface === false) { throw new InvalidArgumentException("Provider '$providerId' does not support service creation"); } // Fetch existing service $service = $provider->serviceFetch($tenantId, $userId, $serviceId); if ($service === null) { throw new InvalidArgumentException("Service '$serviceId' not found"); } // Delete the service return $provider->serviceDestroy($tenantId, $userId, $service); } /** * Test a mail service connection * * Tests connectivity and authentication for either an existing service * or a fresh configuration. Delegates to the appropriate provider. * * @since 2025.05.01 * * @param string $tenantId Tenant identifier * @param string $userId User identifier for context * @param string $providerId Provider ID (for existing service or targeted test) * @param string|int|null $serviceId Service ID (for existing service test) * @param ResourceServiceLocationInterface|array|null $location Service location (for fresh config test) * @param ResourceServiceIdentityInterface|array|null $identity Service credentials (for fresh config test) * * @return array Test results * * @throws InvalidArgumentException If invalid parameters */ public function serviceTest( string $tenantId, string $userId, string $providerId, string|int|null $serviceId = null, ResourceServiceLocationInterface|array|null $location = null, ResourceServiceIdentityInterface|array|null $identity = null ): array { // retrieve provider $provider = $this->providerFetch($tenantId, $userId, $providerId); if ($provider instanceof ProviderServiceTestInterface === false) { throw new InvalidArgumentException("Provider '$providerId' does not support service testing"); } // Testing existing service if ($providerId !== null && $serviceId !== null) { // retrieve service $service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId); if ($service === null) { throw new InvalidArgumentException("Service not found: $providerId/$serviceId"); } try { return $provider->serviceTest($service); } catch (\Throwable $e) { throw new InvalidArgumentException('Service test failed: ' . $e->getMessage()); } } // Testing fresh configuration if ($location !== null && $identity !== null) { if ($provider instanceof ProviderServiceMutateInterface === false) { throw new InvalidArgumentException("Provider '$providerId' does not support fresh service configuration testing"); } if (empty($location['type'])) { throw new InvalidArgumentException('Service location not valid'); } if (empty($identity['type'])) { throw new InvalidArgumentException('Service identity not valid'); } /** @var ServiceMutableInterface $service */ $service = $provider->serviceFresh(); if ($location instanceof ResourceServiceLocationInterface === false) { $location = $service->freshLocation($location['type'], (array)$location); $service->setLocation($location); } if ($identity instanceof ResourceServiceIdentityInterface === false) { $identity = $service->freshIdentity($identity['type'], (array)$identity); $service->setIdentity($identity); } return $provider->serviceTest($service); } throw new InvalidArgumentException( 'Either (provider + service) or (provider + location + identity) must be provided' ); } // ==================== Collection Operations ==================== /** * List collections across services * * @since 2025.05.01 * * @param string $tenantId Tenant identifier * @param string|null $userId User identifier for context * @param SourceSelector|null $sources 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 { // 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->identifier()] instanceof ServiceSelector ? $sources[$provider->identifier()]->identifiers() : []; /** @var ServiceBaseInterface[] $services */ $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->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) { $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) { $collectionSelector = $serviceSelector[$service->identifier()]; $collectionsRequested = $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 * * @since 2025.05.01 * * @param string $tenantId Tenant identifier * @param string|null $userId User identifier for context * @param string $providerId Provider identifier * @param string|int $serviceId Service identifier * @param string|int $collectionId Collection identifier * * @return CollectionBaseInterface|null */ public function collectionFetch(string $tenantId, ?string $userId, string $providerId, string|int $serviceId, string|int $collectionId): ?CollectionBaseInterface { // retrieve service $service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId); if ($service === null) { return null; } // 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 string|int|null $collectionId collection identifier (parent collection) * @param CollectionMutableInterface|array $object collection to create * @param array $options additional options for creation * * @return CollectionBaseInterface * @throws InvalidArgumentException */ public function collectionCreate(string $tenantId, string $userId, string $providerId, string|int $serviceId, string|int|null $collectionId, CollectionMutableInterface|array $object, array $options = []): CollectionBaseInterface { // retrieve service $service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId); // Check if service supports collection creation if (!($service instanceof ServiceCollectionMutableInterface)) { throw new InvalidArgumentException("Service does not support collection mutations"); } if (!$service->capable(ServiceCollectionMutableInterface::CAPABILITY_COLLECTION_CREATE)) { throw new InvalidArgumentException("Service is not capable of creating collections"); } if (is_array($object)) { $collection = $service->collectionFresh(); $collection->getProperties()->jsonDeserialize($object); } else { $collection = $object; } // Create collection return $service->collectionCreate($collectionId, $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 CollectionMutableInterface|array $object collection to modify * * @return CollectionBaseInterface * @throws InvalidArgumentException */ public function collectionUpdate(string $tenantId, string $userId, string $providerId, string|int $serviceId, string|int $collectionId, CollectionMutableInterface|array $object): CollectionBaseInterface { // retrieve service $service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId); // Check if service supports collection creation if (!($service instanceof ServiceCollectionMutableInterface)) { throw new InvalidArgumentException("Service does not support collection mutations"); } if (!$service->capable(ServiceCollectionMutableInterface::CAPABILITY_COLLECTION_UPDATE)) { throw new InvalidArgumentException("Service is not capable of updating collections"); } if (is_array($object)) { $collection = $service->collectionFresh(); $collection->getProperties()->jsonDeserialize($object); } else { $collection = $object; } // Update collection return $service->collectionUpdate($collectionId, $collection); } /** * Delete a specific collection * * @since 2025.05.01 * * @param string $tenantId Tenant identifier * @param string|null $userId User identifier for context * @param string $providerId Provider identifier * @param string|int $serviceId Service identifier * @param string|int $collectionId Collection identifier * * @return CollectionBaseInterface|null */ public function collectionDelete(string $tenantId, ?string $userId, string $providerId, string|int $serviceId, string|int $collectionId, array $options = []): bool { // retrieve service $service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId); // Check if service supports collection deletion if (!($service instanceof ServiceCollectionMutableInterface)) { throw new InvalidArgumentException("Service does not support collection mutations"); } if (!$service->capable(ServiceCollectionMutableInterface::CAPABILITY_COLLECTION_DELETE)) { throw new InvalidArgumentException("Service is not capable of deleting collections"); } $force = $options['force'] ?? false; $recursive = $options['recursive'] ?? false; // delete collection return $service->collectionDelete($collectionId, $force, $recursive); } // ==================== Message Operations ==================== /** * List messages in a collection * * @since 2025.05.01 * * @param string $tenantId Tenant identifier * @param string $userId User identifier * @param SourceSelector $sources 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 entityList(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); // retrieve services for each provider $responseData = []; foreach ($providers as $provider) { // retrieve services for each provider $serviceSelector = $sources[$provider->identifier()]; $servicesSelected = $provider->serviceList($tenantId,$userId, $serviceSelector->identifiers()); /** @var ServiceBaseInterface $service */ foreach ($servicesSelected as $service) { // retrieve collections for each service $collectionSelector = $serviceSelector[$service->identifier()]; $collectionSelected = $collectionSelector instanceof CollectionSelector ? $collectionSelector->identifiers() : []; if ($collectionSelected === []) { $collections = $service->collectionList(''); $collectionSelected = array_map( fn($collection) => $collection->identifier(), $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->identifier()][$service->identifier()][$collectionId] = $entities; } } } return $responseData; } /** * Fetch specific messages * * @since 2025.05.01 * * @param string $tenantId Tenant identifier * @param string|null $userId User identifier for context * @param string $providerId Provider identifier * @param string|int $serviceId Service identifier * @param string|int $collectionId Collection identifier * @param array $identifiers Message identifiers * * @return array Messages indexed by ID */ public function entityFetch(string $tenantId, ?string $userId, string $providerId, string|int $serviceId, string|int $collectionId, array $identifiers): array { $service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId); // retrieve collection return $service->entityFetch($collectionId, ...$identifiers); } /** * Check if messages exist * * @since 2025.05.01 * * @param string $tenantId Tenant identifier * @param string|null $userId User identifier for context * @param SourceSelector $sources 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 $responseData = array_fill_keys($providersUnavailable, false); // check services, collections, and entities for each available provider foreach ($providers as $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); } // check collections and entities for each available service foreach ($servicesAvailable as $service) { $collectionSelector = $serviceSelector[$service->identifier()]; $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->identifier()][$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) { // just checking if collection exists (already confirmed above) $responseData[$provider->identifier()][$service->identifier()][$collectionId] = true; } } } } return $responseData; } /** * Get message delta/changes * * @since 2025.05.01 * * @param string $tenantId Tenant identifier * @param string|null $userId User identifier for context * @param SourceSelector $sources 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 $responseData = array_fill_keys($providersUnavailable, false); // iterate through available providers foreach ($providers as $provider) { $serviceSelector = $sources[$provider->identifier()]; $servicesRequested = $serviceSelector instanceof ServiceSelector ? $serviceSelector->identifiers() : []; /** @var ServiceBaseInterface[] $services */ $services = $provider->serviceList($tenantId, $userId, $servicesRequested); $servicesUnavailable = array_diff($servicesRequested, array_keys($services)); if ($servicesUnavailable !== []) { $responseData[$provider->identifier()] = array_fill_keys($servicesUnavailable, false); } // iterate through available services foreach ($services as $service) { $collectionSelector = $serviceSelector[$service->identifier()]; $collectionsRequested = $collectionSelector instanceof CollectionSelector ? $collectionSelector->identifiers() : []; if ($collectionsRequested === []) { $responseData[$provider->identifier()][$service->identifier()] = false; continue; } foreach ($collectionsRequested as $collection) { $entitySelector = $collectionSelector[$collection] ?? null; $responseData[$provider->identifier()][$service->identifier()][$collection] = $service->entityDelta($collection, $entitySelector); } } } return $responseData; } /** * 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 EntityMutableInterface|array $entity entity to create * @param array $options additional options * * @return EntityBaseInterface * @throws InvalidArgumentException */ public function entityCreate(string $tenantId, string $userId, string $providerId, string|int $serviceId, string|int $collectionId, EntityMutableInterface|array $object, array $options = []): EntityBaseInterface { // retrieve service $service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId); // Check if service supports entity creation if (!($service instanceof ServiceEntityMutableInterface)) { throw new InvalidArgumentException("Service does not support entity mutations"); } if (!$service->capable(ServiceEntityMutableInterface::CAPABILITY_ENTITY_CREATE)) { throw new InvalidArgumentException("Service is not capable of creating entities"); } if (is_array($object)) { $entity = $service->entityFresh(); $entity->getProperties()->jsonDeserialize($object); } else { $entity = $object; } 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 EntityBaseInterface|array $entity entity with modifications * * @return EntityBaseInterface * @throws InvalidArgumentException */ public function entityUpdate(string $tenantId, string $userId, string|int $providerId, string|int $serviceId, string|int $collectionId, string|int $identifier, EntityBaseInterface|array $object): EntityBaseInterface { // retrieve service $service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId); // Check if service supports entity creation if (!($service instanceof ServiceEntityMutableInterface)) { throw new InvalidArgumentException("Service does not support entity mutations"); } if (!$service->capable(ServiceEntityMutableInterface::CAPABILITY_ENTITY_CREATE)) { throw new InvalidArgumentException("Service is not capable of creating entities"); } if (is_array($object)) { $entity = $service->entityFresh(); $entity->getProperties()->jsonDeserialize($object); } else { $entity = $object; } return $service->entityUpdate($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 entityDelete(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 ServiceEntityMutableInterface)) { throw new InvalidArgumentException('Service does not support entity destruction'); } $entity = $service->entityDelete($collectionId, $identifier); return $entity !== null; } }