Skip to content

Commit

Permalink
Merge pull request #566 from FriendsOfSymfony/psr-http
Browse files Browse the repository at this point in the history
use psr17 and psr18 factories
  • Loading branch information
dbu committed Mar 28, 2024
2 parents 4b4751b + b98accf commit b36ddb5
Show file tree
Hide file tree
Showing 11 changed files with 110 additions and 104 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -13,6 +13,8 @@ See also the [GitHub releases page](https://github.com/FriendsOfSymfony/FOSHttpC
* Drop support for Symfony < 6.4
* Test with PHP 8.2 and 8.3
* Drop support for PHP < 8.1
* Switched to PSR-17 message factories
* Switched some places to PSR-18 HTTP client. The main functionality needs the Httplug Async Client specification. There is no PSR for asynchronous clients.
* Parameter and return type declarations where possible.
* Ignore empty tag lists passed to `TagCapable::invalidateTags` so you don't need to check if there are tags or not.

Expand Down
11 changes: 6 additions & 5 deletions composer.json
Expand Up @@ -24,11 +24,11 @@
"php": "^8.1",
"symfony/event-dispatcher": "^6.4 || ^7.0",
"symfony/options-resolver": "^6.4 || ^7.0",
"php-http/client-implementation": "^1.0 || ^2.0",
"php-http/client-common": "^1.1.0 || ^2.0",
"php-http/message": "^1.0 || ^2.0",
"php-http/message-factory": "^1.0",
"php-http/discovery": "^1.12"
"php-http/discovery": "^1.12",
"php-http/async-client-implementation": "^1.1.0 || ^2.0",
"psr/http-client-implementation": "^1.0 || ^2.0",
"psr/http-factory": "^1.0"
},
"require-dev": {
"mockery/mockery": "^1.6.0",
Expand All @@ -41,7 +41,8 @@
},
"conflict": {
"toflar/psr6-symfony-http-cache-store": "<2.2.1",
"phpunit/phpunit": "<10"
"phpunit/phpunit": "<10",
"guzzlehttp/psr7": "<2"
},
"suggest": {
"friendsofsymfony/http-cache-bundle": "For integration with the Symfony framework",
Expand Down
24 changes: 5 additions & 19 deletions doc/installation.rst
Expand Up @@ -9,28 +9,15 @@ and its dependencies using Composer_:

.. code-block:: bash
$ composer require friendsofsymfony/http-cache
The library relies on HTTPlug_ for sending invalidation requests over HTTP, so
you need to install an HTTPlug-compatible client or adapter first:

.. code-block:: bash
$ composer require php-http/guzzle6-adapter
You also need a `PSR-7 message implementation`_. If you use Guzzle 6, Guzzle’s
implementation is already included. If you use another client, you need to
install one of the message implementations. Recommended:

.. code-block:: bash
$ composer require guzzlehttp/psr7
$ composer require friendsofsymfony/http-cache
Alternatively:
The library relies on HTTPlug_ for asynchronously sending invalidation requests
over HTTP. There is no PSR for asynchronous HTTP client, you need to install an
HTTPlug-compatible client or adapter:

.. code-block:: bash
$ composer require zendframework/zend-diactoros
$ composer require php-http/guzzle7-adapter
Then install the FOSHttpCache library itself:

Expand Down Expand Up @@ -73,6 +60,5 @@ invalidation requests:

.. _Packagist: https://packagist.org/packages/friendsofsymfony/http-cache
.. _Composer: http://getcomposer.org
.. _PSR-7 message implementation: https://packagist.org/providers/psr/http-message-implementation
.. _Semantic Versioning: http://semver.org/
.. _HTTPlug: http://httplug.io
7 changes: 3 additions & 4 deletions doc/proxy-clients.rst
Expand Up @@ -447,10 +447,9 @@ all requests together, reducing the performance impact of sending invalidation
requests.

.. _HTTPlug: http://httplug.io/
.. _HTTPlug discovery: http://php-http.readthedocs.io/en/latest/discovery.html
.. _in the HTTPlug documentation: http://php-http.readthedocs.io/en/latest/clients.html
.. _HTTPlug plugins: http://php-http.readthedocs.io/en/latest/plugins/index.html
.. _message factory and URI factory: http://php-http.readthedocs.io/en/latest/message/message-factory.html
.. _HTTPlug discovery: https://docs.php-http.org/en/latest/discovery.html
.. _in the HTTPlug documentation: https://docs.php-http.org/en/latest/clients.html
.. _HTTPlug plugins: https://docs.php-http.org/en/latest/plugins/index.html
.. _Toflar Psr6Store: https://github.com/Toflar/psr6-symfony-http-cache-store
.. _Fastly Purge API: https://docs.fastly.com/api/purge
.. _Cloudflare: https://developers.cloudflare.com/cache/
Expand Down
6 changes: 3 additions & 3 deletions src/ProxyClient/Cloudflare.php
Expand Up @@ -14,7 +14,7 @@
use FOS\HttpCache\ProxyClient\Invalidation\ClearCapable;
use FOS\HttpCache\ProxyClient\Invalidation\PurgeCapable;
use FOS\HttpCache\ProxyClient\Invalidation\TagCapable;
use Http\Message\RequestFactory;
use Psr\Http\Message\RequestFactoryInterface;
use Psr\Http\Message\UriInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

Expand Down Expand Up @@ -54,13 +54,13 @@ class Cloudflare extends HttpProxyClient implements ClearCapable, PurgeCapable,
public function __construct(
Dispatcher $dispatcher,
array $options = [],
?RequestFactory $messageFactory = null
?RequestFactoryInterface $requestFactory = null
) {
if (!function_exists('json_encode')) {
throw new \Exception('ext-json is required for cloudflare invalidation');
}

parent::__construct($dispatcher, $options, $messageFactory);
parent::__construct($dispatcher, $options, $requestFactory);
}

/**
Expand Down
8 changes: 4 additions & 4 deletions src/ProxyClient/Fastly.php
Expand Up @@ -15,7 +15,7 @@
use FOS\HttpCache\ProxyClient\Invalidation\PurgeCapable;
use FOS\HttpCache\ProxyClient\Invalidation\RefreshCapable;
use FOS\HttpCache\ProxyClient\Invalidation\TagCapable;
use Http\Message\RequestFactory;
use Psr\Http\Message\RequestFactoryInterface;
use Psr\Http\Message\UriInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

Expand Down Expand Up @@ -50,13 +50,13 @@ class Fastly extends HttpProxyClient implements ClearCapable, PurgeCapable, Refr
public function __construct(
Dispatcher $dispatcher,
array $options = [],
?RequestFactory $messageFactory = null
?RequestFactoryInterface $requestFactory = null
) {
if (!function_exists('json_encode')) {
throw new \Exception('ext-json is required for fastly invalidation');
}

parent::__construct($dispatcher, $options, $messageFactory);
parent::__construct($dispatcher, $options, $requestFactory);
}

/**
Expand All @@ -81,7 +81,7 @@ public function invalidateTags(array $tags): static
$url,
$headers,
false,
json_encode(['surrogate_keys' => $tagChunk])
json_encode(['surrogate_keys' => $tagChunk], JSON_THROW_ON_ERROR)
);
}

Expand Down
36 changes: 18 additions & 18 deletions src/ProxyClient/HttpDispatcher.php
Expand Up @@ -23,10 +23,10 @@
use Http\Client\Exception\NetworkException;
use Http\Client\HttpAsyncClient;
use Http\Discovery\HttpAsyncClientDiscovery;
use Http\Discovery\UriFactoryDiscovery;
use Http\Message\UriFactory;
use Http\Discovery\Psr17FactoryDiscovery;
use Http\Promise\Promise;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\UriFactoryInterface;
use Psr\Http\Message\UriInterface;

/**
Expand All @@ -37,7 +37,7 @@
class HttpDispatcher implements Dispatcher
{
private HttpAsyncClient $httpClient;
private UriFactory $uriFactory;
private UriFactoryInterface $uriFactory;

/**
* Queued requests.
Expand Down Expand Up @@ -66,24 +66,24 @@ class HttpDispatcher implements Dispatcher
* class and overwrite getServers. Be sure to have some caching in
* getServers.
*
* @param string[] $servers Caching proxy server hostnames or IP
* addresses, including port if not port 80.
* E.g. ['127.0.0.1:6081']
* @param string $baseUri Default application hostname, optionally
* including base URL, for purge and refresh
* requests (optional). This is required if
* you purge and refresh paths instead of
* absolute URLs
* @param HttpAsyncClient|null $httpClient Client capable of sending HTTP requests. If no
* client is supplied, a default one is created
* @param UriFactory|null $uriFactory Factory for PSR-7 URIs. If not specified, a
* default one is created
* @param string[] $servers Caching proxy server hostnames or IP
* addresses, including port if not port 80.
* E.g. ['127.0.0.1:6081']
* @param string $baseUri Default application hostname, optionally
* including base URL, for purge and refresh
* requests (optional). This is required if
* you purge and refresh paths instead of
* absolute URLs
* @param HttpAsyncClient|null $httpClient Client capable of sending HTTP requests. If no
* client is supplied, a default one is created
* @param UriFactoryInterface|null $uriFactory Factory for PSR-7 URIs. If not specified, a
* default one is created
*/
public function __construct(
array $servers,
string $baseUri = '',
?HttpAsyncClient $httpClient = null,
?UriFactory $uriFactory = null
?UriFactoryInterface $uriFactory = null,
) {
if (!$httpClient) {
$httpClient = new PluginClient(
Expand All @@ -92,7 +92,7 @@ public function __construct(
);
}
$this->httpClient = $httpClient;
$this->uriFactory = $uriFactory ?: UriFactoryDiscovery::find();
$this->uriFactory = $uriFactory ?: Psr17FactoryDiscovery::findUriFactory();

$this->setServers($servers);
$this->setBaseUri($baseUri);
Expand Down Expand Up @@ -286,7 +286,7 @@ private function filterUri(string $uriString, array $allowedParts = []): UriInte

try {
$uri = $this->uriFactory->createUri($uriString);
} catch (\InvalidArgumentException $e) {
} catch (\InvalidArgumentException) {
throw InvalidUrlException::invalidUrl($uriString);
}

Expand Down
42 changes: 29 additions & 13 deletions src/ProxyClient/HttpProxyClient.php
Expand Up @@ -11,8 +11,9 @@

namespace FOS\HttpCache\ProxyClient;

use Http\Discovery\MessageFactoryDiscovery;
use Http\Message\RequestFactory;
use Http\Discovery\Psr17FactoryDiscovery;
use Psr\Http\Message\RequestFactoryInterface;
use Psr\Http\Message\StreamFactoryInterface;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UriInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
Expand All @@ -29,7 +30,8 @@ abstract class HttpProxyClient implements ProxyClient
*/
private Dispatcher $httpDispatcher;

private RequestFactory $requestFactory;
private RequestFactoryInterface $requestFactory;
private StreamFactoryInterface $streamFactory;

/**
* The options configured in the constructor argument or default values.
Expand All @@ -41,19 +43,23 @@ abstract class HttpProxyClient implements ProxyClient
/**
* The base class has no options.
*
* @param Dispatcher $dispatcher Helper to send instructions to the caching proxy
* @param array $options Options for this client
* @param RequestFactory|null $messageFactory Factory for PSR-7 messages. If none supplied,
* a default one is created
* @param Dispatcher $dispatcher Helper to send instructions to the caching proxy
* @param array $options Options for this client
* @param RequestFactoryInterface|null $requestFactory Factory for PSR-7 messages. If none supplied,
* a default one is created
* @param StreamFactoryInterface|null $streamFactory Factory for PSR-7 streams. If none supplied,
* a default one is created
*/
public function __construct(
Dispatcher $dispatcher,
array $options = [],
?RequestFactory $messageFactory = null
?RequestFactoryInterface $requestFactory = null,
?StreamFactoryInterface $streamFactory = null,
) {
$this->httpDispatcher = $dispatcher;
$this->options = $this->configureOptions()->resolve($options);
$this->requestFactory = $messageFactory ?: MessageFactoryDiscovery::find();
$this->requestFactory = $requestFactory ?: Psr17FactoryDiscovery::findRequestFactory();
$this->streamFactory = $streamFactory ?: Psr17FactoryDiscovery::findStreamFactory();
}

public function flush(): int
Expand All @@ -78,10 +84,20 @@ protected function configureOptions(): OptionsResolver
*/
protected function queueRequest(string $method, UriInterface|string $url, array $headers, bool $validateHost = true, $body = null): void
{
$this->httpDispatcher->invalidate(
$this->requestFactory->createRequest($method, $url, $headers, $body),
$validateHost
);
$request = $this->requestFactory->createRequest($method, $url);
foreach ($headers as $name => $value) {
$request = $request->withHeader($name, $value);
}
if ($body) {
if (is_string($body)) {
$body = $this->streamFactory->createStream($body);
} elseif (is_resource($body)) {
$body = $this->streamFactory->createStreamFromResource($body);
}
$request = $request->withBody($body);
}

$this->httpDispatcher->invalidate($request, $validateHost);
}

/**
Expand Down
25 changes: 14 additions & 11 deletions src/Test/HttpClient.php
Expand Up @@ -11,10 +11,9 @@

namespace FOS\HttpCache\Test;

use Http\Client\HttpClient as PhpHttpClient;
use Http\Discovery\HttpClientDiscovery;
use Http\Discovery\MessageFactoryDiscovery;
use Http\Discovery\UriFactoryDiscovery;
use Http\Discovery\Psr17FactoryDiscovery;
use Http\Discovery\Psr18ClientDiscovery;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\UriInterface;
Expand All @@ -27,7 +26,7 @@ class HttpClient
/**
* HTTP client for requests to the application.
*/
private PhpHttpClient $httpClient;
private ClientInterface $httpClient;

private string $hostname;

Expand Down Expand Up @@ -72,10 +71,10 @@ public function sendRequest(RequestInterface $request): ResponseInterface
/**
* Get HTTP client for your application.
*/
private function getHttpClient(): PhpHttpClient
private function getHttpClient(): ClientInterface
{
if (!isset($this->httpClient)) {
$this->httpClient = HttpClientDiscovery::find();
$this->httpClient = Psr18ClientDiscovery::find();
}

return $this->httpClient;
Expand All @@ -102,18 +101,22 @@ private function createRequest(string $method, string $uri, array $headers): Req
$uri = $uri->withScheme('http');
}

return MessageFactoryDiscovery::find()->createRequest(
$request = Psr17FactoryDiscovery::findRequestFactory()->createRequest(
$method,
$uri,
$headers
$uri
);
foreach ($headers as $name => $value) {
$request = $request->withHeader($name, $value);
}

return $request;
}

/**
* Create PSR-7 URI object from URI string.
*/
private function createUri(string $uriString): UriInterface
{
return UriFactoryDiscovery::find()->createUri($uriString);
return Psr17FactoryDiscovery::findUriFactory()->createUri($uriString);
}
}
6 changes: 3 additions & 3 deletions tests/Functional/ProxyClient/HttpDispatcherTest.php
Expand Up @@ -15,14 +15,14 @@
use FOS\HttpCache\Exception\ProxyResponseException;
use FOS\HttpCache\Exception\ProxyUnreachableException;
use FOS\HttpCache\ProxyClient\HttpDispatcher;
use Http\Discovery\MessageFactoryDiscovery;
use Http\Discovery\Psr17FactoryDiscovery;
use PHPUnit\Framework\TestCase;

class HttpDispatcherTest extends TestCase
{
public function testNetworkError(): void
{
$requestFactory = MessageFactoryDiscovery::find();
$requestFactory = Psr17FactoryDiscovery::findRequestFactory();
$dispatcher = new HttpDispatcher(['localhost:1']);
$dispatcher->invalidate($requestFactory->createRequest('GET', 'http://fos.test/foobar'));

Expand All @@ -36,7 +36,7 @@ public function testNetworkError(): void

public function testClientError(): void
{
$requestFactory = MessageFactoryDiscovery::find();
$requestFactory = Psr17FactoryDiscovery::findRequestFactory();
$dispatcher = new HttpDispatcher(['http://foshttpcache.readthedocs.io']);
$dispatcher->invalidate($requestFactory->createRequest('GET', 'http://fos.test/this-url-should-not-exist'));

Expand Down

0 comments on commit b36ddb5

Please sign in to comment.