From 10178ad5931c805f956bb8d50639ec39f72c8add Mon Sep 17 00:00:00 2001 From: Colin Murphy Date: Fri, 8 Aug 2025 15:28:06 +0100 Subject: [PATCH 1/2] Fixed activation and de-activation hooks. Fixed bug with plugin initialisation. --- plugins/wpgraphql-logging/activation.php | 3 + plugins/wpgraphql-logging/deactivation.php | 3 + .../src/Events/QueryEventLifecycle.php | 112 ++++++++++++++---- plugins/wpgraphql-logging/src/Hooks/.gitkeep | 0 .../src/Hooks/PluginHooks.php | 57 --------- plugins/wpgraphql-logging/src/Plugin.php | 18 ++- .../tests/wpunit/Hooks/PluginHooksTest.php | 71 ----------- .../wpgraphql-logging/wpgraphql-logging.php | 4 +- 8 files changed, 115 insertions(+), 153 deletions(-) delete mode 100644 plugins/wpgraphql-logging/src/Hooks/.gitkeep delete mode 100644 plugins/wpgraphql-logging/src/Hooks/PluginHooks.php delete mode 100644 plugins/wpgraphql-logging/tests/wpunit/Hooks/PluginHooksTest.php diff --git a/plugins/wpgraphql-logging/activation.php b/plugins/wpgraphql-logging/activation.php index a9847faa..102cd706 100644 --- a/plugins/wpgraphql-logging/activation.php +++ b/plugins/wpgraphql-logging/activation.php @@ -9,9 +9,12 @@ declare(strict_types=1); +use WPGraphQL\Logging\Plugin; + /** * Runs when the plugin is activated. */ function wpgraphql_logging_activation_callback(): void { + Plugin::activate(); do_action( 'wpgraphql_logging_activate' ); } diff --git a/plugins/wpgraphql-logging/deactivation.php b/plugins/wpgraphql-logging/deactivation.php index d76049c8..02ceb8da 100644 --- a/plugins/wpgraphql-logging/deactivation.php +++ b/plugins/wpgraphql-logging/deactivation.php @@ -10,9 +10,12 @@ * @since 0.0.1 */ +use WPGraphQL\Logging\Plugin; + /** * Runs when the plugin is deactivated. */ function wpgraphql_logging_deactivation_callback(): void { + Plugin::deactivate(); do_action( 'wpgraphql_logging_deactivate' ); } diff --git a/plugins/wpgraphql-logging/src/Events/QueryEventLifecycle.php b/plugins/wpgraphql-logging/src/Events/QueryEventLifecycle.php index 4446d8e3..ca589f6d 100644 --- a/plugins/wpgraphql-logging/src/Events/QueryEventLifecycle.php +++ b/plugins/wpgraphql-logging/src/Events/QueryEventLifecycle.php @@ -4,13 +4,16 @@ namespace WPGraphQL\Logging\Events; +use GraphQL\Executor\ExecutionResult; +use GraphQL\Server\OperationParams; use Monolog\Level; use WPGraphQL\Logging\Logger\LoggerService; +use WPGraphQL\Request; /** * WPGraphQL Query Event Lifecycle - * - * POC @TODO - Add pub/sub for query events. + * Handles logging for GraphQL query lifecycle events. * * @package WPGraphQL\Logging * @@ -29,7 +32,7 @@ class QueryEventLifecycle { * * @param \WPGraphQL\Logging\Logger\LoggerService $logger */ - protected function __construct(readonly LoggerService $logger) { + protected function __construct( readonly LoggerService $logger ) { } /** @@ -42,20 +45,26 @@ public static function init(): QueryEventLifecycle { self::$instance = new self( $logger ); self::$instance->setup(); } + return self::$instance; } /** * Logs the pre-request event for a GraphQL query. + * This method is hooked into 'do_graphql_request'. * - * @param string $query The GraphQL query. - * @param mixed $variables The variables for the query. - * @param string $operation_name The name of the operation. + * @param string $query The GraphQL query string. + * @param string|null $operation_name The name of the operation. Made nullable. + * @param array|null $variables The variables for the query. Made nullable. */ - public function log_pre_request( $query, $variables, $operation_name ): void { - + public function log_pre_request( string $query, ?string $operation_name, ?array $variables ): void { try { - $context = []; + $context = [ + 'query' => $query, + 'variables' => $variables, + 'operation_name' => $operation_name, + ]; + $context = apply_filters( 'wpgraphql_logging_pre_request_context', $context, $query, $variables, $operation_name ); $level = apply_filters( 'wpgraphql_logging_pre_request_level', Level::Info, $query, $variables, $operation_name ); $this->logger->log( $level, 'WPGraphQL Incoming Request', $context ); @@ -67,24 +76,86 @@ public function log_pre_request( $query, $variables, $operation_name ): void { /** * Logs the post-request event for a GraphQL query. + * This method is now hooked into 'graphql_after_execute'. * - * @param mixed $response The response from the GraphQL request. - * @param mixed $result The result of the GraphQL request. - * @param string $operation_name The name of the operation. - * @param string $query The GraphQL query. - * @param array $variables The variables for the query. + * @param \GraphQL\Executor\ExecutionResult|array $response The GraphQL execution result(s). + * This can be a single ExecutionResult object or an array of them for batch requests. + * @param \WPGraphQL\Request $request_instance The WPGraphQL Request instance. */ - public function log_post_request( $response, $result, string $operation_name, string $query, array $variables ): void { + public function log_post_request( $response, Request $request_instance ): void { + // Extract relevant data from the WPGraphQL Request instance + $params = $request_instance->get_params(); // Can be OperationParams or array of OperationParams + $query = null; + $operation_name = null; + $variables = null; + $status_code = 200; // Default success status + + // Handle single or batch requests to get query details + if ( $params instanceof OperationParams ) { + $query = $params->query; + $operation_name = $params->operation; + $variables = $params->variables; + } elseif ( is_array( $params ) && ! empty( $params[0] ) && $params[0] instanceof OperationParams ) { + $query = $params[0]->query; + $operation_name = $params[0]->operation; + $variables = $params[0]->variables; + } + + // Determine status code if available (WPGraphQL Router sets this) + if ( class_exists( '\WPGraphQL\Router' ) && property_exists( '\WPGraphQL\Router', '$http_status_code' ) ) { + $status_code = \WPGraphQL\Router::$http_status_code; + } + + // Extract data and errors from the ExecutionResult object(s) + $response_data = null; + $response_errors = null; + + if ( $response instanceof ExecutionResult ) { + $response_data = $response->data; + $response_errors = $response->errors; + } elseif ( is_array( $response ) && ! empty( $response[0] ) && $response[0] instanceof ExecutionResult ) { + // For batch requests, aggregate data/errors from all results + $response_data = array_map( static fn( $res ) => $res->data, $response ); + $response_errors = array_reduce( $response, static fn( $carry, $res ) => array_merge( $carry, $res->errors ?? [] ), [] ); + if ( empty( $response_errors ) ) { + $response_errors = null; // Ensure it's null if no errors + } + } + try { - $context = []; + $context = [ + 'query' => $query, + 'operation_name' => $operation_name, + 'variables' => $variables, + 'status_code' => $status_code, + 'response_data' => $response_data, + 'response_errors' => $response_errors, + ]; $level = Level::Info; - $context = apply_filters( 'wpgraphql_logging_post_request_context', $context, $response, $result, $operation_name, $query, $variables ); - $level = apply_filters( 'wpgraphql_logging_post_request_level', $level, $response, $result, $operation_name, $query, $variables ); + + // Apply filters for context and level + $context = apply_filters( 'wpgraphql_logging_post_request_context', $context, $response, $request_instance ); + $level = apply_filters( 'wpgraphql_logging_post_request_level', $level, $response, $request_instance ); + $this->logger->log( $level, 'WPGraphQL Outgoing Response', $context ); + + // Log errors specifically if present in the response + if ( ! empty( $response_errors ) ) { + $this->logger->error( + 'GraphQL query completed with errors.', + [ + 'query' => $query, + 'operation_name' => $operation_name, + 'status_code' => $status_code, + 'errors' => array_map( static fn( $error ) => $error->getMessage(), $response_errors ), // Extract message from error object + 'full_errors' => $response_errors, // Include full error details for debugging + ] + ); + } } catch ( \Throwable $e ) { // @TODO - Handle logging errors gracefully. - error_log( 'Error in log_post_request: ' . $e->getMessage() ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log + error_log( 'Error in log_post_request: ' . $e->getMessage() . ' in ' . $e->getFile() . ' on line ' . $e->getLine() ); } } @@ -92,9 +163,6 @@ public function log_post_request( $response, $result, string $operation_name, st * Register actions and filters. */ protected function setup(): void { - - // @TODO: Update POC and use pub/sub for query events. - /** * @psalm-suppress HookNotFound */ @@ -103,6 +171,6 @@ protected function setup(): void { /** * @psalm-suppress HookNotFound */ - add_action( 'graphql_process_http_request_response', [ $this, 'log_post_request' ], 10, 5 ); + add_action( 'graphql_after_execute', [ $this, 'log_post_request' ], 10, 2 ); } } diff --git a/plugins/wpgraphql-logging/src/Hooks/.gitkeep b/plugins/wpgraphql-logging/src/Hooks/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/plugins/wpgraphql-logging/src/Hooks/PluginHooks.php b/plugins/wpgraphql-logging/src/Hooks/PluginHooks.php deleted file mode 100644 index a6636f91..00000000 --- a/plugins/wpgraphql-logging/src/Hooks/PluginHooks.php +++ /dev/null @@ -1,57 +0,0 @@ -register(); - } - return self::$instance; - } - - /** - * Activation callback for the plugin. - */ - public static function activate_plugin(): void { - DatabaseEntity::create_table(); - } - - /** - * Deactivation callback for the plugin. - */ - public static function deactivate_plugin(): void { - // @TODO: Add configuration to determine if the table should be dropped on deactivation. - DatabaseEntity::drop_table(); - } - - /** - * Register actions and filters. - */ - protected function register(): void { - add_action( 'wpgraphql_logging_activate', [ self::class, 'activate_plugin' ], 10, 0 ); - add_action( 'wpgraphql_logging_deactivate', [ self::class, 'deactivate_plugin' ], 10, 0 ); - } -} diff --git a/plugins/wpgraphql-logging/src/Plugin.php b/plugins/wpgraphql-logging/src/Plugin.php index b825e08f..5517d8b9 100644 --- a/plugins/wpgraphql-logging/src/Plugin.php +++ b/plugins/wpgraphql-logging/src/Plugin.php @@ -5,7 +5,7 @@ namespace WPGraphQL\Logging; use WPGraphQL\Logging\Events\QueryEventLifecycle; -use WPGraphQL\Logging\Hooks\PluginHooks; +use WPGraphQL\Logging\Logger\Database\DatabaseEntity; /** * Plugin class for WPGraphQL Logging. @@ -51,7 +51,6 @@ public static function init(): self { * Initialize the plugin admin, frontend & api functionality. */ public function setup(): void { - PluginHooks::init(); QueryEventLifecycle::init(); } @@ -78,4 +77,19 @@ public function __wakeup(): void { // De-serializing instances of the class is forbidden. _doing_it_wrong( __METHOD__, 'De-serializing instances of the plugin Main class is not allowed.', '0.0.1' ); } + + /** + * Activation callback for the plugin. + */ + public static function activate(): void { + DatabaseEntity::create_table(); + } + + /** + * Deactivation callback for the plugin. + */ + public static function deactivate(): void { + // @TODO: Add configuration to determine if the table should be dropped on deactivation. + DatabaseEntity::drop_table(); + } } diff --git a/plugins/wpgraphql-logging/tests/wpunit/Hooks/PluginHooksTest.php b/plugins/wpgraphql-logging/tests/wpunit/Hooks/PluginHooksTest.php deleted file mode 100644 index af9cd4d4..00000000 --- a/plugins/wpgraphql-logging/tests/wpunit/Hooks/PluginHooksTest.php +++ /dev/null @@ -1,71 +0,0 @@ -drop_table(); - } - - public function drop_table(): void { - DatabaseEntity::drop_table(); - } - - public function test_instance_from_plugin_instance() { - $instance = PluginHooks::init(); - $this->assertTrue( $instance instanceof PluginHooks ); - } - - public function test_singleton_returns_same_instance() { - $first = PluginHooks::init(); - $second = PluginHooks::init(); - $this->assertSame( $first, $second, 'PluginHooks::instance() should always return the same instance' ); - } - - public function test_instance_creates_and_sets_up_plugin_when_not_set() { - $reflection = new ReflectionClass( PluginHooks::class ); - $instanceProperty = $reflection->getProperty( 'instance' ); - $instanceProperty->setAccessible( true ); - $instanceProperty->setValue( null ); - - $this->assertNull( $instanceProperty->getValue() ); - $instance = PluginHooks::init(); - - $this->assertInstanceOf( PluginHooks::class, $instanceProperty->getValue() ); - $this->assertSame( $instance, $instanceProperty->getValue(), 'Plugin::instance() should set the static instance property' ); - } - - - public function test_database_table_creation() { - global $wpdb; - - // Setup plugin and run activation hook - PluginHooks::init(); - wpgraphql_logging_activation_callback(); - - // Check if the table now exists - $table_exists = $wpdb->get_var( $wpdb->prepare( - "SHOW TABLES LIKE %s", - DatabaseEntity::get_table_name() - ) ); - - $this->assertEquals( DatabaseEntity::get_table_name(), $table_exists, 'Database table should be created by setup method.' ); - } -} diff --git a/plugins/wpgraphql-logging/wpgraphql-logging.php b/plugins/wpgraphql-logging/wpgraphql-logging.php index 7a1f8a6d..3e68b216 100644 --- a/plugins/wpgraphql-logging/wpgraphql-logging.php +++ b/plugins/wpgraphql-logging/wpgraphql-logging.php @@ -140,4 +140,6 @@ function wpgraphql_logging_load_textdomain(): void { add_action( 'init', 'wpgraphql_logging_load_textdomain', 1, 0 ); /** @psalm-suppress HookNotFound */ -wpgraphql_logging_init(); +add_action( 'plugins_loaded', function() : void { + wpgraphql_logging_init(); + }, 10, 0 ); From 327e39761c0766f98d343687ed303fde1a495ec7 Mon Sep 17 00:00:00 2001 From: Colin Murphy Date: Fri, 8 Aug 2025 15:35:53 +0100 Subject: [PATCH 2/2] Fixed formatting issues. Temporarily removed QueryLifecycle class from QA as this is a POC and not actually ready. --- plugins/wpgraphql-logging/deactivation.php | 5 ++-- plugins/wpgraphql-logging/phpcs.xml | 1 + plugins/wpgraphql-logging/phpstan.neon.dist | 2 ++ plugins/wpgraphql-logging/psalm.xml | 4 +++ .../src/Logger/LoggerService.php | 10 +++---- plugins/wpgraphql-logging/src/Plugin.php | 30 +++++++++---------- .../wpgraphql-logging/wpgraphql-logging.php | 4 +-- 7 files changed, 31 insertions(+), 25 deletions(-) diff --git a/plugins/wpgraphql-logging/deactivation.php b/plugins/wpgraphql-logging/deactivation.php index 02ceb8da..5f135ded 100644 --- a/plugins/wpgraphql-logging/deactivation.php +++ b/plugins/wpgraphql-logging/deactivation.php @@ -1,7 +1,4 @@ */phpunit.xml* **/tests/** */vendor/* + */src/Events/QueryEventLifecycle.php diff --git a/plugins/wpgraphql-logging/phpstan.neon.dist b/plugins/wpgraphql-logging/phpstan.neon.dist index 05f71e10..e6722105 100644 --- a/plugins/wpgraphql-logging/phpstan.neon.dist +++ b/plugins/wpgraphql-logging/phpstan.neon.dist @@ -30,3 +30,5 @@ parameters: paths: - wpgraphql-logging.php - src/ + excludePaths: + - src/Events/QueryEventLifecycle.php diff --git a/plugins/wpgraphql-logging/psalm.xml b/plugins/wpgraphql-logging/psalm.xml index f6a82936..552f0d26 100644 --- a/plugins/wpgraphql-logging/psalm.xml +++ b/plugins/wpgraphql-logging/psalm.xml @@ -15,8 +15,12 @@ + + + + diff --git a/plugins/wpgraphql-logging/src/Logger/LoggerService.php b/plugins/wpgraphql-logging/src/Logger/LoggerService.php index fb50ec7d..d55a7585 100644 --- a/plugins/wpgraphql-logging/src/Logger/LoggerService.php +++ b/plugins/wpgraphql-logging/src/Logger/LoggerService.php @@ -44,7 +44,7 @@ class LoggerService { /** * The instance of the logger based off the channel name. * - * @var array + * @var array<\WPGraphQL\Logging\Logger\LoggerService> */ protected static array $instances = []; @@ -90,16 +90,16 @@ public static function get_instance( ?array $processors = null, ?array $default_context = null ): LoggerService { - if ( isset(self::$instances[$channel]) ) { - return self::$instances[$channel]; + if ( isset( self::$instances[ $channel ] ) ) { + return self::$instances[ $channel ]; } $processors = $processors ?? self::get_default_processors(); $handlers = $handlers ?? self::get_default_handlers(); $default_context = $default_context ?? self::get_default_context(); - self::$instances[$channel] = new self( $channel, $handlers, $processors, $default_context ); - return self::$instances[$channel]; + self::$instances[ $channel ] = new self( $channel, $handlers, $processors, $default_context ); + return self::$instances[ $channel ]; } /** diff --git a/plugins/wpgraphql-logging/src/Plugin.php b/plugins/wpgraphql-logging/src/Plugin.php index 5517d8b9..502860b4 100644 --- a/plugins/wpgraphql-logging/src/Plugin.php +++ b/plugins/wpgraphql-logging/src/Plugin.php @@ -54,6 +54,21 @@ public function setup(): void { QueryEventLifecycle::init(); } + /** + * Activation callback for the plugin. + */ + public static function activate(): void { + DatabaseEntity::create_table(); + } + + /** + * Deactivation callback for the plugin. + */ + public static function deactivate(): void { + // @TODO: Add configuration to determine if the table should be dropped on deactivation. + DatabaseEntity::drop_table(); + } + /** * Throw error on object clone. * The whole idea of the singleton design pattern is that there is a single object @@ -77,19 +92,4 @@ public function __wakeup(): void { // De-serializing instances of the class is forbidden. _doing_it_wrong( __METHOD__, 'De-serializing instances of the plugin Main class is not allowed.', '0.0.1' ); } - - /** - * Activation callback for the plugin. - */ - public static function activate(): void { - DatabaseEntity::create_table(); - } - - /** - * Deactivation callback for the plugin. - */ - public static function deactivate(): void { - // @TODO: Add configuration to determine if the table should be dropped on deactivation. - DatabaseEntity::drop_table(); - } } diff --git a/plugins/wpgraphql-logging/wpgraphql-logging.php b/plugins/wpgraphql-logging/wpgraphql-logging.php index 3e68b216..5eeb07d1 100644 --- a/plugins/wpgraphql-logging/wpgraphql-logging.php +++ b/plugins/wpgraphql-logging/wpgraphql-logging.php @@ -140,6 +140,6 @@ function wpgraphql_logging_load_textdomain(): void { add_action( 'init', 'wpgraphql_logging_load_textdomain', 1, 0 ); /** @psalm-suppress HookNotFound */ -add_action( 'plugins_loaded', function() : void { +add_action( 'plugins_loaded', static function (): void { wpgraphql_logging_init(); - }, 10, 0 ); +}, 10, 0 );