Psr18Client.php 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\HttpClient;
  11. use Http\Discovery\Exception\NotFoundException;
  12. use Http\Discovery\Psr17FactoryDiscovery;
  13. use Nyholm\Psr7\Factory\Psr17Factory;
  14. use Nyholm\Psr7\Request;
  15. use Nyholm\Psr7\Uri;
  16. use Psr\Http\Client\ClientInterface;
  17. use Psr\Http\Client\NetworkExceptionInterface;
  18. use Psr\Http\Client\RequestExceptionInterface;
  19. use Psr\Http\Message\RequestFactoryInterface;
  20. use Psr\Http\Message\RequestInterface;
  21. use Psr\Http\Message\ResponseFactoryInterface;
  22. use Psr\Http\Message\ResponseInterface;
  23. use Psr\Http\Message\StreamFactoryInterface;
  24. use Psr\Http\Message\StreamInterface;
  25. use Psr\Http\Message\UriFactoryInterface;
  26. use Psr\Http\Message\UriInterface;
  27. use Symfony\Component\HttpClient\Response\StreamableInterface;
  28. use Symfony\Component\HttpClient\Response\StreamWrapper;
  29. use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
  30. use Symfony\Contracts\HttpClient\HttpClientInterface;
  31. if (!interface_exists(RequestFactoryInterface::class)) {
  32. throw new \LogicException('You cannot use the "Symfony\Component\HttpClient\Psr18Client" as the "psr/http-factory" package is not installed. Try running "composer require nyholm/psr7".');
  33. }
  34. if (!interface_exists(ClientInterface::class)) {
  35. throw new \LogicException('You cannot use the "Symfony\Component\HttpClient\Psr18Client" as the "psr/http-client" package is not installed. Try running "composer require psr/http-client".');
  36. }
  37. /**
  38. * An adapter to turn a Symfony HttpClientInterface into a PSR-18 ClientInterface.
  39. *
  40. * Run "composer require psr/http-client" to install the base ClientInterface. Run
  41. * "composer require nyholm/psr7" to install an efficient implementation of response
  42. * and stream factories with flex-provided autowiring aliases.
  43. *
  44. * @author Nicolas Grekas <p@tchwork.com>
  45. */
  46. final class Psr18Client implements ClientInterface, RequestFactoryInterface, StreamFactoryInterface, UriFactoryInterface
  47. {
  48. private $client;
  49. private $responseFactory;
  50. private $streamFactory;
  51. public function __construct(HttpClientInterface $client = null, ResponseFactoryInterface $responseFactory = null, StreamFactoryInterface $streamFactory = null)
  52. {
  53. $this->client = $client ?? HttpClient::create();
  54. $this->responseFactory = $responseFactory;
  55. $this->streamFactory = $streamFactory ?? ($responseFactory instanceof StreamFactoryInterface ? $responseFactory : null);
  56. if (null !== $this->responseFactory && null !== $this->streamFactory) {
  57. return;
  58. }
  59. if (!class_exists(Psr17Factory::class) && !class_exists(Psr17FactoryDiscovery::class)) {
  60. throw new \LogicException('You cannot use the "Symfony\Component\HttpClient\Psr18Client" as no PSR-17 factories have been provided. Try running "composer require nyholm/psr7".');
  61. }
  62. try {
  63. $psr17Factory = class_exists(Psr17Factory::class, false) ? new Psr17Factory() : null;
  64. $this->responseFactory = $this->responseFactory ?? $psr17Factory ?? Psr17FactoryDiscovery::findResponseFactory();
  65. $this->streamFactory = $this->streamFactory ?? $psr17Factory ?? Psr17FactoryDiscovery::findStreamFactory();
  66. } catch (NotFoundException $e) {
  67. throw new \LogicException('You cannot use the "Symfony\Component\HttpClient\HttplugClient" as no PSR-17 factories have been found. Try running "composer require nyholm/psr7".', 0, $e);
  68. }
  69. }
  70. /**
  71. * {@inheritdoc}
  72. */
  73. public function sendRequest(RequestInterface $request): ResponseInterface
  74. {
  75. try {
  76. $body = $request->getBody();
  77. if ($body->isSeekable()) {
  78. $body->seek(0);
  79. }
  80. $response = $this->client->request($request->getMethod(), (string) $request->getUri(), [
  81. 'headers' => $request->getHeaders(),
  82. 'body' => $body->getContents(),
  83. 'http_version' => '1.0' === $request->getProtocolVersion() ? '1.0' : null,
  84. ]);
  85. $psrResponse = $this->responseFactory->createResponse($response->getStatusCode());
  86. foreach ($response->getHeaders(false) as $name => $values) {
  87. foreach ($values as $value) {
  88. $psrResponse = $psrResponse->withAddedHeader($name, $value);
  89. }
  90. }
  91. $body = $response instanceof StreamableInterface ? $response->toStream(false) : StreamWrapper::createResource($response, $this->client);
  92. $body = $this->streamFactory->createStreamFromResource($body);
  93. if ($body->isSeekable()) {
  94. $body->seek(0);
  95. }
  96. return $psrResponse->withBody($body);
  97. } catch (TransportExceptionInterface $e) {
  98. if ($e instanceof \InvalidArgumentException) {
  99. throw new Psr18RequestException($e, $request);
  100. }
  101. throw new Psr18NetworkException($e, $request);
  102. }
  103. }
  104. /**
  105. * {@inheritdoc}
  106. */
  107. public function createRequest(string $method, $uri): RequestInterface
  108. {
  109. if ($this->responseFactory instanceof RequestFactoryInterface) {
  110. return $this->responseFactory->createRequest($method, $uri);
  111. }
  112. if (class_exists(Request::class)) {
  113. return new Request($method, $uri);
  114. }
  115. if (class_exists(Psr17FactoryDiscovery::class)) {
  116. return Psr17FactoryDiscovery::findRequestFactory()->createRequest($method, $uri);
  117. }
  118. throw new \LogicException(sprintf('You cannot use "%s()" as the "nyholm/psr7" package is not installed. Try running "composer require nyholm/psr7".', __METHOD__));
  119. }
  120. /**
  121. * {@inheritdoc}
  122. */
  123. public function createStream(string $content = ''): StreamInterface
  124. {
  125. $stream = $this->streamFactory->createStream($content);
  126. if ($stream->isSeekable()) {
  127. $stream->seek(0);
  128. }
  129. return $stream;
  130. }
  131. /**
  132. * {@inheritdoc}
  133. */
  134. public function createStreamFromFile(string $filename, string $mode = 'r'): StreamInterface
  135. {
  136. return $this->streamFactory->createStreamFromFile($filename, $mode);
  137. }
  138. /**
  139. * {@inheritdoc}
  140. */
  141. public function createStreamFromResource($resource): StreamInterface
  142. {
  143. return $this->streamFactory->createStreamFromResource($resource);
  144. }
  145. /**
  146. * {@inheritdoc}
  147. */
  148. public function createUri(string $uri = ''): UriInterface
  149. {
  150. if ($this->responseFactory instanceof UriFactoryInterface) {
  151. return $this->responseFactory->createUri($uri);
  152. }
  153. if (class_exists(Uri::class)) {
  154. return new Uri($uri);
  155. }
  156. if (class_exists(Psr17FactoryDiscovery::class)) {
  157. return Psr17FactoryDiscovery::findUrlFactory()->createUri($uri);
  158. }
  159. throw new \LogicException(sprintf('You cannot use "%s()" as the "nyholm/psr7" package is not installed. Try running "composer require nyholm/psr7".', __METHOD__));
  160. }
  161. }
  162. /**
  163. * @internal
  164. */
  165. class Psr18NetworkException extends \RuntimeException implements NetworkExceptionInterface
  166. {
  167. private $request;
  168. public function __construct(TransportExceptionInterface $e, RequestInterface $request)
  169. {
  170. parent::__construct($e->getMessage(), 0, $e);
  171. $this->request = $request;
  172. }
  173. public function getRequest(): RequestInterface
  174. {
  175. return $this->request;
  176. }
  177. }
  178. /**
  179. * @internal
  180. */
  181. class Psr18RequestException extends \InvalidArgumentException implements RequestExceptionInterface
  182. {
  183. private $request;
  184. public function __construct(TransportExceptionInterface $e, RequestInterface $request)
  185. {
  186. parent::__construct($e->getMessage(), 0, $e);
  187. $this->request = $request;
  188. }
  189. public function getRequest(): RequestInterface
  190. {
  191. return $this->request;
  192. }
  193. }