|
| 1 | +--- |
| 2 | +title: "feat: lazy loading fields for Object Types and Interface Types" |
| 3 | +pr: 3356 |
| 4 | +author: "jasonbahl" |
| 5 | +type: "feat" |
| 6 | +breaking: false |
| 7 | +--- |
| 8 | + |
| 9 | +## What does this implement/fix? Explain your changes. |
| 10 | + |
| 11 | +This PR introduces lazy evaluation of GraphQL field definitions through callable functions in the core type system. The key changes focus on: |
| 12 | + |
| 13 | +1. Added support in \`WPInterfaceTrait\` to handle callable field definitions: |
| 14 | +\`\`\`php |
| 15 | +if ( is_callable( $config['fields'] ) ) { |
| 16 | + $config['fields'] = $config['fields'](); |
| 17 | +} |
| 18 | +\`\`\` |
| 19 | + |
| 20 | +2. Modified \`WPInterfaceType\` and \`WPObjectType\` to use callable functions for field definitions: |
| 21 | +\`\`\`php |
| 22 | +$config['fields'] = function () use ( $config ) { |
| 23 | + return ! empty( $this->fields ) ? $this->fields : $this->get_fields( $config, $this->type_registry ); |
| 24 | +}; |
| 25 | +\`\`\` |
| 26 | + |
| 27 | +This architectural change defers the execution of field definitions until they are actually needed, rather than defining all fields upfront during schema initialization. |
| 28 | + |
| 29 | +### Before/After Example |
| 30 | + |
| 31 | +Here's an example of how the \`User\` type fields were refactored: |
| 32 | + |
| 33 | +**Before:** |
| 34 | +\`\`\`php |
| 35 | +'fields' => [ |
| 36 | + 'id' => [ |
| 37 | + 'description' => __( 'The globally unique identifier for the user object.', 'wp-graphql' ), |
| 38 | + ], |
| 39 | + 'name' => [ |
| 40 | + 'type' => 'String', |
| 41 | + 'description' => __( 'Display name of the user.', 'wp-graphql' ), |
| 42 | + ], |
| 43 | + // ... more fields |
| 44 | +] |
| 45 | +\`\`\` |
| 46 | + |
| 47 | +**After:** |
| 48 | +\`\`\`php |
| 49 | +'fields' => static function () { |
| 50 | + return [ |
| 51 | + 'id' => [ |
| 52 | + 'description' => __( 'The globally unique identifier for the user object.', 'wp-graphql' ), |
| 53 | + ], |
| 54 | + 'name' => [ |
| 55 | + 'type' => 'String', |
| 56 | + 'description' => __( 'Display name of the user.', 'wp-graphql' ), |
| 57 | + ], |
| 58 | + // ... more fields |
| 59 | + ]; |
| 60 | +} |
| 61 | +\`\`\` |
| 62 | + |
| 63 | +To measure the impact of this change, we added a \`TestGetText\` class that tracks all \`__()\` function calls during schema generation and query execution. This logging shows that translations are now only executed when fields are actually queried, rather than during schema initialization. |
| 64 | + |
| 65 | +\`\`\`php |
| 66 | +class TestGetText { |
| 67 | + |
| 68 | + /** |
| 69 | + * @var array |
| 70 | + */ |
| 71 | + protected $gettext_log = []; |
| 72 | + |
| 73 | + /** |
| 74 | + * Constructor |
| 75 | + */ |
| 76 | + public function __construct() { |
| 77 | + // Reset at the very start of schema generation |
| 78 | + add_action( 'graphql_process_http_request', [ $this, 'init_log' ], 1 ); |
| 79 | + |
| 80 | + // Track all translations |
| 81 | + add_filter( |
| 82 | + 'gettext', |
| 83 | + function ( $text ) { |
| 84 | + $this->gettext_log[] = $text; |
| 85 | + return $text; |
| 86 | + }, |
| 87 | + 10, |
| 88 | + 3 |
| 89 | + ); |
| 90 | + |
| 91 | + // Add to extensions |
| 92 | + add_filter( 'graphql_request_results', [ $this, 'add_to_extensions' ], 10, 1 ); |
| 93 | + } |
| 94 | + |
| 95 | + /** |
| 96 | + * Initialize the log |
| 97 | + */ |
| 98 | + public function init_log(): void { |
| 99 | + $this->gettext_log = []; |
| 100 | + } |
| 101 | + |
| 102 | + /** |
| 103 | + * Add translation data to GraphQL extensions |
| 104 | + * |
| 105 | + * @param \GraphQL\Executor\ExecutionResult $response The GraphQL response |
| 106 | + */ |
| 107 | + public function add_to_extensions( \GraphQL\Executor\ExecutionResult $response ): \GraphQL\Executor\ExecutionResult { |
| 108 | + $extensions = $response->extensions ?? []; |
| 109 | + $extensions['translations'] = [ |
| 110 | + 'count' => count( $this->gettext_log ), |
| 111 | + // 'log' => $this->gettext_log, |
| 112 | + ]; |
| 113 | + $response->extensions = $extensions; |
| 114 | + return $response; |
| 115 | + } |
| 116 | +} |
| 117 | + |
| 118 | +new TestGetText(); |
| 119 | +\`\`\` |
| 120 | + |
| 121 | +## Does this close any currently open issues? |
| 122 | + |
| 123 | +Working on #3354 (more to come) |
| 124 | + |
| 125 | +## Any other comments? |
| 126 | + |
| 127 | +This change provides significant performance benefits by: |
| 128 | + |
| 129 | +1. **Deferred Translation**: The \`__()\` function calls for field descriptions are now only executed when the fields are actually used in a query, rather than during schema initialization. This is particularly important for multilingual sites where translation lookups can be expensive. |
| 130 | + |
| 131 | +2. **Reduced Memory Usage**: Field definitions that are rarely used in queries no longer need to be stored in memory during the entire request lifecycle. |
| 132 | + |
| 133 | +3. **Faster Schema Initialization**: The schema can be initialized more quickly since field definitions are not processed until they are actually needed. |
| 134 | + |
| 135 | +4. **Better Resource Management**: Resources like database connections or complex calculations needed for field definitions are only allocated when the fields are actually queried. |
| 136 | + |
| 137 | +The changes maintain full backward compatibility while providing these performance improvements. All existing functionality remains the same, but the implementation is now more efficient, especially for large WordPress installations with complex schemas. |
0 commit comments