Add PSR HTTP Message Interfaces and Dependencies
- Implemented StreamInterface, UploadedFileInterface, and UriInterface as per PSR standards. - Added getallheaders function to retrieve HTTP headers in a compatible manner. - Included LICENSE files for ralouphie/getallheaders and symfony/deprecation-contracts. - Introduced function for triggering deprecation notices in Symfony.
This commit is contained in:
@@ -0,0 +1,326 @@
|
||||
<?php
|
||||
/**
|
||||
* Class ATFPP\AI_Translate\Services\Cache\Service_Request_Cache
|
||||
*
|
||||
* @since 0.1.0
|
||||
* @package ai-services
|
||||
*/
|
||||
|
||||
namespace ATFPP\AI_Translate\Services\Cache;
|
||||
|
||||
use Exception;
|
||||
use ATFPP\AI_Translate\Services\Contracts\Generative_AI_Model;
|
||||
use ATFPP\AI_Translate\Services\Contracts\Generative_AI_Service;
|
||||
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Contracts\Arrayable;
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Class that allows to wrap service method calls so that their return values are cached.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*/
|
||||
final class Service_Request_Cache {
|
||||
|
||||
/**
|
||||
* Wraps the given method call in a WordPress transient so that its return value is cached.
|
||||
*
|
||||
* The transient name is generated based on the method name and arguments. It is unique per service and method
|
||||
* name, so that different services can have methods with the same name that are cached separately. It also
|
||||
* includes a timestamp of when the service configuration was last changed, so that the cache is invalidated as
|
||||
* needed.
|
||||
*
|
||||
* If the method throws an exception, the exception is cached as well, so that it can be rethrown on subsequent
|
||||
* calls.
|
||||
*
|
||||
* The transient is stored for 24 hours.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @param string $service_slug Service slug.
|
||||
* @param callable $method Method to cache.
|
||||
* @param mixed[] $args Optional. Method arguments. Default empty array.
|
||||
* @return mixed Method return value, potentially served from cache.
|
||||
*
|
||||
* @throws Exception Rethrown original exception from the method call, if there was one.
|
||||
*/
|
||||
public static function wrap_transient( string $service_slug, callable $method, array $args = array() ) {
|
||||
$key = self::get_cache_key( $method, $args );
|
||||
$last_changed = self::get_last_changed( $service_slug );
|
||||
|
||||
$transient_name = "ATFPP:{$service_slug}:{$key}:{$last_changed}";
|
||||
|
||||
$value = get_transient( $transient_name );
|
||||
if ( false === $value ) {
|
||||
$value = self::call_method( $method, $args );
|
||||
set_transient( $transient_name, self::sanitize_value_for_cache( $value ), DAY_IN_SECONDS );
|
||||
} else {
|
||||
$value = self::parse_value_from_cache( $value );
|
||||
}
|
||||
if ( $value instanceof Exception ) {
|
||||
throw $value;
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps the given method call in the WordPress object cache so that its return value is cached.
|
||||
*
|
||||
* The cache key is generated based on the method name and arguments. It is unique per service and method name,
|
||||
* so that different services can have methods with the same name that are cached separately. It also includes
|
||||
* a timestamp of when the service configuration was last changed, so that the cache is invalidated as needed.
|
||||
*
|
||||
* The service slug is used as the cache group.
|
||||
*
|
||||
* If the method throws an exception, the exception is cached as well, so that it can be rethrown on subsequent
|
||||
* calls.
|
||||
*
|
||||
* The cached value is stored for 24 hours.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @param string $service_slug Service slug.
|
||||
* @param callable $method Method to cache.
|
||||
* @param mixed[] $args Optional. Method arguments. Default empty array.
|
||||
* @return mixed Method return value, potentially served from cache.
|
||||
*
|
||||
* @throws Exception Rethrown original exception from the method call, if there was one.
|
||||
*/
|
||||
public static function wrap_cache( string $service_slug, callable $method, array $args = array() ) {
|
||||
$key = self::get_cache_key( $method, $args );
|
||||
$last_changed = self::get_last_changed( $service_slug );
|
||||
|
||||
$cache_name = "{$key}:{$last_changed}";
|
||||
|
||||
$value = wp_cache_get( $cache_name, $service_slug );
|
||||
if ( false === $value ) {
|
||||
$value = self::call_method( $method, $args );
|
||||
wp_cache_set( $cache_name, self::sanitize_value_for_cache( $value ), $service_slug, DAY_IN_SECONDS );
|
||||
} else {
|
||||
$value = self::parse_value_from_cache( $value );
|
||||
}
|
||||
if ( $value instanceof Exception ) {
|
||||
throw $value;
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates the caches for a service.
|
||||
*
|
||||
* This method should be called whenever the configuration of a service changes, so that the caches are invalidated
|
||||
* and the next request will fetch fresh data. This encompasses both transients and the object cache.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @param string $service_slug Service slug.
|
||||
*/
|
||||
public static function invalidate_caches( string $service_slug ): void {
|
||||
self::set_last_changed( $service_slug );
|
||||
|
||||
// Not strictly necessary, but if we can clean up, let's do so.
|
||||
if (
|
||||
function_exists( 'wp_cache_flush_group' ) &&
|
||||
function_exists( 'wp_cache_supports' ) &&
|
||||
wp_cache_supports( 'flush_group' )
|
||||
) {
|
||||
wp_cache_flush_group( $service_slug );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the given method with the given arguments, catching any exceptions that are thrown.
|
||||
*
|
||||
* If an exception is thrown, it will be returned instead of the method's return value.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @param callable $method Method to call.
|
||||
* @param mixed[] $args Method arguments.
|
||||
* @return mixed Method return value or exception.
|
||||
*/
|
||||
private static function call_method( callable $method, array $args ) {
|
||||
try {
|
||||
return call_user_func_array( $method, $args );
|
||||
} catch ( Exception $e ) {
|
||||
return $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizes the given value to be stored in the cache.
|
||||
*
|
||||
* If the value is an exception, it is converted to an array with the exception class name and message.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @param mixed $value Value to sanitize.
|
||||
* @return mixed Sanitized value.
|
||||
*/
|
||||
private static function sanitize_value_for_cache( $value ) {
|
||||
// Exception thrown.
|
||||
if ( is_object( $value ) && $value instanceof Exception ) {
|
||||
return array(
|
||||
'classname' => get_class( $value ),
|
||||
'message' => $value->getMessage(),
|
||||
);
|
||||
}
|
||||
|
||||
// Arrayable class object.
|
||||
if ( is_object( $value ) && $value instanceof Arrayable && method_exists( get_class( $value ), 'from_array' ) ) {
|
||||
return array(
|
||||
'classname' => get_class( $value ),
|
||||
'data' => $value->to_array(),
|
||||
);
|
||||
}
|
||||
|
||||
// Array (recursion necessary).
|
||||
if ( is_array( $value ) ) {
|
||||
foreach ( $value as $key => $item ) {
|
||||
$value[ $key ] = self::sanitize_value_for_cache( $item );
|
||||
}
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the given value from the cache.
|
||||
*
|
||||
* This converts any sanitized exceptions back to their original exception form.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @param mixed $value Value from the cache.
|
||||
* @return mixed Parsed value.
|
||||
*
|
||||
* @throws RuntimeException Thrown if the cached value uses an invalid class.
|
||||
*/
|
||||
private static function parse_value_from_cache( $value ) {
|
||||
// Exception thrown.
|
||||
if ( is_array( $value ) && isset( $value['classname'], $value['message'] ) ) {
|
||||
$class = $value['classname'];
|
||||
if ( ! class_exists( $class ) ) { // This should never be true, but a reasonable safeguard.
|
||||
$class = Exception::class;
|
||||
}
|
||||
$message = $value['message'];
|
||||
return new $class( $message );
|
||||
}
|
||||
|
||||
// Arrayable class object.
|
||||
if ( is_array( $value ) && isset( $value['classname'], $value['data'] ) ) {
|
||||
$class = $value['classname'];
|
||||
if ( ! class_exists( $class ) || ! method_exists( $class, 'from_array' ) ) { // This should never be true, but a reasonable safeguard.
|
||||
throw new RuntimeException(
|
||||
sprintf(
|
||||
/* translators: %s: class name */
|
||||
esc_html__( 'The class %s from the cached value does not exist or does not have a from_array method.', 'ai-services' ),
|
||||
esc_html( $class )
|
||||
)
|
||||
);
|
||||
}
|
||||
$data = $value['data'];
|
||||
return call_user_func( array( $class, 'from_array' ), $data );
|
||||
}
|
||||
|
||||
// Array (recursion necessary).
|
||||
if ( is_array( $value ) ) {
|
||||
foreach ( $value as $key => $item ) {
|
||||
$value[ $key ] = self::parse_value_from_cache( $item );
|
||||
}
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the cache key for a method call.
|
||||
*
|
||||
* The returned key does not include the service slug, so the service slug has to be separately included as part of
|
||||
* the identifier for where to cache the value.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @param callable $method Method to cache.
|
||||
* @param mixed[] $args Optional. Method arguments. Default empty array.
|
||||
* @return string Cache key.
|
||||
*
|
||||
* @throws InvalidArgumentException Thrown if the method is not a method on a service or model instance.
|
||||
*/
|
||||
private static function get_cache_key( callable $method, array $args = array() ): string {
|
||||
if ( ! is_array( $method ) || ! is_object( $method[0] ) || ! is_string( $method[1] ) ) {
|
||||
throw new InvalidArgumentException(
|
||||
esc_html__( 'Only methods on service and model instances can be cached.', 'ai-services' )
|
||||
);
|
||||
}
|
||||
|
||||
if ( $method[0] instanceof Generative_AI_Service ) {
|
||||
$type = 'service';
|
||||
} elseif ( $method[0] instanceof Generative_AI_Model ) {
|
||||
$type = 'model';
|
||||
} else {
|
||||
throw new InvalidArgumentException(
|
||||
esc_html__( 'Only methods on service and model instances can be cached.', 'ai-services' )
|
||||
);
|
||||
}
|
||||
|
||||
return $type . ':' . self::get_cache_hash( $method[1], $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the cache hash for a method call.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @param string $method_name Method name.
|
||||
* @param mixed[] $args Optional. Method arguments. Default empty array.
|
||||
* @return string Cache hash.
|
||||
*/
|
||||
private static function get_cache_hash( string $method_name, array $args = array() ): string {
|
||||
$hash = $method_name;
|
||||
if ( ! empty( $args ) ) {
|
||||
// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
|
||||
$hash .= '_' . md5( serialize( $args ) );
|
||||
}
|
||||
return $hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the last changed value for a service.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @param string $service_slug Service slug.
|
||||
* @return string UNIX timestamp for when the configuration of the service was last changed.
|
||||
*/
|
||||
private static function get_last_changed( string $service_slug ): string {
|
||||
if ( wp_using_ext_object_cache() ) {
|
||||
return wp_cache_get_last_changed( $service_slug );
|
||||
}
|
||||
|
||||
$last_changed_option = (array) get_option( 'ais_services_last_changed', array() );
|
||||
if ( ! isset( $last_changed_option[ $service_slug ] ) ) {
|
||||
$last_changed_option[ $service_slug ] = microtime();
|
||||
update_option( 'ais_services_last_changed', $last_changed_option );
|
||||
}
|
||||
return $last_changed_option[ $service_slug ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the last changed value for a service to the current UNIX timestamp.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @param string $service_slug Service slug.
|
||||
*/
|
||||
private static function set_last_changed( string $service_slug ): void {
|
||||
if ( wp_using_ext_object_cache() ) {
|
||||
wp_cache_set_last_changed( $service_slug );
|
||||
return;
|
||||
}
|
||||
|
||||
$last_changed_option = (array) get_option( 'ais_services_last_changed', array() );
|
||||
$last_changed_option[ $service_slug ] = microtime();
|
||||
update_option( 'ais_services_last_changed', $last_changed_option );
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user