Load controllers only when needed for performance. (#47704)
* Load controllers only when needed for performance. * Classify controllers based on their namespace and load selectively. * Enable private namespace along with store api. * Only prevent route loading when request is known for back compat. * Lint fixes. * Remove duplicate inclusion. * Correctly load feature controller. * Add since tag. * Add unit tests.
This commit is contained in:
parent
fcb7f6798f
commit
6d92aa9ccb
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: performance
|
||||
|
||||
Load REST API namespaces only when needed.
|
|
@ -28,7 +28,7 @@ class Server {
|
|||
/**
|
||||
* Hook into WordPress ready to init the REST API as needed.
|
||||
*/
|
||||
public function init() {
|
||||
public function init() { // phpcs:ignore WooCommerce.Functions.InternalInjectionMethod -- Not an injection method.
|
||||
add_action( 'rest_api_init', array( $this, 'register_rest_routes' ), 10 );
|
||||
|
||||
\WC_REST_System_Status_V2_Controller::register_cache_clean();
|
||||
|
@ -57,13 +57,19 @@ class Server {
|
|||
* @return array List of Namespaces and Main controller classes.
|
||||
*/
|
||||
protected function get_rest_namespaces() {
|
||||
/**
|
||||
* Filter the list of REST API controllers to load.
|
||||
*
|
||||
* @since 4.5.0
|
||||
* @param array $controllers List of $namespace => $controllers to load.
|
||||
*/
|
||||
return apply_filters(
|
||||
'woocommerce_rest_api_get_rest_namespaces',
|
||||
array(
|
||||
'wc/v1' => $this->get_v1_controllers(),
|
||||
'wc/v2' => $this->get_v2_controllers(),
|
||||
'wc/v3' => $this->get_v3_controllers(),
|
||||
'wc-telemetry' => $this->get_telemetry_controllers(),
|
||||
'wc/v1' => wc_rest_should_load_namespace( 'wc/v1' ) ? $this->get_v1_controllers() : array(),
|
||||
'wc/v2' => wc_rest_should_load_namespace( 'wc/v2' ) ? $this->get_v2_controllers() : array(),
|
||||
'wc/v3' => wc_rest_should_load_namespace( 'wc/v3' ) ? $this->get_v3_controllers() : array(),
|
||||
'wc-telemetry' => wc_rest_should_load_namespace( 'wc-telemetry' ) ? $this->get_telemetry_controllers() : array(),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -373,3 +373,56 @@ function wc_rest_check_product_reviews_permissions( $context = 'read', $object_i
|
|||
function wc_rest_is_from_product_editor() {
|
||||
return isset( $_SERVER['HTTP_X_WC_FROM_PRODUCT_EDITOR'] ) && '1' === $_SERVER['HTTP_X_WC_FROM_PRODUCT_EDITOR'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a REST namespace should be loaded. Useful to maintain site performance even when lots of REST namespaces are registered.
|
||||
*
|
||||
* @since 9.2.0.
|
||||
*
|
||||
* @param string $ns The namespace to check.
|
||||
* @param string $rest_route (Optional) The REST route being checked.
|
||||
*
|
||||
* @return bool True if the namespace should be loaded, false otherwise.
|
||||
*/
|
||||
function wc_rest_should_load_namespace( string $ns, string $rest_route = '' ): bool {
|
||||
if ( '' === $rest_route ) {
|
||||
$rest_route = $GLOBALS['wp']->query_vars['rest_route'] ?? '';
|
||||
}
|
||||
|
||||
if ( '' === $rest_route ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$rest_route = trailingslashit( ltrim( $rest_route, '/' ) );
|
||||
$ns = trailingslashit( $ns );
|
||||
|
||||
/**
|
||||
* Known namespaces that we know are safe to not load if the request is not for them. Namespaces not in this namespace should always be loaded, because we don't know if they won't be making another internal REST request to an unloaded namespace.
|
||||
*/
|
||||
$known_namespaces = array(
|
||||
'wc/v1',
|
||||
'wc/v2',
|
||||
'wc/v3',
|
||||
'wc-telemetry',
|
||||
'wc-admin',
|
||||
'wc-analytics',
|
||||
'wc/store',
|
||||
'wc/private',
|
||||
);
|
||||
|
||||
// We can consider allowing filtering this list in the future.
|
||||
|
||||
$known_namespace_request = false;
|
||||
foreach ( $known_namespaces as $known_namespace ) {
|
||||
if ( str_starts_with( $rest_route, $known_namespace ) ) {
|
||||
$known_namespace_request = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! $known_namespace_request ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return str_starts_with( $rest_route, $ns );
|
||||
}
|
||||
|
|
|
@ -57,83 +57,98 @@ class Init {
|
|||
* Init REST API.
|
||||
*/
|
||||
public function rest_api_init() {
|
||||
$controllers = array(
|
||||
'Automattic\WooCommerce\Admin\API\Features',
|
||||
'Automattic\WooCommerce\Admin\API\Notes',
|
||||
'Automattic\WooCommerce\Admin\API\NoteActions',
|
||||
'Automattic\WooCommerce\Admin\API\Coupons',
|
||||
'Automattic\WooCommerce\Admin\API\Data',
|
||||
'Automattic\WooCommerce\Admin\API\DataCountries',
|
||||
'Automattic\WooCommerce\Admin\API\DataDownloadIPs',
|
||||
'Automattic\WooCommerce\Admin\API\Experiments',
|
||||
'Automattic\WooCommerce\Admin\API\Marketing',
|
||||
'Automattic\WooCommerce\Admin\API\MarketingOverview',
|
||||
'Automattic\WooCommerce\Admin\API\MarketingRecommendations',
|
||||
'Automattic\WooCommerce\Admin\API\MarketingChannels',
|
||||
'Automattic\WooCommerce\Admin\API\MarketingCampaigns',
|
||||
'Automattic\WooCommerce\Admin\API\MarketingCampaignTypes',
|
||||
'Automattic\WooCommerce\Admin\API\Notice',
|
||||
'Automattic\WooCommerce\Admin\API\Options',
|
||||
'Automattic\WooCommerce\Admin\API\Orders',
|
||||
'Automattic\WooCommerce\Admin\API\PaymentGatewaySuggestions',
|
||||
'Automattic\WooCommerce\Admin\API\Products',
|
||||
'Automattic\WooCommerce\Admin\API\ProductAttributes',
|
||||
'Automattic\WooCommerce\Admin\API\ProductAttributeTerms',
|
||||
'Automattic\WooCommerce\Admin\API\ProductCategories',
|
||||
'Automattic\WooCommerce\Admin\API\ProductVariations',
|
||||
'Automattic\WooCommerce\Admin\API\ProductReviews',
|
||||
'Automattic\WooCommerce\Admin\API\ProductVariations',
|
||||
'Automattic\WooCommerce\Admin\API\ProductsLowInStock',
|
||||
'Automattic\WooCommerce\Admin\API\SettingOptions',
|
||||
'Automattic\WooCommerce\Admin\API\Themes',
|
||||
'Automattic\WooCommerce\Admin\API\Plugins',
|
||||
'Automattic\WooCommerce\Admin\API\OnboardingFreeExtensions',
|
||||
'Automattic\WooCommerce\Admin\API\OnboardingProductTypes',
|
||||
'Automattic\WooCommerce\Admin\API\OnboardingProfile',
|
||||
'Automattic\WooCommerce\Admin\API\OnboardingTasks',
|
||||
'Automattic\WooCommerce\Admin\API\OnboardingThemes',
|
||||
'Automattic\WooCommerce\Admin\API\OnboardingPlugins',
|
||||
'Automattic\WooCommerce\Admin\API\OnboardingProducts',
|
||||
'Automattic\WooCommerce\Admin\API\NavigationFavorites',
|
||||
'Automattic\WooCommerce\Admin\API\Taxes',
|
||||
'Automattic\WooCommerce\Admin\API\MobileAppMagicLink',
|
||||
'Automattic\WooCommerce\Admin\API\ShippingPartnerSuggestions',
|
||||
);
|
||||
$controllers = array();
|
||||
$analytics_controllers = array();
|
||||
|
||||
if ( wc_rest_should_load_namespace( 'wc-admin' ) ) {
|
||||
// Controllers in the wc-admin namespace.
|
||||
$controllers = array(
|
||||
'Automattic\WooCommerce\Admin\API\Notice',
|
||||
'Automattic\WooCommerce\Admin\API\Features',
|
||||
'Automattic\WooCommerce\Admin\API\Experiments',
|
||||
'Automattic\WooCommerce\Admin\API\Marketing',
|
||||
'Automattic\WooCommerce\Admin\API\MarketingOverview',
|
||||
'Automattic\WooCommerce\Admin\API\MarketingRecommendations',
|
||||
'Automattic\WooCommerce\Admin\API\MarketingChannels',
|
||||
'Automattic\WooCommerce\Admin\API\MarketingCampaigns',
|
||||
'Automattic\WooCommerce\Admin\API\MarketingCampaignTypes',
|
||||
'Automattic\WooCommerce\Admin\API\Options',
|
||||
'Automattic\WooCommerce\Admin\API\PaymentGatewaySuggestions',
|
||||
'Automattic\WooCommerce\Admin\API\Themes',
|
||||
'Automattic\WooCommerce\Admin\API\Plugins',
|
||||
'Automattic\WooCommerce\Admin\API\OnboardingFreeExtensions',
|
||||
'Automattic\WooCommerce\Admin\API\OnboardingProductTypes',
|
||||
'Automattic\WooCommerce\Admin\API\OnboardingProfile',
|
||||
'Automattic\WooCommerce\Admin\API\OnboardingTasks',
|
||||
'Automattic\WooCommerce\Admin\API\OnboardingThemes',
|
||||
'Automattic\WooCommerce\Admin\API\OnboardingPlugins',
|
||||
'Automattic\WooCommerce\Admin\API\OnboardingProducts',
|
||||
'Automattic\WooCommerce\Admin\API\NavigationFavorites',
|
||||
'Automattic\WooCommerce\Admin\API\MobileAppMagicLink',
|
||||
'Automattic\WooCommerce\Admin\API\ShippingPartnerSuggestions',
|
||||
);
|
||||
}
|
||||
|
||||
if ( Features::is_enabled( 'launch-your-store' ) ) {
|
||||
$controllers[] = 'Automattic\WooCommerce\Admin\API\LaunchYourStore';
|
||||
}
|
||||
|
||||
if ( Features::is_enabled( 'analytics' ) ) {
|
||||
$analytics_controllers = array(
|
||||
'Automattic\WooCommerce\Admin\API\Customers',
|
||||
'Automattic\WooCommerce\Admin\API\Leaderboards',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Import\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Export\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Products\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Variations\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Products\Stats\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Variations\Stats\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Revenue\Stats\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Orders\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Orders\Stats\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Categories\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Taxes\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Taxes\Stats\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Coupons\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Coupons\Stats\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Stock\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Stock\Stats\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Downloads\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Downloads\Stats\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Customers\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Customers\Stats\Controller',
|
||||
if ( wc_rest_should_load_namespace( 'wc-analytics' ) ) {
|
||||
// Controllers in wc-analytics namespace, but loaded irrespective of analytics feature value.
|
||||
$analytic_mu_controllers = array(
|
||||
'Automattic\WooCommerce\Admin\API\Notes',
|
||||
'Automattic\WooCommerce\Admin\API\NoteActions',
|
||||
'Automattic\WooCommerce\Admin\API\Coupons',
|
||||
'Automattic\WooCommerce\Admin\API\Data',
|
||||
'Automattic\WooCommerce\Admin\API\DataCountries',
|
||||
'Automattic\WooCommerce\Admin\API\DataDownloadIPs',
|
||||
'Automattic\WooCommerce\Admin\API\Orders',
|
||||
'Automattic\WooCommerce\Admin\API\Products',
|
||||
'Automattic\WooCommerce\Admin\API\ProductAttributes',
|
||||
'Automattic\WooCommerce\Admin\API\ProductAttributeTerms',
|
||||
'Automattic\WooCommerce\Admin\API\ProductCategories',
|
||||
'Automattic\WooCommerce\Admin\API\ProductVariations',
|
||||
'Automattic\WooCommerce\Admin\API\ProductReviews',
|
||||
'Automattic\WooCommerce\Admin\API\ProductVariations',
|
||||
'Automattic\WooCommerce\Admin\API\ProductsLowInStock',
|
||||
'Automattic\WooCommerce\Admin\API\SettingOptions',
|
||||
'Automattic\WooCommerce\Admin\API\Taxes',
|
||||
);
|
||||
|
||||
// The performance indicators controller must be registered last, after other /stats endpoints have been registered.
|
||||
$analytics_controllers[] = 'Automattic\WooCommerce\Admin\API\Reports\PerformanceIndicators\Controller';
|
||||
$controllers = array_merge( $controllers, $analytics_controllers );
|
||||
if ( Features::is_enabled( 'analytics' ) ) {
|
||||
$analytics_controllers = array(
|
||||
'Automattic\WooCommerce\Admin\API\Customers',
|
||||
'Automattic\WooCommerce\Admin\API\Leaderboards',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Import\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Export\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Products\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Variations\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Products\Stats\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Variations\Stats\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Revenue\Stats\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Orders\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Orders\Stats\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Categories\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Taxes\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Taxes\Stats\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Coupons\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Coupons\Stats\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Stock\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Stock\Stats\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Downloads\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Downloads\Stats\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Customers\Controller',
|
||||
'Automattic\WooCommerce\Admin\API\Reports\Customers\Stats\Controller',
|
||||
);
|
||||
|
||||
// The performance indicators controllerq must be registered last, after other /stats endpoints have been registered.
|
||||
$analytics_controllers[] = 'Automattic\WooCommerce\Admin\API\Reports\PerformanceIndicators\Controller';
|
||||
|
||||
$analytics_controllers = array_merge( $analytics_controllers, $analytic_mu_controllers );
|
||||
}
|
||||
|
||||
$controllers = array_merge( $controllers, $analytics_controllers, $analytic_mu_controllers );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -23,6 +23,9 @@ final class StoreApi {
|
|||
add_action(
|
||||
'rest_api_init',
|
||||
function () {
|
||||
if ( ! wc_rest_should_load_namespace( 'wc/store' ) && ! wc_rest_should_load_namespace( 'wc/private' ) ) {
|
||||
return;
|
||||
}
|
||||
self::container()->get( Legacy::class )->init();
|
||||
self::container()->get( RoutesController::class )->register_all_routes();
|
||||
}
|
||||
|
@ -31,6 +34,9 @@ final class StoreApi {
|
|||
add_action(
|
||||
'rest_api_init',
|
||||
function () {
|
||||
if ( ! wc_rest_should_load_namespace( 'wc/store' ) ) {
|
||||
return;
|
||||
}
|
||||
self::container()->get( Authentication::class )->init();
|
||||
},
|
||||
11
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
declare( strict_types = 1);
|
||||
|
||||
// phpcs:disable Squiz.Classes.ClassFileName.NoMatch -- backcompat nomenclature.
|
||||
|
||||
/**
|
||||
* Tests for wc-rest-functions.php.
|
||||
* Class WC_Rest_Functions_Test.
|
||||
*/
|
||||
class WCRestFunctionsTest extends WC_Unit_Test_Case {
|
||||
|
||||
/**
|
||||
* @testDox All namespaces are loaded for unknown path.
|
||||
*/
|
||||
public function test_wc_rest_should_load_namespace_unknown() {
|
||||
$this->assertTrue( wc_rest_should_load_namespace( 'wc/v1', 'wc/unknown' ) );
|
||||
$this->assertTrue( wc_rest_should_load_namespace( 'wc-analytics', 'wc/unknown' ) );
|
||||
$this->assertTrue( wc_rest_should_load_namespace( 'wc-telemetry', 'wc/unknown' ) );
|
||||
$this->assertTrue( wc_rest_should_load_namespace( 'wc-random', 'wc/unknown' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testDox Only required namespace is loaded for known path.
|
||||
*/
|
||||
public function test_wc_rest_should_load_namespace_known() {
|
||||
$this->assertFalse( wc_rest_should_load_namespace( 'wc/v1', 'wc/v2' ) );
|
||||
$this->assertFalse( wc_rest_should_load_namespace( 'wc-analytics', 'wc/v2' ) );
|
||||
$this->assertTrue( wc_rest_should_load_namespace( 'wc/v2', 'wc/v2' ) );
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue