Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Loading