• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

wp-graphql / wp-graphql / 18790791685

24 Oct 2025 08:03PM UTC coverage: 83.207% (-1.4%) from 84.575%
18790791685

push

github

actions-user
release: merge develop into master for v2.5.0

2 of 4 new or added lines in 2 files covered. (50.0%)

189 existing lines in 10 files now uncovered.

16143 of 19401 relevant lines covered (83.21%)

257.79 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

69.23
/src/Experimental/ExperimentRegistry.php
1
<?php
2
/**
3
 * Registers and manages our experimental features.
4
 *
5
 * @package WPGraphQL\Experimental
6
 * @since 2.3.8
7
 */
8

9
namespace WPGraphQL\Experimental;
10

11
use WPGraphQL\Experimental\Experiment\AbstractExperiment;
12
use WPGraphQL\Experimental\Experiment\TestDependantExperiment\TestDependantExperiment;
13
use WPGraphQL\Experimental\Experiment\TestExperiment\TestExperiment;
14
use WPGraphQL\Experimental\Experiment\TestOptionalDependencyExperiment\TestOptionalDependencyExperiment;
15

16
/**
17
 * Class - ExperimentRegistry
18
 */
19
final class ExperimentRegistry {
20
        /**
21
         * The registered experiments.
22
         *
23
         * @var ?array<string,class-string<\WPGraphQL\Experimental\Experiment\AbstractExperiment>>
24
         */
25
        protected static $registry;
26

27
        /**
28
         * The initialized experiments.
29
         *
30
         * @var ?array<string,\WPGraphQL\Experimental\Experiment\AbstractExperiment>
31
         */
32
        protected static $experiments;
33

34
        /**
35
         * The active experiments.
36
         *
37
         * @var ?array<string,\WPGraphQL\Experimental\Experiment\AbstractExperiment>
38
         */
39
        protected static $active_experiments;
40

41

42
        /**
43
         * Initializes the Experimental Functionality for WPGraphQL
44
         */
45
        public function init(): void {
27✔
46
                $this->register_experiments();
27✔
47
                $this->load_experiments();
27✔
48
        }
49

50
        /**
51
         * Whether the current user can manage experimental features.
52
         *
53
         * @uses 'graphql_experimental_features_cap' filter to determine the capability required.
54
         */
55
        public static function can_manage_experiments(): bool {
×
56
                return current_user_can( self::capability() );
×
57
        }
58

59
        /**
60
         * The capability required to turn experimental features on and off.
61
         *
62
         * Defaults to `manage_options`.
63
         */
64
        public static function capability(): string {
×
65
                /**
66
                 * Filters the capability required to turn experimental features on and off.
67
                 *
68
                 * @param string $capability The capability required to turn experimental features on and off. Defaults to `manage_options`.
69
                 */
70
                return apply_filters( 'graphql_experimental_features_cap', 'manage_options' );
×
71
        }
72

73
        /**
74
         * Checks whether the experiment is enabled.
75
         *
76
         * @return array<string,class-string<\WPGraphQL\Experimental\Experiment\AbstractExperiment>>
77
         */
78
        public static function get_experiment_registry(): array {
27✔
79
                if ( ! isset( self::$registry ) ) {
27✔
80
                        _doing_it_wrong(
×
81
                                __METHOD__,
×
82
                                esc_html__( 'Registered experiments have not been set. Make sure not to call this function before the `graphql_experiments_registered` hook.', 'wp-graphql' ),
×
NEW
83
                                '@since 2.3.8'
×
84
                        );
×
85

86
                        return [];
×
87
                }
88

89
                return self::$registry;
27✔
90
        }
91

92
        /**
93
         * Gets the list of all experiments.
94
         *
95
         * @return array<string,\WPGraphQL\Experimental\Experiment\AbstractExperiment>
96
         */
97
        public static function get_experiments(): array {
13✔
98
                if ( ! isset( self::$experiments ) ) {
13✔
99
                        _doing_it_wrong(
×
100
                                __METHOD__,
×
101
                                esc_html__( 'Experiments have not been loaded. Make sure not to call this function before the `graphql_experiments_loaded` hook.', 'wp-graphql' ),
×
NEW
102
                                '@since 2.3.8'
×
103
                        );
×
104

105
                        return [];
×
106
                }
107

108
                return self::$experiments;
13✔
109
        }
110

111
        /**
112
         * Get the list of active experiments.
113
         *
114
         * @return array<string,\WPGraphQL\Experimental\Experiment\AbstractExperiment>
115
         */
116
        public static function get_active_experiments(): array {
783✔
117
                if ( ! isset( self::$active_experiments ) ) {
783✔
118
                        _doing_it_wrong(
772✔
119
                                __METHOD__,
772✔
120
                                esc_html__( 'Active experiments have not been loaded. Make sure not to call this function before the `graphql_experiments_loaded` hook.', 'wp-graphql' ),
772✔
121
                                '@since 2.3.8'
772✔
122
                        );
772✔
123

124
                        return [];
772✔
125
                }
126

127
                return self::$active_experiments;
11✔
128
        }
129

130
        /**
131
         * Get all registered experiment classes (before instantiation).
132
         *
133
         * @return array<string,class-string<\WPGraphQL\Experimental\Experiment\AbstractExperiment>>
134
         */
135
        public static function get_registered_experiments(): array {
×
136
                return self::$registry ?? [];
×
137
        }
138

139

140
        /**
141
         * Returns whether the experiment is active.
142
         *
143
         * @param string $experiment_name The name of the experiment.
144
         */
145
        public static function is_experiment_active( string $experiment_name ): bool {
7✔
146
                $active_experiments = self::get_active_experiments();
7✔
147

148
                return isset( $active_experiments[ $experiment_name ] );
7✔
149
        }
150

151
        /**
152
         * Registers the experiments.
153
         *
154
         * This is where core experiments are registered. Third-party experiments
155
         * can be registered using the 'graphql_experiments_registered_classes' filter.
156
         */
157
        protected function register_experiments(): void {
27✔
158
                $registry = [
27✔
159
                        // TestExperiment: A simple example that adds a testExperiment field to RootQuery.
160
                        // This serves as both a working example for developers and validates the Experiments API.
161
                        'test_experiment'                     => TestExperiment::class,
27✔
162

163
                        // TestDependantExperiment: Demonstrates required experiment dependencies.
164
                        // This experiment requires TestExperiment and shows how required dependencies work.
165
                        'test-dependant-experiment'           => TestDependantExperiment::class,
27✔
166

167
                        // TestOptionalDependencyExperiment: Demonstrates optional experiment dependencies.
168
                        // This experiment works independently but provides enhanced functionality when TestExperiment is active.
169
                        'test-optional-dependency-experiment' => TestOptionalDependencyExperiment::class,
27✔
170
                ];
27✔
171

172
                /**
173
                 * Filters the list of registered experiment classes.
174
                 *
175
                 * Use this filter to register custom experiments:
176
                 *
177
                 * ```php
178
                 * add_filter( 'graphql_experiments_registered_classes', function( $registry ) {
179
                 *     $registry['my-experiment'] = MyExperiment::class;
180
                 *     return $registry;
181
                 * } );
182
                 * ```
183
                 *
184
                 * @param array<string,class-string<\WPGraphQL\Experimental\Experiment\AbstractExperiment>> $registry The list of registered experiment classes, keyed by experiment slug.
185
                 */
186
                self::$registry = apply_filters( 'graphql_experiments_registered_classes', $registry );
27✔
187

188
                /**
189
                 * Fires after the experiment classes have been registered.
190
                 *
191
                 * @param array<string,class-string<\WPGraphQL\Experimental\Experiment\AbstractExperiment>> $registry The list of registered experiment classes, keyed by experiment slug.
192
                 */
193
                do_action( 'graphql_experiments_registered', self::$registry );
27✔
194
        }
195

196
        /**
197
         * Initializes the experiments.
198
         */
199
        protected function load_experiments(): void {
27✔
200
                // Bail if experiments have already been initialized.
201
                if ( isset( self::$experiments ) && isset( self::$active_experiments ) ) {
27✔
202
                        return;
×
203
                }
204

205
                // Initialize the experiments.
206
                self::$experiments        = [];
27✔
207
                self::$active_experiments = [];
27✔
208

209
                $experiment_classes = self::get_experiment_registry();
27✔
210

211
                foreach ( $experiment_classes as $slug => $class_name ) {
27✔
212
                        $this->load_experiment( $slug, $class_name );
27✔
213
                }
214

215
                /**
216
                 * Fires after all the experiments have been loaded.
217
                 *
218
                 * @param array<string,\WPGraphQL\Experimental\Experiment\AbstractExperiment>|null          $experiments The list of loaded experiment classes, keyed by experiment slug.
219
                 * @param array<string,class-string<\WPGraphQL\Experimental\Experiment\AbstractExperiment>> $registry The list of registered experiment classes, keyed by experiment slug.
220
                 */
221
                do_action( 'graphql_experiments_loaded', self::$experiments, self::get_experiment_registry() );
27✔
222
        }
223

224
        /**
225
         * Load a single experiment.
226
         *
227
         * @param string $slug       The experiment slug.
228
         * @param string $class_name The fully-qualified class name for the experiment.
229
         */
230
        protected function load_experiment( string $slug, string $class_name ): void {
27✔
231
                $experiment = new $class_name();
27✔
232

233
                if ( ! $experiment instanceof AbstractExperiment ) {
27✔
234
                        _doing_it_wrong(
×
235
                                __METHOD__,
×
236
                                sprintf(
×
237
                                        // translators: %s is the fully-qualified class name for AbstractExperiment.
238
                                        esc_html__( 'Experiment classes must extend the %s class.', 'wp-graphql' ),
×
239
                                        esc_html( AbstractExperiment::class )
×
240
                                ),
×
241
                                '0.0.1'
×
242
                        );
×
243
                        return;
×
244
                }
245

246
                self::$experiments[ $slug ] = $experiment;
27✔
247

248
                // Check if experiment can be loaded based on dependencies
249
                if ( $experiment->is_active() && $this->can_load_experiment( $experiment ) ) {
27✔
250
                        $experiment->load();
13✔
251

252
                        self::$active_experiments[ $slug ] = $experiment;
13✔
253
                }
254
        }
255

256
        /**
257
         * Check if an experiment can be loaded based on its dependencies.
258
         *
259
         * @param \WPGraphQL\Experimental\Experiment\AbstractExperiment $experiment The experiment to check.
260
         * @return bool True if the experiment can be loaded, false otherwise.
261
         */
262
        protected function can_load_experiment( AbstractExperiment $experiment ): bool {
14✔
263
                $dependencies  = $experiment->get_dependencies();
14✔
264
                $required_deps = $dependencies['required'] ?? [];
14✔
265

266
                // Check if all required dependencies are active
267
                foreach ( $required_deps as $dep_slug ) {
14✔
268
                        $is_dep_active = self::is_experiment_active( $dep_slug );
5✔
269

270
                        if ( ! $is_dep_active ) {
5✔
271
                                return false;
2✔
272
                        }
273
                }
274

275
                return true;
13✔
276
        }
277

278
        /**
279
         * Reload experiments (useful for testing).
280
         */
281
        public function reload_experiments(): void {
14✔
282
                // Clear the cached active state for all experiments
283
                if ( isset( self::$experiments ) ) {
14✔
284
                        foreach ( self::$experiments as $experiment ) {
14✔
285
                                // Clear the cached is_active value using the public method
286
                                $experiment->clear_active_cache();
14✔
287
                        }
288
                }
289

290
                // Set both arrays to null to force reload
291
                self::$active_experiments = null;
14✔
292
                self::$experiments        = null;
14✔
293

294
                // Reload experiments
295
                $this->load_experiments();
14✔
296
        }
297

298
        /**
299
         * Reset the experiment registry (useful for testing).
300
         *
301
         * This clears all static properties to ensure a clean slate between tests.
302
         */
303
        public static function reset(): void {
28✔
304
                self::$registry           = null;
28✔
305
                self::$experiments        = null;
28✔
306
                self::$active_experiments = null;
28✔
307
        }
308
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc