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:
2025-12-28 12:44:00 +01:00
parent cf600ae727
commit cd264483f8
410 changed files with 60841 additions and 16 deletions

View File

@@ -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 );
}
}