config = $this->resolveOptions($options); } /** * @throws ApiException */ public function getToken(): string { try { $item = $this->cache->getItem(self::CACHE_KEY); } catch (InvalidArgumentException $e) { throw new ApiException('Unable to read the cached MyE&P access token', 0, $e); } if ($item->isHit()) { return $item->get(); } [$token, $lifetime] = $this->requestToken(); $item->set($token); $item->expiresAfter($lifetime); $this->cache->save($item); return $token; } /** * Drops the cached token so the next call fetches a fresh one. Used when the API * rejects a token the cache still considered valid. */ public function invalidate(): void { try { $this->cache->deleteItem(self::CACHE_KEY); } catch (InvalidArgumentException) { // the key is a constant, so it cannot be malformed; nothing to recover from } } /** * @return array{0: string, 1: int} the token and the seconds it may be cached for * * @throws ApiException */ private function requestToken(): array { try { $response = $this->myepClient->request('POST', $this->config['token_path'], [ 'body' => [ 'grant_type' => 'client_credentials', 'client_id' => $this->config['client_id'], 'client_secret' => $this->config['client_secret'], 'scope' => $this->config['scope'], ], ]); $statusCode = $response->getStatusCode(); $payload = $response->toArray(false); } catch (TransportException $e) { $this->logger->error('Unable to obtain a MyE&P access token', [ 'client_id' => $this->config['client_id'], 'exception' => $e, ]); throw new ApiException('Unable to obtain a MyE&P access token', 0, $e); } $token = $payload['access_token'] ?? null; if (200 !== $statusCode || false === is_string($token) || '' === $token) { $this->logger->error('MyE&P rejected the access token request', [ 'client_id' => $this->config['client_id'], 'status_code' => $statusCode, 'error' => $payload['error'] ?? null, ]); throw new ApiException('MyE&P rejected the access token request with status '.$statusCode); } $expiresIn = (int) ($payload['expires_in'] ?? self::DEFAULT_LIFETIME); return [$token, max(self::MINIMUM_LIFETIME, $expiresIn - self::EXPIRY_MARGIN)]; } private function resolveOptions(array $options): array { $optionsResolver = new OptionsResolver(); $optionsResolver->setRequired(['client_id', 'client_secret']); $optionsResolver->setDefaults([ 'token_path' => 'token', 'scope' => 'api', ]); return $optionsResolver->resolve($options); } }