Commit c20d406e103 for woocommerce
commit c20d406e10389693e9375a2aa916fe4e91bc8cd7
Author: Taha Paksu <3295+tpaksu@users.noreply.github.com>
Date: Fri Apr 10 19:31:10 2026 +0300
Consolidate get_shipping_providers to always return AbstractShippingProvider instances (#64095)
* Consolidate get_shipping_providers to always return AbstractShippingProvider instances
* Add changefile(s) from automation for the following project(s): woocommerce
* Fix PHPStan baseline and address CodeRabbit review feedback
Remove stale baseline entry for is_array() in FulfillmentUtils.php,
guard nullable tracking URLs with ?? '', and add assertNotEmpty to
shipping providers test.
---------
Co-authored-by: woocommercebot <woocommercebot@users.noreply.github.com>
diff --git a/plugins/woocommerce/changelog/64095-wooplug-6533-consolidate-get_shipping_providers-and b/plugins/woocommerce/changelog/64095-wooplug-6533-consolidate-get_shipping_providers-and
new file mode 100644
index 00000000000..c4936a0d80b
--- /dev/null
+++ b/plugins/woocommerce/changelog/64095-wooplug-6533-consolidate-get_shipping_providers-and
@@ -0,0 +1,4 @@
+Significance: patch
+Type: dev
+
+Consolidate get_shipping_providers and get_shipping_providers_object into a single method that always returns AbstractShippingProvider instances.
\ No newline at end of file
diff --git a/plugins/woocommerce/includes/class-wc-ajax.php b/plugins/woocommerce/includes/class-wc-ajax.php
index 2df165ee07c..f605d3d65f2 100644
--- a/plugins/woocommerce/includes/class-wc-ajax.php
+++ b/plugins/woocommerce/includes/class-wc-ajax.php
@@ -3872,13 +3872,8 @@ class WC_AJAX {
$all_providers = \Automattic\WooCommerce\Admin\Features\Fulfillments\FulfillmentUtils::get_shipping_providers();
$built_in_keys = array();
foreach ( $all_providers as $provider ) {
- if ( is_string( $provider ) && class_exists( $provider ) && is_subclass_of( $provider, \Automattic\WooCommerce\Admin\Features\Fulfillments\Providers\AbstractShippingProvider::class ) ) {
- try {
- $instance = wc_get_container()->get( $provider );
- $built_in_keys[] = $instance->get_key();
- } catch ( \Throwable $e ) {
- continue;
- }
+ if ( ! $provider instanceof \Automattic\WooCommerce\Admin\Features\Fulfillments\Providers\CustomShippingProvider ) {
+ $built_in_keys[] = $provider->get_key();
}
}
$reserved_slug_error = '';
diff --git a/plugins/woocommerce/phpstan-baseline.neon b/plugins/woocommerce/phpstan-baseline.neon
index a7314dd9b87..7c252a05ebf 100644
--- a/plugins/woocommerce/phpstan-baseline.neon
+++ b/plugins/woocommerce/phpstan-baseline.neon
@@ -46587,12 +46587,6 @@ parameters:
count: 1
path: src/Admin/Features/Fulfillments/FulfillmentUtils.php
- -
- message: '#^Call to function is_array\(\) with array will always evaluate to true\.$#'
- identifier: function.alreadyNarrowedType
- count: 1
- path: src/Admin/Features/Fulfillments/FulfillmentUtils.php
-
-
message: '#^Cannot access offset int on array\|false\.$#'
identifier: offsetAccess.nonOffsetAccessible
diff --git a/plugins/woocommerce/src/Admin/Features/Fulfillments/FulfillmentUtils.php b/plugins/woocommerce/src/Admin/Features/Fulfillments/FulfillmentUtils.php
index a88afb2225b..bc034724254 100644
--- a/plugins/woocommerce/src/Admin/Features/Fulfillments/FulfillmentUtils.php
+++ b/plugins/woocommerce/src/Admin/Features/Fulfillments/FulfillmentUtils.php
@@ -409,7 +409,10 @@ class FulfillmentUtils {
* This method retrieves the shipping providers registered in the WooCommerce Fulfillments system.
* It can be filtered using the `woocommerce_fulfillment_shipping_providers` filter.
*
- * @return array An associative array of shipping providers with their details.
+ * Any class name strings in the filter result are resolved into AbstractShippingProvider instances
+ * via the DI container. Invalid entries are silently skipped.
+ *
+ * @return AbstractShippingProvider[] An associative array of shipping provider instances keyed by provider key.
*/
public static function get_shipping_providers(): array {
/**
@@ -421,52 +424,33 @@ class FulfillmentUtils {
*
* @param array $shipping_providers The default list of shipping providers.
*/
- return apply_filters(
+ $raw_providers = apply_filters(
'woocommerce_fulfillment_shipping_providers',
array()
);
- }
- /**
- * Get the shipping providers as an array of JS objects, for use in the fulfillment UI.
- *
- * @return array An associative array of shipping providers with their details.
- */
- public static function get_shipping_providers_object(): array {
- $shipping_providers = self::get_shipping_providers();
- if ( ! is_array( $shipping_providers ) ) {
+ if ( ! is_array( $raw_providers ) ) {
return array();
}
- $shipping_providers_object = array();
- foreach ( $shipping_providers as $shipping_provider ) {
- if ( is_string( $shipping_provider )
- && class_exists( $shipping_provider )
- && is_subclass_of( $shipping_provider, AbstractShippingProvider::class )
+
+ $resolved = array();
+ foreach ( $raw_providers as $provider ) {
+ if ( $provider instanceof AbstractShippingProvider ) {
+ $resolved[ $provider->get_key() ] = $provider;
+ } elseif ( is_string( $provider )
+ && class_exists( $provider )
+ && is_subclass_of( $provider, AbstractShippingProvider::class )
) {
try {
- // Instantiate the shipping provider class.
- $shipping_provider_instance = wc_get_container()->get( $shipping_provider );
+ $instance = wc_get_container()->get( $provider );
} catch ( \Throwable $e ) {
- continue; // Skip if instantiation fails.
+ continue;
}
- $shipping_providers_object[ $shipping_provider_instance->get_key() ] = array(
- 'label' => $shipping_provider_instance->get_name(),
- 'icon' => $shipping_provider_instance->get_icon(),
- 'value' => $shipping_provider_instance->get_key(),
- 'url' => $shipping_provider_instance->get_tracking_url( '__PLACEHOLDER__' ),
- );
- }
- if ( is_object( $shipping_provider ) && $shipping_provider instanceof AbstractShippingProvider ) {
- $shipping_providers_object[ $shipping_provider->get_key() ] = array(
- 'label' => $shipping_provider->get_name(),
- 'icon' => $shipping_provider->get_icon(),
- 'value' => $shipping_provider->get_key(),
- 'url' => $shipping_provider->get_tracking_url( '__PLACEHOLDER__' ),
- );
+ $resolved[ $instance->get_key() ] = $instance;
}
}
- return $shipping_providers_object;
+ return $resolved;
}
/**
diff --git a/plugins/woocommerce/src/Admin/Features/Fulfillments/FulfillmentsManager.php b/plugins/woocommerce/src/Admin/Features/Fulfillments/FulfillmentsManager.php
index ee3caa3dfce..e93218c38b8 100644
--- a/plugins/woocommerce/src/Admin/Features/Fulfillments/FulfillmentsManager.php
+++ b/plugins/woocommerce/src/Admin/Features/Fulfillments/FulfillmentsManager.php
@@ -507,33 +507,9 @@ class FulfillmentsManager {
$shipping_providers = FulfillmentUtils::get_shipping_providers();
$results = array();
foreach ( $shipping_providers as $provider ) {
- if ( is_string( $provider ) && class_exists( $provider ) && is_subclass_of( $provider, AbstractShippingProvider::class ) ) {
- try {
- /**
- * Instantiate the shipping provider class.
- *
- * @var AbstractShippingProvider $provider_instance
- */
- $provider_instance = wc_get_container()->get( $provider );
- } catch ( \Throwable $e ) {
- $logger = wc_get_logger();
- $logger->error(
- sprintf(
- 'Error instantiating shipping provider class %s: %s',
- $provider,
- $e->getMessage()
- ),
- array( 'source' => 'woocommerce-fulfillments' )
- );
- continue; // Skip if the provider class cannot be instantiated.
- }
- } else {
- continue; // Skip if the provider class does not exist or is not a valid shipping provider.
- }
-
- $parsing_result = $provider_instance->try_parse_tracking_number( $tracking_number, $shipping_from, $shipping_to );
+ $parsing_result = $provider->try_parse_tracking_number( $tracking_number, $shipping_from, $shipping_to );
if ( ! is_null( $parsing_result ) ) {
- $results[ $provider_instance->get_key() ] = $parsing_result;
+ $results[ $provider->get_key() ] = $parsing_result;
}
}
diff --git a/plugins/woocommerce/src/Admin/Features/Fulfillments/FulfillmentsRenderer.php b/plugins/woocommerce/src/Admin/Features/Fulfillments/FulfillmentsRenderer.php
index f8e6d232cd7..ef2f6cd95d0 100644
--- a/plugins/woocommerce/src/Admin/Features/Fulfillments/FulfillmentsRenderer.php
+++ b/plugins/woocommerce/src/Admin/Features/Fulfillments/FulfillmentsRenderer.php
@@ -194,10 +194,11 @@ class FulfillmentsRenderer {
} elseif ( 1 === count( $providers ) ) {
$provider_fulfillment = reset( $providers );
$provider_slug = $provider_fulfillment->get_shipment_provider();
- $known_providers = FulfillmentUtils::get_shipping_providers_object();
+ $known_providers = FulfillmentUtils::get_shipping_providers();
$provider_name_meta = $provider_fulfillment->get_meta( '_provider_name' );
- $provider_display_label = $known_providers[ $provider_slug ]['label']
- ?? ( ! empty( $provider_name_meta ) ? $provider_name_meta : $provider_slug );
+ $provider_display_label = isset( $known_providers[ $provider_slug ] )
+ ? $known_providers[ $provider_slug ]->get_name()
+ : ( ! empty( $provider_name_meta ) ? $provider_name_meta : $provider_slug );
echo '<span>' . esc_html( $provider_display_label ) . '</span>';
} else {
echo '<span>--</span>';
@@ -423,8 +424,18 @@ class FulfillmentsRenderer {
* @return void
*/
protected function load_fulfillments_js_settings() {
+ $providers_for_js = array();
+ foreach ( FulfillmentUtils::get_shipping_providers() as $provider ) {
+ $providers_for_js[ $provider->get_key() ] = array(
+ 'label' => $provider->get_name(),
+ 'icon' => $provider->get_icon(),
+ 'value' => $provider->get_key(),
+ 'url' => $provider->get_tracking_url( '__PLACEHOLDER__' ) ?? '',
+ );
+ }
+
$fulfillment_settings = array(
- 'providers' => FulfillmentUtils::get_shipping_providers_object(),
+ 'providers' => $providers_for_js,
'currency_symbols' => get_woocommerce_currency_symbols(),
'fulfillment_statuses' => FulfillmentUtils::get_fulfillment_statuses(),
'order_fulfillment_statuses' => FulfillmentUtils::get_order_fulfillment_statuses(),
@@ -552,7 +563,7 @@ class FulfillmentsRenderer {
return;
}
- $providers = FulfillmentUtils::get_shipping_providers_object();
+ $providers = FulfillmentUtils::get_shipping_providers();
// This is a read-only filter on the admin orders table, so nonce verification is not required.
// phpcs:ignore WordPress.Security.NonceVerification
@@ -560,9 +571,9 @@ class FulfillmentsRenderer {
?>
<select id="shipping-provider-filter" name="shipping_provider">
<option value="" <?php selected( $selected_provider, '' ); ?>><?php esc_html_e( 'Filter by shipping provider', 'woocommerce' ); ?></option>
- <?php foreach ( $providers as $key => $provider ) : ?>
- <option value="<?php echo esc_attr( $key ); ?>" <?php selected( $selected_provider, $key ); ?>>
- <?php echo esc_html( $provider['label'] ?? '' ); ?>
+ <?php foreach ( $providers as $provider ) : ?>
+ <option value="<?php echo esc_attr( $provider->get_key() ); ?>" <?php selected( $selected_provider, $provider->get_key() ); ?>>
+ <?php echo esc_html( $provider->get_name() ); ?>
</option>
<?php endforeach; ?>
<option value="__other__" <?php selected( $selected_provider, '__other__' ); ?>><?php esc_html_e( 'Other', 'woocommerce' ); ?></option>
@@ -673,7 +684,7 @@ class FulfillmentsRenderer {
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
if ( '__other__' === $shipping_provider ) {
- $known_providers = FulfillmentUtils::get_shipping_providers_object();
+ $known_providers = FulfillmentUtils::get_shipping_providers();
$known_keys = array_keys( $known_providers );
if ( empty( $known_keys ) ) {
diff --git a/plugins/woocommerce/src/Internal/RestApi/Routes/V4/Fulfillments/Controller.php b/plugins/woocommerce/src/Internal/RestApi/Routes/V4/Fulfillments/Controller.php
index e5226806d16..9941157a922 100644
--- a/plugins/woocommerce/src/Internal/RestApi/Routes/V4/Fulfillments/Controller.php
+++ b/plugins/woocommerce/src/Internal/RestApi/Routes/V4/Fulfillments/Controller.php
@@ -436,7 +436,15 @@ class Controller extends AbstractController {
* @return WP_REST_Response
*/
public function get_providers( WP_REST_Request $request ): WP_REST_Response {
- $providers = \Automattic\WooCommerce\Admin\Features\Fulfillments\FulfillmentUtils::get_shipping_providers_object();
+ $providers = array();
+ foreach ( \Automattic\WooCommerce\Admin\Features\Fulfillments\FulfillmentUtils::get_shipping_providers() as $provider ) {
+ $providers[ $provider->get_key() ] = array(
+ 'label' => $provider->get_name(),
+ 'icon' => $provider->get_icon(),
+ 'value' => $provider->get_key(),
+ 'url' => $provider->get_tracking_url( '__PLACEHOLDER__' ) ?? '',
+ );
+ }
/**
* Filters the shipping providers response before it is returned.
diff --git a/plugins/woocommerce/tests/php/src/Admin/Features/Fulfillments/FulfillmentsManagerTest.php b/plugins/woocommerce/tests/php/src/Admin/Features/Fulfillments/FulfillmentsManagerTest.php
index 4ddfbaff2e5..2d1d49814d4 100644
--- a/plugins/woocommerce/tests/php/src/Admin/Features/Fulfillments/FulfillmentsManagerTest.php
+++ b/plugins/woocommerce/tests/php/src/Admin/Features/Fulfillments/FulfillmentsManagerTest.php
@@ -136,34 +136,26 @@ class FulfillmentsManagerTest extends \WC_Unit_Test_Case {
}
/**
- * Test that the initial shipping providers can be extended.
+ * Test that the initial shipping providers can be extended with an AbstractShippingProvider instance.
*/
public function test_extend_initial_shipping_providers() {
- // Extend the shipping providers.
- add_filter(
- 'woocommerce_fulfillment_shipping_providers',
- function ( $providers ) {
- $providers['custom_provider'] = array(
- 'label' => __( 'Custom Provider', 'woocommerce' ),
- 'icon' => 'custom-icon',
- 'value' => 'custom_provider',
- );
- return $providers;
- }
- );
+ $mock_provider = new ShippingProviderMock();
- /**
- * Filter to get initial shipping providers.
- *
- * @since 10.1.0
- */
- $shipping_providers = apply_filters( 'woocommerce_fulfillment_shipping_providers', array() );
+ // Extend the shipping providers with an AbstractShippingProvider instance.
+ $filter = function ( $providers ) use ( $mock_provider ) {
+ $providers[] = $mock_provider;
+ return $providers;
+ };
+ add_filter( 'woocommerce_fulfillment_shipping_providers', $filter );
+
+ $shipping_providers = \Automattic\WooCommerce\Admin\Features\Fulfillments\FulfillmentUtils::get_shipping_providers();
+
+ remove_filter( 'woocommerce_fulfillment_shipping_providers', $filter );
- // Check if the custom provider is included.
- $this->assertArrayHasKey( 'custom_provider', $shipping_providers );
- $this->assertIsArray( $shipping_providers['custom_provider'] );
- $this->assertArrayHasKey( 'label', $shipping_providers['custom_provider'] );
- $this->assertEquals( __( 'Custom Provider', 'woocommerce' ), $shipping_providers['custom_provider']['label'] );
+ // Check if the mock provider is included, keyed by its key.
+ $this->assertArrayHasKey( $mock_provider->get_key(), $shipping_providers );
+ $this->assertInstanceOf( ShippingProviderMock::class, $shipping_providers[ $mock_provider->get_key() ] );
+ $this->assertEquals( 'Mock Shipping Provider', $shipping_providers[ $mock_provider->get_key() ]->get_name() );
}
/**
diff --git a/plugins/woocommerce/tests/php/src/Admin/Features/Fulfillments/ShippingProvidersTest.php b/plugins/woocommerce/tests/php/src/Admin/Features/Fulfillments/ShippingProvidersTest.php
index 7c98d0e641e..363646aa381 100644
--- a/plugins/woocommerce/tests/php/src/Admin/Features/Fulfillments/ShippingProvidersTest.php
+++ b/plugins/woocommerce/tests/php/src/Admin/Features/Fulfillments/ShippingProvidersTest.php
@@ -40,7 +40,7 @@ class ShippingProvidersTest extends \WP_UnitTestCase {
}
/**
- * Test that the shipping providers configuration returns the correct classes.
+ * Test that the shipping providers configuration returns the correct instances.
*/
public function test_shipping_providers_configuration(): void {
update_option( 'woocommerce_feature_fulfillments_enabled', 'yes' );
@@ -49,27 +49,22 @@ class ShippingProvidersTest extends \WP_UnitTestCase {
$controller->initialize_fulfillments();
$shipping_providers = FulfillmentUtils::get_shipping_providers();
+ $this->assertNotEmpty( $shipping_providers, 'Expected at least one registered shipping provider.' );
- foreach ( $shipping_providers as $key => $provider_class ) {
- $this->assertTrue(
- class_exists( $provider_class ),
- sprintf( 'Shipping provider class %s does not exist.', $provider_class )
- );
-
- $provider_instance = new $provider_class();
+ foreach ( $shipping_providers as $key => $provider ) {
$this->assertInstanceOf(
ShippingProviders\AbstractShippingProvider::class,
- $provider_instance,
+ $provider,
sprintf( 'Shipping provider %s is not an instance of AbstractShippingProvider.', $key )
);
$this->assertNotEmpty(
- $provider_instance->get_key(),
+ $provider->get_key(),
sprintf( 'Shipping provider %s does not have a valid key.', $key )
);
$this->assertEquals(
$key,
- $provider_instance->get_key(),
- sprintf( 'Shipping provider key %s does not match the expected key %s.', $provider_instance->get_key(), $key )
+ $provider->get_key(),
+ sprintf( 'Shipping provider key %s does not match the expected key %s.', $provider->get_key(), $key )
);
}
}