Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new CacheStrategy & CacheMiddleware classes #3

Merged
merged 30 commits into from
Sep 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
93f469c
Add new CacheStrategy class
jblz Aug 20, 2024
84aa0f8
customize our TTL header
jblz Aug 20, 2024
683ab17
Implement our own CacheMiddleware class that supports POST requests a…
jblz Aug 20, 2024
3470574
add logging on fetch & cache; add mechanism to bypass cache
jblz Aug 21, 2024
ac27360
do not cache graphql mutations
jblz Aug 26, 2024
8ae5f3b
Add phpunit test cases to the HttpClientTest
jblz Aug 26, 2024
6bd4714
Merge branch 'trunk' of github.com:Automattic/remote-data-blocks into…
jblz Aug 27, 2024
32a1c9b
Use much smaller Graphql Parsing lib: infinityloop-dev/graphpinator-p…
jblz Aug 28, 2024
917bde9
POC cache TTL injection
chriszarate Aug 28, 2024
9aba65a
Add GraphQLMutationContext
chriszarate Aug 28, 2024
838c205
Provide sensible defaults for most queries
chriszarate Aug 28, 2024
0717874
Remove GraphQL request body inspection
chriszarate Aug 28, 2024
4e3956e
Merge branch 'trunk' of github.com:Automattic/remote-data-blocks into…
jblz Sep 2, 2024
1132feb
Merge branch 'trunk' of github.com:Automattic/remote-data-blocks into…
jblz Sep 24, 2024
d4616ae
fixup spacing
jblz Sep 24, 2024
31e42ba
Merge branch 'trunk' of github.com:Automattic/remote-data-blocks into…
jblz Sep 25, 2024
65db305
fix casing on Config dir
jblz Sep 25, 2024
ae0fe37
lint fixes and type annotations / enhancements
jblz Sep 25, 2024
83c1439
more php lint fixes
jblz Sep 25, 2024
aaf6be2
fix type issue caused by linter
jblz Sep 25, 2024
8aadec8
Merge branch 'trunk' of github.com:Automattic/remote-data-blocks into…
jblz Sep 25, 2024
90932a4
Merge branch 'trunk' into add/guzzle-caching-strategy
mhsdef Sep 25, 2024
6cb94c0
Merge remote-tracking branch 'origin/trunk' into add/guzzle-caching-s…
chriszarate Sep 25, 2024
cb1ef74
Fix psalm issue
chriszarate Sep 25, 2024
cb058da
Merge remote-tracking branch 'origin/trunk' into add/guzzle-caching-s…
chriszarate Sep 25, 2024
917fc00
Merge branch 'trunk' into add/guzzle-caching-strategy
jblz Sep 26, 2024
2c6d343
Merge branch 'trunk' of github.com:Automattic/remote-data-blocks into…
jblz Sep 26, 2024
d148a41
make get_cache_ttl return a nullable int
jblz Sep 26, 2024
30b0e00
Merge branch 'add/guzzle-caching-strategy' of github.com:Automattic/r…
jblz Sep 26, 2024
757c6d7
tweak the constant name
jblz Sep 26, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

namespace RemoteDataBlocks\Example\Shopify;

use RemoteDataBlocks\Config\QueryContext\GraphqlQueryContext;
use RemoteDataBlocks\Config\QueryContext\GraphqlMutationContext;

