| 1 |
<?php |
| 2 |
/** |
| 3 |
* For a GraphQL query, look for the results in the WP transient cache and return that. |
| 4 |
* If not cached, when return results to client, save results to transient cache for future requests. |
| 5 |
*/ |
| 6 |
|
| 7 |
namespace WPGraphQL\SmartCache\Cache; |
| 8 |
|
| 9 |
use WPGraphQL\SmartCache\Document; |
| 10 |
use WPGraphQL\SmartCache\Storage\Transient; |
| 11 |
use WPGraphQL\SmartCache\Storage\WpCache; |
| 12 |
use WPGraphQL\SmartCache\Storage\Ephemeral; |
| 13 |
|
| 14 |
class Query { |
| 15 |
|
| 16 |
const GROUP_NAME = 'gql_cache'; |
| 17 |
|
| 18 |
/** |
| 19 |
* The storage object for the actual system of choice transient, database, object, memory, etc |
| 20 |
* |
| 21 |
* @var WpCache|Transient|Ephemeral |
| 22 |
**/ |
| 23 |
public static $storage = null; |
| 24 |
|
| 25 |
/** |
| 26 |
* The current GraphQL request. |
| 27 |
* |
| 28 |
* @var \WPGraphQL\Request|null |
| 29 |
*/ |
| 30 |
protected $request; |
| 31 |
|
| 32 |
/** |
| 33 |
* @return void |
| 34 |
*/ |
| 35 |
public function init() { |
| 36 |
if ( null === self::$storage ) { |
| 37 |
self::$storage = apply_filters( |
| 38 |
'graphql_cache_storage_object', //phpcs:ignore |
| 39 |
wp_using_ext_object_cache() ? new WpCache( self::GROUP_NAME ) : new Transient( self::GROUP_NAME ) |
| 40 |
); |
| 41 |
} |
| 42 |
} |
| 43 |
|
| 44 |
/** |
| 45 |
* Unique identifier for this request is normalized query string, operation and variables |
| 46 |
* |
| 47 |
* @param string|null $query_id queryId from the graphql query request |
| 48 |
* @param string $query query string |
| 49 |
* @param array $variables Variables sent with request or null |
| 50 |
* @param string $operation Name of operation if specified on the request or null |
| 51 |
* |
| 52 |
* @return string|false unique id for this request or false if query not provided |
| 53 |
*/ |
| 54 |
public function build_key( $query_id, $query, $variables = null, $operation = null ) { |
| 55 |
// Unique identifier for this request is normalized query string, operation and variables |
| 56 |
// If request is by queryId, get the saved query string, which is already normalized |
| 57 |
if ( $query_id ) { |
| 58 |
$saved_query = new Document(); |
| 59 |
$query = $saved_query->get( $query_id ); |
| 60 |
} elseif ( $query ) { |
| 61 |
// Query string provided, normalize it |
| 62 |
$query_ast = \GraphQL\Language\Parser::parse( $query ); |
| 63 |
$query = \GraphQL\Language\Printer::doPrint( $query_ast ); |
| 64 |
} |
| 65 |
|
| 66 |
if ( ! $query ) { |
| 67 |
return false; |
| 68 |
} |
| 69 |
|
| 70 |
// Get user ID from AppContext->viewer which is set at Request creation |
| 71 |
// and doesn't change even if wp_set_current_user(0) is called later. |
| 72 |
// We intentionally do NOT fall back to wp_get_current_user() because that |
| 73 |
// function's return value can change mid-request (e.g., when WPGraphQL calls |
| 74 |
// wp_set_current_user(0) in has_authentication_errors()). Using 0 as fallback |
| 75 |
// treats the request as unauthenticated, which is the safe default. |
| 76 |
if ( $this->request ) { |
| 77 |
$user_id = $this->request->app_context->viewer->ID; |
| 78 |
} else { |
| 79 |
$user_id = 0; |
| 80 |
} |
| 81 |
|
| 82 |
$parts = [ |
| 83 |
'query' => $query, |
| 84 |
'variables' => $variables ?: null, |
| 85 |
'operation' => $operation ?: null, |
| 86 |
'user' => $user_id, |
| 87 |
]; |
| 88 |
|
| 89 |
$parts_string = wp_json_encode( $parts ); |
| 90 |
|
| 91 |
if ( false === $parts_string ) { |
| 92 |
return false; |
| 93 |
} |
| 94 |
|
| 95 |
return hash( 'sha256', $parts_string ); |
| 96 |
} |
| 97 |
|
| 98 |
/** |
| 99 |
* Get the data from cache/transient based on the provided key |
| 100 |
* |
| 101 |
* @param string $key unique id for this request |
| 102 |
* @return mixed|array|object|null The graphql response or null if not found |
| 103 |
*/ |
| 104 |
public function get( $key ) { |
| 105 |
return self::$storage->get( $key ); |
| 106 |
} |
| 107 |
|
| 108 |
/** |
| 109 |
* Converts GraphQL query result to spec-compliant serializable array using provided function |
| 110 |
* |
| 111 |
* @param string $key unique id for this request |
| 112 |
* @param mixed|array|object|null $data The graphql response |
| 113 |
* @param int $expire Time in seconds for the data to persist in cache. Zero means no expiration. |
| 114 |
* |
| 115 |
* @return bool False if value was not set and true if value was set. |
| 116 |
*/ |
| 117 |
public function save( $key, $data, $expire = DAY_IN_SECONDS ) { |
| 118 |
return self::$storage->set( $key, $data, $expire ); |
| 119 |
} |
| 120 |
|
| 121 |
/** |
| 122 |
* Delete the data from cache/transient based on the provided key |
| 123 |
* |
| 124 |
* @param string $key unique id for this request |
| 125 |
* @return bool True on successful removal, false on failure. |
| 126 |
*/ |
| 127 |
public function delete( $key ) { |
| 128 |
return self::$storage->delete( $key ); |
| 129 |
} |
| 130 |
|
| 131 |
/** |
| 132 |
* Searches the database for all graphql transients matching our prefix |
| 133 |
* |
| 134 |
* @return int|false Count of the number deleted. False if error, nothing to delete or caching not enabled. |
| 135 |
* @return bool True on success, false on failure. |
| 136 |
*/ |
| 137 |
public function purge_all() { |
| 138 |
return self::$storage->purge_all(); |
| 139 |
} |
| 140 |
} |
| 141 |
|