$className = $this->resolveProviderClassName($modelInstance->providerMetadata()->getId()); if ($modelInstance instanceof WithHttpTransporterInterface) { $modelInstance->setHttpTransporter($this->getHttpTransporter()); } if ($modelInstance instanceof WithRequestAuthenticationInterface) { $requestAuthentication = $this->getProviderRequestAuthentication($className); if ($requestAuthentication !== null) { $modelInstance->setRequestAuthentication($requestAuthentication); } } } /** * Gets the class name for a registered provider (handles both ID and class name input). * * @param string|class-string $idOrClassName The provider ID or class name. * @return class-string The provider class name. * @throws InvalidArgumentException If provider is not registered. */ private function resolveProviderClassName(string $idOrClassName): string { // If it's already a class name, return it if ($this->isRegisteredClassName($idOrClassName)) { return $idOrClassName; } // If it's a registered ID, return its class name if ($this->isRegisteredId($idOrClassName)) { return $this->registeredIdsToClassNames[$idOrClassName]; } // Not found throw new InvalidArgumentException(sprintf('Provider not registered: %s', $idOrClassName)); } /** * {@inheritDoc} * * @since 0.1.0 */ public function setHttpTransporter(HttpTransporterInterface $httpTransporter): void { $this->setHttpTransporterOriginal($httpTransporter); // Make sure all registered providers have the HTTP transporter hooked up as needed. foreach ($this->registeredIdsToClassNames as $className) { $this->setHttpTransporterForProvider($className, $httpTransporter); } } /** * Sets the request authentication instance for the given provider. * * @since 0.1.0 * * @param string|class-string $idOrClassName The provider ID or class name. * @param RequestAuthenticationInterface $requestAuthentication The request authentication instance. */ public function setProviderRequestAuthentication(string $idOrClassName, RequestAuthenticationInterface $requestAuthentication): void { $className = $this->resolveProviderClassName($idOrClassName); $this->providerAuthenticationInstances[$className] = $requestAuthentication; $this->setRequestAuthenticationForProvider($className, $requestAuthentication); } /** * Gets the request authentication instance for the given provider, if set. * * @since 0.1.0 * * @param string|class-string $idOrClassName The provider ID or class name. * @return ?RequestAuthenticationInterface The request authentication instance, or null if not set. */ public function getProviderRequestAuthentication(string $idOrClassName): ?RequestAuthenticationInterface { $className = $this->resolveProviderClassName($idOrClassName); if (!isset($this->providerAuthenticationInstances[$className])) { return null; } return $this->providerAuthenticationInstances[$className]; } /** * Sets the HTTP transporter for a specific provider, hooking up its class instances. * * @since 0.1.0 * * @param class-string $className The provider class name. * @param HttpTransporterInterface $httpTransporter The HTTP transporter instance. */ private function setHttpTransporterForProvider(string $className, HttpTransporterInterface $httpTransporter): void { $availability = $className::availability(); if ($availability instanceof WithHttpTransporterInterface) { $availability->setHttpTransporter($httpTransporter); } $modelMetadataDirectory = $className::modelMetadataDirectory(); if ($modelMetadataDirectory instanceof WithHttpTransporterInterface) { $modelMetadataDirectory->setHttpTransporter($httpTransporter); } if (is_subclass_of($className, ProviderWithOperationsHandlerInterface::class)) { $operationsHandler = $className::operationsHandler(); if ($operationsHandler instanceof WithHttpTransporterInterface) { $operationsHandler->setHttpTransporter($httpTransporter); } } } /** * Sets the request authentication for a specific provider, hooking up its class instances. * * @since 0.1.0 * * @param class-string $className The provider class name. * @param RequestAuthenticationInterface $requestAuthentication The authentication instance. * * @throws InvalidArgumentException If the authentication instance is not of the expected type. */ private function setRequestAuthenticationForProvider(string $className, RequestAuthenticationInterface $requestAuthentication): void { $authenticationMethod = $className::metadata()->getAuthenticationMethod(); if ($authenticationMethod === null) { throw new InvalidArgumentException(sprintf('Provider %s does not expect any authentication, but got %s.', $className, get_class($requestAuthentication))); } $expectedClass = $authenticationMethod->getImplementationClass(); if (!$requestAuthentication instanceof $expectedClass) { throw new InvalidArgumentException(sprintf('Provider %s expects authentication of type %s, but got %s.', $className, $expectedClass, get_class($requestAuthentication))); } $availability = $className::availability(); if ($availability instanceof WithRequestAuthenticationInterface) { $availability->setRequestAuthentication($requestAuthentication); } $modelMetadataDirectory = $className::modelMetadataDirectory(); if ($modelMetadataDirectory instanceof WithRequestAuthenticationInterface) { $modelMetadataDirectory->setRequestAuthentication($requestAuthentication); } if (is_subclass_of($className, ProviderWithOperationsHandlerInterface::class)) { $operationsHandler = $className::operationsHandler(); if ($operationsHandler instanceof WithRequestAuthenticationInterface) { $operationsHandler->setRequestAuthentication($requestAuthentication); } } } /** * Creates a default request authentication instance for a provider. * * @since 0.1.0 * * @param class-string $className The provider class name. * @return ?RequestAuthenticationInterface The default request authentication instance, or null if not required or * if no credential data can be found. */ private function createDefaultProviderRequestAuthentication(string $className): ?RequestAuthenticationInterface { $providerMetadata = $className::metadata(); $providerId = $providerMetadata->getId(); $authenticationMethod = $providerMetadata->getAuthenticationMethod(); if ($authenticationMethod === null) { return null; } $authenticationClass = $authenticationMethod->getImplementationClass(); if ($authenticationClass === null) { return null; } $authenticationSchema = $authenticationClass::getJsonSchema(); // Iterate over all JSON schema object properties to try to determine the necessary authentication data. $authenticationData = []; if (isset($authenticationSchema['properties']) && is_array($authenticationSchema['properties'])) { /** @var array $details */ foreach ($authenticationSchema['properties'] as $property => $details) { $envVarName = $this->getEnvVarName($providerId, $property); // Try to get the value from environment variable or constant. $envValue = getenv($envVarName); if ($envValue === \false) { if (!defined($envVarName)) { continue; // Skip if neither environment variable nor constant is defined. } $envValue = constant($envVarName); if (!is_scalar($envValue)) { continue; } } if (isset($details['type'])) { switch ($details['type']) { case 'boolean': $authenticationData[$property] = filter_var($envValue, \FILTER_VALIDATE_BOOLEAN); break; case 'number': $authenticationData[$property] = (int) $envValue; break; case 'string': default: $authenticationData[$property] = (string) $envValue; } } else { // Default to string if no type is specified. $authenticationData[$property] = (string) $envValue; } } // If any required fields are missing, return null to avoid immediate errors. if (isset($authenticationSchema['required']) && is_array($authenticationSchema['required'])) { /** @var list $requiredProperties */ $requiredProperties = $authenticationSchema['required']; if (array_diff_key(array_flip($requiredProperties), $authenticationData)) { return null; } } } /** @var RequestAuthenticationInterface */ /** @var array $authenticationData */ return $authenticationClass::fromArray($authenticationData); } /** * Checks if the given value is a registered provider class name. * * @since 0.4.0 * * @param string $idOrClassName The value to check. * @return bool True if it's a registered class name. * @phpstan-assert-if-true class-string $idOrClassName */ private function isRegisteredClassName(string $idOrClassName): bool { return isset($this->registeredClassNamesToIds[$idOrClassName]); } /** * Checks if the given value is a registered provider ID. * * @since 0.4.0 * * @param string $idOrClassName The value to check. * @return bool True if it's a registered provider ID. */ private function isRegisteredId(string $idOrClassName): bool { return isset($this->registeredIdsToClassNames[$idOrClassName]); } /** * Converts a provider ID and field name to a constant case environment variable name. * * @since 0.1.0 * * @param string $providerId The provider ID. * @param string $field The field name. * @return string The environment variable name in CONSTANT_CASE. */ private function getEnvVarName(string $providerId, string $field): string { // Convert camelCase or kebab-case or snake_case to CONSTANT_CASE. $constantCaseProviderId = strtoupper((string) preg_replace('/([a-z])([A-Z])/', '$1_$2', str_replace('-', '_', $providerId))); $constantCaseField = strtoupper((string) preg_replace('/([a-z])([A-Z])/', '$1_$2', str_replace('-', '_', $field))); return "{$constantCaseProviderId}_{$constantCaseField}"; } }