Skip to content

Commit

Permalink
Add cached HTTP client (#589)
Browse files Browse the repository at this point in the history
* Adding cached http client support

* Rector fix

* Include headers
  • Loading branch information
srtfisher authored Sep 27, 2024
1 parent eabe2fc commit 9d15e93
Show file tree
Hide file tree
Showing 8 changed files with 411 additions and 43 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added dynamic creation of post type/taxonomy factories.
- Added `Reset_Server` trait to reset the server between tests.
- Add `with_https()` to control if the request being tested is over HTTPS.
- Add cached HTTP response support using the `cache()` method.

### Changed

- **Breaking:** Http Client pools should now be built using `->method()` and `->url()` instead.
- Dropped support for Redis as a cache backend in favor of the default object
cache drop-in.
- Allow returning falsey from `Collection::map_to_dictionary()`.
Expand Down
2 changes: 2 additions & 0 deletions src/mantle/http-client/autoload.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@

declare( strict_types=1 );

namespace Mantle\Http_Client;

use Mantle\Http_Client\Pending_Request;
use Mantle\Http_Client\Response;

Expand Down
95 changes: 95 additions & 0 deletions src/mantle/http-client/class-cache-middleware.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<?php
/**
* Cache_Middleware class
*
* @package Mantle
*/

namespace Mantle\Http_Client;

use Closure;
use DateTimeInterface;

/**
* Cache Middleware for Http Client.
*
* Allows for simple caching of HTTP requests.
*/
class Cache_Middleware {
/**
* Cache group.
*/
public const CACHE_GROUP = 'httpclient';

/**
* Constructor.
*
* @param int|DateTimeInterface|callable $ttl Time to live for the cache.
*/
public function __construct( protected mixed $ttl ) {}

/**
* Invoke the middleware.
*
* @param Pending_Request $request Request to process.
* @param Closure $next Next middleware in the stack.
* @return Response Response from the request.
*/
public function __invoke( Pending_Request $request, Closure $next ): Response {
$cache_key = $this->get_cache_key( $request );
$cache = wp_cache_get( $cache_key, self::CACHE_GROUP );

if ( $cache && $cache instanceof Response ) {
return $cache;
}

$response = $next( $request );

wp_cache_set( $cache_key, $response, self::CACHE_GROUP, $this->calculate_ttl( $request ) ); // phpcs:ignore WordPressVIPMinimum.Performance.LowExpiryCacheTime.CacheTimeUndetermined

return $response;
}

/**
* Purge the cache for a request.
*
* @param Pending_Request $request Request to purge the cache for.
*/
public function purge( Pending_Request $request ): bool {
return wp_cache_delete( $this->get_cache_key( $request ), self::CACHE_GROUP );
}

/**
* Retrieve the cache key for the request.
*
* @param Pending_Request $request Request to retrieve the cache key for.
*/
protected function get_cache_key( Pending_Request $request ): string {
return md5( json_encode( [ // phpcs:ignore WordPress.WP.AlternativeFunctions.json_encode_json_encode
$request->base_url(),
$request->url(),
$request->method(),
$request->body(),
$request->headers(),
] ) );
}

/**
* Calculate the time to live for the cache in seconds.
*
* @param Pending_Request $request Request to calculate the TTL for.
*/
protected function calculate_ttl( Pending_Request $request ): int {
if ( is_int( $this->ttl ) ) {
return $this->ttl;
}

if ( $this->ttl instanceof DateTimeInterface ) {
return $this->ttl->getTimestamp() - time();
}

$callback = $this->ttl;

return (int) $callback( $request );
}
}
Loading

0 comments on commit 9d15e93

Please sign in to comment.