class ShopifyAddToCartMutation extends GraphqlQueryContext {
class ShopifyAddToCartMutation extends GraphqlMutationContext {
public function get_input_schema(): array {
return [
'cart_id' => [
Expand Down Expand Up @@ -48,7 +48,7 @@ public function get_output_schema(): array {
];
}

public function get_query(): string {
public function get_mutation(): string {
return '
mutation AddProductToCart( $cart_id: ID!, $variant_id: ID!, $quantity: Int = 1 ){
cartLinesAdd(cartId: $cart_id, lines: [{ quantity: $quantity, merchandiseId: $variant_id }]) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

namespace RemoteDataBlocks\Example\Shopify;

use RemoteDataBlocks\Config\QueryContext\GraphqlQueryContext;
use RemoteDataBlocks\Config\QueryContext\GraphqlMutationContext;

class ShopifyCreateCartMutation extends GraphqlQueryContext {
class ShopifyCreateCartMutation extends GraphqlMutationContext {
public function get_output_schema(): array {
return [
'root_path' => '$.data.cartCreate.cart',
Expand All @@ -24,7 +24,7 @@ public function get_output_schema(): array {
];
}

public function get_query(): string {
public function get_mutation(): string {
return '
mutation CreateShoppingCart {
cartCreate {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

namespace RemoteDataBlocks\Example\Shopify;

use RemoteDataBlocks\Config\QueryContext\GraphqlQueryContext;
use RemoteDataBlocks\Config\QueryContext\GraphqlMutationContext;

class ShopifyRemoveFromCartMutation extends GraphqlQueryContext {
class ShopifyRemoveFromCartMutation extends GraphqlMutationContext {
public function get_input_schema(): array {
return [
'cart_id' => [
Expand Down Expand Up @@ -45,7 +45,7 @@ public function get_output_schema(): array {
];
}

public function get_query(): string {
public function get_mutation(): string {
return '
mutation RemoveProductFromCart( $cart_id: ID!, $line_id: ID! ){
cartLinesAdd(cartId: $cart_id, lineIds: [ $line_id ] ) {
Expand Down
56 changes: 56 additions & 0 deletions inc/Config/QueryContext/GraphqlMutationContext.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php declare(strict_types = 1);

namespace RemoteDataBlocks\Config\QueryContext;

defined( 'ABSPATH' ) || exit();

/**
* Base class used to define a Remote Data Query. This class defines a
* composable query that allows it to be composed with another query or a block.
*
* @psalm-api
*/
abstract class GraphqlMutationContext extends HttpQueryContext {

/**
* Override this method to define a custom request method for this mutation.
*/
public function get_request_method(): string {
return 'POST';
}

/**
* Define this method to provide the GraphQL mutation document.
*
* @return string The GraphQL mutation document.
*/
abstract public function get_mutation(): string;

/**
* Override this method to define the GraphQL mutation variables.
*
* @return array The GraphQL query variables.
*/
public function get_mutation_variables( array $input_variables ): array {
return $input_variables;
}

/**
* Convert the mutation and variables into a GraphQL request body.
*/
public function get_request_body( array $input_variables ): array {
$variables = $this->get_mutation_variables( $input_variables );

return [
'query' => $this->get_mutation(),
'variables' => empty( $variables ) ? null : $variables,
];
}

/**
* GraphQL mutations are uncachable by default.
*/
public function get_cache_ttl( array $input_variables ): int {
return -1;
}
}
13 changes: 12 additions & 1 deletion inc/Config/QueryContext/GraphqlQueryContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public function get_request_method(): string {
abstract public function get_query(): string;

/**
* Override this method to define the GraphQL query document.
* Override this method to define the GraphQL query variables.
*
* @return array The GraphQL query variables.
*/
Expand All @@ -49,4 +49,15 @@ public function get_request_body( array $input_variables ): array {
'variables' => empty( $variables ) ? null : $variables,
];
}

/**
* Override this method to define the cache object TTL for this query. Return
* -1 to disable caching. Return null to use the default cache TTL.
*
* @return int|null The cache object TTL in seconds.
*/
public function get_cache_ttl( array $input_variables ): ?int {
// Use default cache TTL.
return null;
}
}
18 changes: 18 additions & 0 deletions inc/Config/QueryContext/HttpQueryContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,24 @@ public function get_query_runner(): QueryRunnerInterface {
return new QueryRunner( $this );
}

/**
* Override this method to define the cache object TTL for this query. Return
* -1 to disable caching. Return null to use the default cache TTL.
*
* @return int|null The cache object TTL in seconds.
*/
public function get_cache_ttl( array $input_variables ): null|int {
// For most HTTP requests, we only want to cache GET requests. This is
// overridden for GraphQL queries when using GraphqlQueryContext
if ( 'GET' !== strtoupper( $this->get_request_method() ) ) {
// Disable caching.
return -1;
}

// Use default cache TTL.
return null;
}

/**
* Override this method to process the raw response data from the query before
* it is passed to the query runner and the output variables are extracted. The
Expand Down
1 change: 1 addition & 0 deletions inc/Config/QueryContext/HttpQueryContextInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
* @since 0.1.0
*/
interface HttpQueryContextInterface {
public function get_cache_ttl( array $input_variables ): null|int;
public function get_endpoint( array $input_variables ): string;
public function get_request_method(): string;
public function get_request_headers( array $input_variables ): array;
Expand Down
21 changes: 13 additions & 8 deletions inc/Config/QueryRunner/QueryRunner.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,16 @@ public function __construct(
* method: string,
* options: array<string, mixed>,
* origin: string,
* ttl: int|null,
* uri: string,
* } The request details.
*/
protected function get_request_details( array $input_variables ): array|WP_Error {
$headers = $this->query_context->get_request_headers( $input_variables );
$method = $this->query_context->get_request_method();
$body = $this->query_context->get_request_body( $input_variables );
$endpoint = $this->query_context->get_endpoint( $input_variables );
$headers = $this->query_context->get_request_headers( $input_variables );
$method = $this->query_context->get_request_method();
$body = $this->query_context->get_request_body( $input_variables );
$endpoint = $this->query_context->get_endpoint( $input_variables );
$cache_ttl = $this->query_context->get_cache_ttl( $input_variables );

$parsed_url = wp_parse_url( $endpoint );

Expand Down Expand Up @@ -80,12 +82,11 @@ protected function get_request_details( array $input_variables ): array|WP_Error
$request_details = [
'method' => $method,
'options' => [
RequestOptions::HEADERS => array_merge( [
'User-Agent' => 'WordPress Remote Data Blocks/1.0',
], $headers ),
RequestOptions::HEADERS => $headers,
RequestOptions::JSON => $body,
],
'origin' => sprintf( '%s://%s%s%s%s', $scheme, $user, $pass, $host, $port ),
'ttl' => $cache_ttl,
'uri' => sprintf( '%s%s', $path, $query ),
];

Expand Down Expand Up @@ -121,7 +122,11 @@ protected function get_raw_response_data( array $input_variables ): array|WP_Err
return $request_details;
}

$this->http_client->init( $request_details['origin'] );
$client_options = [
HttpClient::CACHE_TTL_CLIENT_OPTION_KEY => $request_details['ttl'],
];

$this->http_client->init( $request_details['origin'], [], $client_options );

try {
$response = $this->http_client->request( $request_details['method'], $request_details['uri'], $request_details['options'] );
Expand Down
44 changes: 29 additions & 15 deletions inc/HttpClient/HttpClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@
use GuzzleHttp\MessageFormatter;
use GuzzleHttp\Middleware;
use GuzzleHttp\Promise\Utils;
use Kevinrob\GuzzleCache\CacheMiddleware;
use Kevinrob\GuzzleCache\KeyValueHttpHeader;
use Kevinrob\GuzzleCache\Storage\CacheStorageInterface;
use Kevinrob\GuzzleCache\Storage\WordPressObjectCacheStorage;
use Kevinrob\GuzzleCache\Strategy\GreedyCacheStrategy;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\UriInterface;
use RemoteDataBlocks\HttpClient\RdbCacheStrategy;
use RemoteDataBlocks\HttpClient\RdbCacheMiddleware;
use RemoteDataBlocks\Logging\LoggerManager;

defined( 'ABSPATH' ) || exit();
Expand All @@ -24,10 +25,12 @@ class HttpClient {
public Client $client;

private const MAX_RETRIES = 3;
private const CACHE_TTL_IN_SECONDS = 60;
private const FALLBACK_CACHE_TTL_IN_SECONDS = 60;
private const WP_OBJECT_CACHE_GROUP = 'remote-data-blocks';
private const CACHE_INVALIDATING_REQUEST_HEADERS = [ 'Authorization', 'Cache-Control' ];

public const CACHE_TTL_CLIENT_OPTION_KEY = '__default_cache_ttl';

private string $base_uri;
private HandlerStack $handler_stack;

Expand Down Expand Up @@ -55,13 +58,31 @@ class HttpClient {
*/
private array $queued_requests = [];

public static function get_cache_storage(): CacheStorageInterface {
return new WordPressObjectCacheStorage( self::WP_OBJECT_CACHE_GROUP );
}

/**
* Get the cache middleware for the HTTP client.
*
*/
public static function get_cache_middleware( CacheStorageInterface $cache_storage, int|null $default_ttl = null ): callable {
return new RdbCacheMiddleware(
new RdbCacheStrategy(
$cache_storage,
$default_ttl ?? self::FALLBACK_CACHE_TTL_IN_SECONDS,
new KeyValueHttpHeader( self::CACHE_INVALIDATING_REQUEST_HEADERS )
)
);
}

/**
* Initialize the HTTP client.
*/
public function init( string $base_uri, array $headers = [], array $options = [] ): void {
public function init( string $base_uri, array $headers = [], array $client_options = [] ): void {
$this->base_uri = $base_uri;
$this->headers = $headers;
$this->options = $options;
$this->options = $client_options;

// Initialize a request handler that uses wp_remote_request instead of cURL.
// PHP cURL bindings are not always available, e.g., in WASM environments
Expand All @@ -83,16 +104,9 @@ public function init( string $base_uri, array $headers = [], array $options = []
return $request;
} ) );

$this->handler_stack->push(
new CacheMiddleware(
new GreedyCacheStrategy(
new WordPressObjectCacheStorage( self::WP_OBJECT_CACHE_GROUP ),
self::CACHE_TTL_IN_SECONDS,
new KeyValueHttpHeader( self::CACHE_INVALIDATING_REQUEST_HEADERS )
)
),
'remote_data_blocks_cache'
);
$default_ttl = $client_options[ self::CACHE_TTL_CLIENT_OPTION_KEY ] ?? null;
$cache_middleware = self::get_cache_middleware( self::get_cache_storage(), $default_ttl );
$this->handler_stack->push( $cache_middleware, 'remote_data_blocks_cache' );

$this->handler_stack->push( Middleware::log(
LoggerManager::instance(),
Expand Down
14 changes: 14 additions & 0 deletions inc/HttpClient/RdbCacheMiddleware.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php declare(strict_types = 1);

namespace RemoteDataBlocks\HttpClient;

class RdbCacheMiddleware extends \Kevinrob\GuzzleCache\CacheMiddleware {
/**
* @var array<string, bool>
*/
// phpcs:ignore WordPress.NamingConventions.ValidVariableName.PropertyNotSnakeCase, SlevomatCodingStandard.TypeHints.PropertyTypeHint.MissingNativeTypeHint
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are needed to comply with the parent class' declaration

protected $httpMethods = [
'GET' => true,
'POST' => true,
];
}
Loading