Commit e444d320fef for woocommerce
commit e444d320fef1face5ebef588194efe3335da3c00
Author: daledupreez <dale@automattic.com>
Date: Fri Jun 26 21:36:38 2026 +0200
Remove usage of traits in woocommerce-subscriptions-engine (#66057)
Remove usage of traits
diff --git a/packages/php/woocommerce-subscriptions-engine/changelog/dev-refactor-subscriptions-engine-support-helpers b/packages/php/woocommerce-subscriptions-engine/changelog/dev-refactor-subscriptions-engine-support-helpers
new file mode 100644
index 00000000000..01d85c06254
--- /dev/null
+++ b/packages/php/woocommerce-subscriptions-engine/changelog/dev-refactor-subscriptions-engine-support-helpers
@@ -0,0 +1,4 @@
+Significance: patch
+Type: dev
+
+Convert internal scalar and money support helpers from traits to static classes
diff --git a/packages/php/woocommerce-subscriptions-engine/src/Api/Rest/PlansController.php b/packages/php/woocommerce-subscriptions-engine/src/Api/Rest/PlansController.php
index 8aacb1c1eb6..67e1f4029e5 100644
--- a/packages/php/woocommerce-subscriptions-engine/src/Api/Rest/PlansController.php
+++ b/packages/php/woocommerce-subscriptions-engine/src/Api/Rest/PlansController.php
@@ -32,8 +32,6 @@ defined( 'ABSPATH' ) || exit;
*/
final class PlansController extends WP_REST_Controller {
- use ScalarCoercion;
-
private const REST_NAMESPACE = 'wc/v3';
private const REST_BASE = 'subscriptions-engine/plans';
@@ -215,7 +213,7 @@ final class PlansController extends WP_REST_Controller {
return $extension_slugs;
}
- $page = max( 1, self::coerce_int( $request->get_param( 'page' ), 1 ) );
+ $page = max( 1, ScalarCoercion::coerce_int( $request->get_param( 'page' ), 1 ) );
$per_page = $this->resolve_per_page( $request );
$args = array(
'limit' => $per_page,
@@ -263,7 +261,7 @@ final class PlansController extends WP_REST_Controller {
return $extension_slug;
}
- $plan = $this->plan_repository->find( self::coerce_int( $request->get_param( 'id' ) ), $extension_slug );
+ $plan = $this->plan_repository->find( ScalarCoercion::coerce_int( $request->get_param( 'id' ) ), $extension_slug );
if ( ! $plan instanceof Plan ) {
return $this->not_found_error();
}
@@ -303,7 +301,7 @@ final class PlansController extends WP_REST_Controller {
'pricing_policy' => $this->pricing_policy_from_param( $request->get_param( 'pricing_policy' ), null ),
'category' => $this->string_param( $request, 'category', Plan::DEFAULT_CATEGORY ),
'status' => $this->string_param( $request, 'status', Plan::STATUS_ACTIVE ),
- 'sort_order' => self::coerce_int( $request->get_param( 'sort_order' ) ),
+ 'sort_order' => ScalarCoercion::coerce_int( $request->get_param( 'sort_order' ) ),
'extension_slug' => $extension_slug,
);
Plan::create( 0, $plan_args );
@@ -344,7 +342,7 @@ final class PlansController extends WP_REST_Controller {
return $extension_slug;
}
- $plan = $this->plan_repository->find( self::coerce_int( $request->get_param( 'id' ) ), $extension_slug );
+ $plan = $this->plan_repository->find( ScalarCoercion::coerce_int( $request->get_param( 'id' ) ), $extension_slug );
if ( ! $plan instanceof Plan ) {
return $this->not_found_error();
}
@@ -388,7 +386,7 @@ final class PlansController extends WP_REST_Controller {
}
if ( $request->has_param( 'sort_order' ) ) {
- $plan->set_sort_order( self::coerce_int( $request->get_param( 'sort_order' ) ) );
+ $plan->set_sort_order( ScalarCoercion::coerce_int( $request->get_param( 'sort_order' ) ) );
}
if ( null !== $sync_group_name ) {
@@ -422,7 +420,7 @@ final class PlansController extends WP_REST_Controller {
$sort_order_by_id = array();
$response_ids = array();
foreach ( array_values( $ids ) as $index => $raw_id ) {
- $id = self::coerce_nullable_int( $raw_id );
+ $id = ScalarCoercion::coerce_nullable_int( $raw_id );
if ( null === $id || $id <= 0 ) {
return $this->invalid_error( __( 'ids must contain only positive integers.', 'woocommerce-subscriptions-engine' ) );
}
@@ -474,7 +472,7 @@ final class PlansController extends WP_REST_Controller {
: null,
);
- $context = self::coerce_string( $request->get_param( 'context' ), 'view' );
+ $context = ScalarCoercion::coerce_string( $request->get_param( 'context' ), 'view' );
$context = '' !== $context ? $context : 'view';
$data = $this->add_additional_fields_to_object( $data, $request );
$data = $this->filter_response_by_context( $data, $context );
@@ -616,7 +614,7 @@ final class PlansController extends WP_REST_Controller {
* @param WP_REST_Request $request Request.
*/
private function resolve_per_page( WP_REST_Request $request ): int {
- $value = self::coerce_int( $request->get_param( 'per_page' ), self::DEFAULT_PER_PAGE );
+ $value = ScalarCoercion::coerce_int( $request->get_param( 'per_page' ), self::DEFAULT_PER_PAGE );
if ( $value < 1 ) {
return self::DEFAULT_PER_PAGE;
}
@@ -680,7 +678,7 @@ final class PlansController extends WP_REST_Controller {
if ( null === $raw ) {
return $this->invalid_error( __( 'extension_slug is required.', 'woocommerce-subscriptions-engine' ) );
}
- $raw_string = trim( self::coerce_string( $raw ) );
+ $raw_string = trim( ScalarCoercion::coerce_string( $raw ) );
if ( '' === $raw_string ) {
return $this->invalid_error( __( 'extension_slug is required.', 'woocommerce-subscriptions-engine' ) );
}
@@ -713,7 +711,7 @@ final class PlansController extends WP_REST_Controller {
if ( null === $raw ) {
return $this->invalid_error( __( 'extension_slug is required.', 'woocommerce-subscriptions-engine' ) );
}
- $raw_string = trim( self::coerce_string( $raw ) );
+ $raw_string = trim( ScalarCoercion::coerce_string( $raw ) );
if ( '' === $raw_string ) {
return $this->invalid_error( __( 'extension_slug is required.', 'woocommerce-subscriptions-engine' ) );
}
@@ -742,7 +740,7 @@ final class PlansController extends WP_REST_Controller {
* @param string $fallback Fallback.
*/
private function string_param( WP_REST_Request $request, string $key, string $fallback = '' ): string {
- return sanitize_text_field( self::coerce_string( $request->get_param( $key ), $fallback ) );
+ return sanitize_text_field( ScalarCoercion::coerce_string( $request->get_param( $key ), $fallback ) );
}
/**
@@ -752,7 +750,7 @@ final class PlansController extends WP_REST_Controller {
* @param string $key Param key.
*/
private function nullable_string_param( WP_REST_Request $request, string $key ): ?string {
- $value = self::coerce_nullable_string( $request->get_param( $key ) );
+ $value = ScalarCoercion::coerce_nullable_string( $request->get_param( $key ) );
if ( null === $value || '' === $value ) {
return null;
}
diff --git a/packages/php/woocommerce-subscriptions-engine/src/Core/Entity/Contract.php b/packages/php/woocommerce-subscriptions-engine/src/Core/Entity/Contract.php
index db38fb2d872..67c5dbe9d84 100644
--- a/packages/php/woocommerce-subscriptions-engine/src/Core/Entity/Contract.php
+++ b/packages/php/woocommerce-subscriptions-engine/src/Core/Entity/Contract.php
@@ -39,9 +39,6 @@ defined( 'ABSPATH' ) || exit;
*/
final class Contract {
- use ScalarCoercion;
- use MoneyScale;
-
const SCHEDULE_SOURCE_PRIMITIVE = 'primitive';
const SCHEDULE_SOURCE_GATEWAY = 'gateway';
@@ -239,29 +236,29 @@ final class Contract {
* @param array<string, mixed> $data Raw attributes keyed by property name.
*/
private function __construct( array $data ) {
- $this->id = self::coerce_nullable_int( $data['id'] ?? null );
- $this->status = self::coerce_string( $data['status'] ?? null, ContractStatus::ACTIVE );
- $this->customer_id = self::coerce_int( $data['customer_id'] ?? null );
- $this->currency = self::coerce_string( $data['currency'] ?? null );
- $this->selling_plan_id = self::coerce_int( $data['selling_plan_id'] ?? null );
- $this->origin_order_id = self::coerce_nullable_int( $data['origin_order_id'] ?? null );
- $this->extension_slug = self::coerce_nullable_string( $data['extension_slug'] ?? null );
- $this->payment_method = self::coerce_nullable_string( $data['payment_method'] ?? null );
- $this->payment_method_title = self::coerce_nullable_string( $data['payment_method_title'] ?? null );
- $this->payment_token_id = self::coerce_nullable_int( $data['payment_token_id'] ?? null );
- $this->start_gmt = self::coerce_string( $data['start_gmt'] ?? null );
- $this->next_payment_gmt = self::coerce_nullable_string( $data['next_payment_gmt'] ?? null );
- $this->plan_snapshot_id = self::coerce_nullable_int( $data['plan_snapshot_id'] ?? null );
- $this->items_snapshot_id = self::coerce_nullable_int( $data['items_snapshot_id'] ?? null );
- $this->billing_total = self::normalize_money( $data['billing_total'] ?? '0' );
- $this->discount_total = self::normalize_money( $data['discount_total'] ?? '0' );
- $this->shipping_total = self::normalize_money( $data['shipping_total'] ?? '0' );
- $this->tax_total = self::normalize_money( $data['tax_total'] ?? '0' );
- $this->last_payment_gmt = self::coerce_nullable_string( $data['last_payment_gmt'] ?? null );
- $this->last_attempt_gmt = self::coerce_nullable_string( $data['last_attempt_gmt'] ?? null );
- $this->trial_end_gmt = self::coerce_nullable_string( $data['trial_end_gmt'] ?? null );
- $this->end_gmt = self::coerce_nullable_string( $data['end_gmt'] ?? null );
- $this->schedule_source = self::coerce_string( $data['schedule_source'] ?? null, self::SCHEDULE_SOURCE_PRIMITIVE );
+ $this->id = ScalarCoercion::coerce_nullable_int( $data['id'] ?? null );
+ $this->status = ScalarCoercion::coerce_string( $data['status'] ?? null, ContractStatus::ACTIVE );
+ $this->customer_id = ScalarCoercion::coerce_int( $data['customer_id'] ?? null );
+ $this->currency = ScalarCoercion::coerce_string( $data['currency'] ?? null );
+ $this->selling_plan_id = ScalarCoercion::coerce_int( $data['selling_plan_id'] ?? null );
+ $this->origin_order_id = ScalarCoercion::coerce_nullable_int( $data['origin_order_id'] ?? null );
+ $this->extension_slug = ScalarCoercion::coerce_nullable_string( $data['extension_slug'] ?? null );
+ $this->payment_method = ScalarCoercion::coerce_nullable_string( $data['payment_method'] ?? null );
+ $this->payment_method_title = ScalarCoercion::coerce_nullable_string( $data['payment_method_title'] ?? null );
+ $this->payment_token_id = ScalarCoercion::coerce_nullable_int( $data['payment_token_id'] ?? null );
+ $this->start_gmt = ScalarCoercion::coerce_string( $data['start_gmt'] ?? null );
+ $this->next_payment_gmt = ScalarCoercion::coerce_nullable_string( $data['next_payment_gmt'] ?? null );
+ $this->plan_snapshot_id = ScalarCoercion::coerce_nullable_int( $data['plan_snapshot_id'] ?? null );
+ $this->items_snapshot_id = ScalarCoercion::coerce_nullable_int( $data['items_snapshot_id'] ?? null );
+ $this->billing_total = MoneyScale::normalize_money( $data['billing_total'] ?? '0' );
+ $this->discount_total = MoneyScale::normalize_money( $data['discount_total'] ?? '0' );
+ $this->shipping_total = MoneyScale::normalize_money( $data['shipping_total'] ?? '0' );
+ $this->tax_total = MoneyScale::normalize_money( $data['tax_total'] ?? '0' );
+ $this->last_payment_gmt = ScalarCoercion::coerce_nullable_string( $data['last_payment_gmt'] ?? null );
+ $this->last_attempt_gmt = ScalarCoercion::coerce_nullable_string( $data['last_attempt_gmt'] ?? null );
+ $this->trial_end_gmt = ScalarCoercion::coerce_nullable_string( $data['trial_end_gmt'] ?? null );
+ $this->end_gmt = ScalarCoercion::coerce_nullable_string( $data['end_gmt'] ?? null );
+ $this->schedule_source = ScalarCoercion::coerce_string( $data['schedule_source'] ?? null, self::SCHEDULE_SOURCE_PRIMITIVE );
$this->items = self::coerce_item_rows( $data['items'] ?? null );
$this->addresses = self::coerce_address_map( $data['addresses'] ?? null );
$this->meta = self::coerce_meta_map( $data['meta'] ?? null );
@@ -464,7 +461,7 @@ final class Contract {
* @param string $billing_total Money value (decimal string or number).
*/
public function set_billing_total( string $billing_total ): void {
- $this->billing_total = self::normalize_money( $billing_total );
+ $this->billing_total = MoneyScale::normalize_money( $billing_total );
}
/**
@@ -480,7 +477,7 @@ final class Contract {
* @param string $discount_total Money value (decimal string or number).
*/
public function set_discount_total( string $discount_total ): void {
- $this->discount_total = self::normalize_money( $discount_total );
+ $this->discount_total = MoneyScale::normalize_money( $discount_total );
}
/**
@@ -496,7 +493,7 @@ final class Contract {
* @param string $shipping_total Money value (decimal string or number).
*/
public function set_shipping_total( string $shipping_total ): void {
- $this->shipping_total = self::normalize_money( $shipping_total );
+ $this->shipping_total = MoneyScale::normalize_money( $shipping_total );
}
/**
@@ -512,7 +509,7 @@ final class Contract {
* @param string $tax_total Money value (decimal string or number).
*/
public function set_tax_total( string $tax_total ): void {
- $this->tax_total = self::normalize_money( $tax_total );
+ $this->tax_total = MoneyScale::normalize_money( $tax_total );
}
/**
@@ -710,7 +707,7 @@ final class Contract {
$map = array();
foreach ( $value as $key => $meta_value ) {
- $map[ (string) $key ] = self::coerce_string( $meta_value );
+ $map[ (string) $key ] = ScalarCoercion::coerce_string( $meta_value );
}
return $map;
diff --git a/packages/php/woocommerce-subscriptions-engine/src/Core/Entity/Cycle.php b/packages/php/woocommerce-subscriptions-engine/src/Core/Entity/Cycle.php
index 5f57b331599..420aee73873 100644
--- a/packages/php/woocommerce-subscriptions-engine/src/Core/Entity/Cycle.php
+++ b/packages/php/woocommerce-subscriptions-engine/src/Core/Entity/Cycle.php
@@ -32,9 +32,6 @@ defined( 'ABSPATH' ) || exit;
*/
final class Cycle {
- use ScalarCoercion;
- use MoneyScale;
-
const KIND_BILLING = 'billing';
/**
@@ -165,21 +162,21 @@ final class Cycle {
* @param array<string, mixed> $data Raw attributes keyed by property name.
*/
private function __construct( array $data ) {
- $this->id = self::coerce_nullable_int( $data['id'] ?? null );
- $this->contract_id = self::coerce_int( $data['contract_id'] ?? null );
- $this->sequence_no = self::coerce_int( $data['sequence_no'] ?? null );
- $this->count = isset( $data['count'] ) ? self::coerce_int( $data['count'] ) : null;
- $this->kind = self::coerce_string( $data['kind'] ?? null, self::KIND_BILLING );
+ $this->id = ScalarCoercion::coerce_nullable_int( $data['id'] ?? null );
+ $this->contract_id = ScalarCoercion::coerce_int( $data['contract_id'] ?? null );
+ $this->sequence_no = ScalarCoercion::coerce_int( $data['sequence_no'] ?? null );
+ $this->count = isset( $data['count'] ) ? ScalarCoercion::coerce_int( $data['count'] ) : null;
+ $this->kind = ScalarCoercion::coerce_string( $data['kind'] ?? null, self::KIND_BILLING );
$this->status = self::coerce_status( $data['status'] ?? null );
- $this->reason = self::coerce_nullable_string( $data['reason'] ?? null );
- $this->starts_at_gmt = self::coerce_string( $data['starts_at_gmt'] ?? null );
- $this->ends_at_gmt = self::coerce_string( $data['ends_at_gmt'] ?? null );
- $this->expected_total = self::normalize_money( $data['expected_total'] ?? '0' );
- $this->currency = self::coerce_string( $data['currency'] ?? null );
- $this->plan_snapshot_id = self::coerce_nullable_int( $data['plan_snapshot_id'] ?? null );
- $this->items_snapshot_id = self::coerce_nullable_int( $data['items_snapshot_id'] ?? null );
- $this->order_id = self::coerce_nullable_int( $data['order_id'] ?? null );
- $this->extension_slug = self::coerce_nullable_string( $data['extension_slug'] ?? null );
+ $this->reason = ScalarCoercion::coerce_nullable_string( $data['reason'] ?? null );
+ $this->starts_at_gmt = ScalarCoercion::coerce_string( $data['starts_at_gmt'] ?? null );
+ $this->ends_at_gmt = ScalarCoercion::coerce_string( $data['ends_at_gmt'] ?? null );
+ $this->expected_total = MoneyScale::normalize_money( $data['expected_total'] ?? '0' );
+ $this->currency = ScalarCoercion::coerce_string( $data['currency'] ?? null );
+ $this->plan_snapshot_id = ScalarCoercion::coerce_nullable_int( $data['plan_snapshot_id'] ?? null );
+ $this->items_snapshot_id = ScalarCoercion::coerce_nullable_int( $data['items_snapshot_id'] ?? null );
+ $this->order_id = ScalarCoercion::coerce_nullable_int( $data['order_id'] ?? null );
+ $this->extension_slug = ScalarCoercion::coerce_nullable_string( $data['extension_slug'] ?? null );
$this->plan_snapshot = ( $data['plan_snapshot'] ?? null ) instanceof PlanSnapshot ? $data['plan_snapshot'] : null;
$this->items_snapshot = ( $data['items_snapshot'] ?? null ) instanceof ItemsSnapshot ? $data['items_snapshot'] : null;
}
@@ -223,10 +220,10 @@ final class Cycle {
* @throws DomainException If the stored status, kind, or sequence_no is invalid.
*/
public static function from_storage( array $row ): self {
- $kind = self::coerce_string( $row['kind'] ?? null, self::KIND_BILLING );
+ $kind = ScalarCoercion::coerce_string( $row['kind'] ?? null, self::KIND_BILLING );
self::assert_valid_kind( $kind );
- $sequence_no = self::coerce_int( $row['sequence_no'] ?? null );
+ $sequence_no = ScalarCoercion::coerce_int( $row['sequence_no'] ?? null );
self::assert_valid_sequence_no( $sequence_no );
// The typed snapshot value objects are attached on load, never hydrated here.
@@ -558,7 +555,7 @@ final class Cycle {
return CycleStatus::pending();
}
- return CycleStatus::from( self::coerce_string( $status ) );
+ return CycleStatus::from( ScalarCoercion::coerce_string( $status ) );
}
/**
@@ -576,7 +573,7 @@ final class Cycle {
return null;
}
- $count = self::coerce_int( $count );
+ $count = ScalarCoercion::coerce_int( $count );
if ( $count < 1 ) {
throw new DomainException(
sprintf( 'Cycle: count must be 1 or greater when set, got %d.', $count )
diff --git a/packages/php/woocommerce-subscriptions-engine/src/Core/Entity/Plan.php b/packages/php/woocommerce-subscriptions-engine/src/Core/Entity/Plan.php
index 03b6098231a..92d6a3e2acf 100644
--- a/packages/php/woocommerce-subscriptions-engine/src/Core/Entity/Plan.php
+++ b/packages/php/woocommerce-subscriptions-engine/src/Core/Entity/Plan.php
@@ -26,8 +26,6 @@ defined( 'ABSPATH' ) || exit;
*/
final class Plan {
- use ScalarCoercion;
-
const DEFAULT_CATEGORY = 'SUBSCRIPTION';
const DEFAULT_STATUS = 'active';
@@ -199,16 +197,16 @@ final class Plan {
return new self(
null,
$group_id,
- self::coerce_string( $args['name'] ?? null ),
- self::coerce_nullable_string( $args['description'] ?? null ),
+ ScalarCoercion::coerce_string( $args['name'] ?? null ),
+ ScalarCoercion::coerce_nullable_string( $args['description'] ?? null ),
is_array( $args['options'] ?? null ) ? $args['options'] : array(),
$billing_policy,
$delivery_policy,
$pricing_policy,
- self::coerce_string( $args['category'] ?? null, self::DEFAULT_CATEGORY ),
- self::coerce_string( $args['status'] ?? null, self::DEFAULT_STATUS ),
- self::coerce_int( $args['sort_order'] ?? null, 0 ),
- self::coerce_nullable_string( $args['extension_slug'] ?? null )
+ ScalarCoercion::coerce_string( $args['category'] ?? null, self::DEFAULT_CATEGORY ),
+ ScalarCoercion::coerce_string( $args['status'] ?? null, self::DEFAULT_STATUS ),
+ ScalarCoercion::coerce_int( $args['sort_order'] ?? null, 0 ),
+ ScalarCoercion::coerce_nullable_string( $args['extension_slug'] ?? null )
);
}
@@ -232,18 +230,18 @@ final class Plan {
}
return new self(
- isset( $row['id'] ) ? self::coerce_int( $row['id'] ) : null,
- self::coerce_int( $row['group_id'] ?? null ),
- self::coerce_string( $row['name'] ?? null ),
- self::coerce_nullable_string( $row['description'] ?? null ),
+ isset( $row['id'] ) ? ScalarCoercion::coerce_int( $row['id'] ) : null,
+ ScalarCoercion::coerce_int( $row['group_id'] ?? null ),
+ ScalarCoercion::coerce_string( $row['name'] ?? null ),
+ ScalarCoercion::coerce_nullable_string( $row['description'] ?? null ),
is_array( $row['options'] ?? null ) ? $row['options'] : array(),
BillingPolicy::from_array( is_array( $row['billing_policy'] ?? null ) ? $row['billing_policy'] : array() ),
isset( $row['delivery_policy'] ) && is_array( $row['delivery_policy'] ) ? DeliveryPolicy::from_array( $row['delivery_policy'] ) : null,
$pricing_policy,
- self::coerce_string( $row['category'] ?? null, self::DEFAULT_CATEGORY ),
- self::coerce_string( $row['status'] ?? null, self::DEFAULT_STATUS ),
- self::coerce_int( $row['sort_order'] ?? null, 0 ),
- self::coerce_nullable_string( $row['extension_slug'] ?? null )
+ ScalarCoercion::coerce_string( $row['category'] ?? null, self::DEFAULT_CATEGORY ),
+ ScalarCoercion::coerce_string( $row['status'] ?? null, self::DEFAULT_STATUS ),
+ ScalarCoercion::coerce_int( $row['sort_order'] ?? null, 0 ),
+ ScalarCoercion::coerce_nullable_string( $row['extension_slug'] ?? null )
);
}
diff --git a/packages/php/woocommerce-subscriptions-engine/src/Core/Entity/PlanGroup.php b/packages/php/woocommerce-subscriptions-engine/src/Core/Entity/PlanGroup.php
index b0b62a2b537..5dac0f094f8 100644
--- a/packages/php/woocommerce-subscriptions-engine/src/Core/Entity/PlanGroup.php
+++ b/packages/php/woocommerce-subscriptions-engine/src/Core/Entity/PlanGroup.php
@@ -25,8 +25,6 @@ defined( 'ABSPATH' ) || exit;
*/
final class PlanGroup {
- use ScalarCoercion;
-
/**
* Group id, or null before it is persisted.
*
@@ -87,10 +85,10 @@ final class PlanGroup {
public static function create( array $args ): self {
return new self(
null,
- self::coerce_string( $args['name'] ?? null ),
- self::coerce_nullable_string( $args['merchant_code'] ?? null ),
+ ScalarCoercion::coerce_string( $args['name'] ?? null ),
+ ScalarCoercion::coerce_nullable_string( $args['merchant_code'] ?? null ),
is_array( $args['options_display'] ?? null ) ? $args['options_display'] : array(),
- self::coerce_nullable_string( $args['extension_slug'] ?? null )
+ ScalarCoercion::coerce_nullable_string( $args['extension_slug'] ?? null )
);
}
@@ -101,11 +99,11 @@ final class PlanGroup {
*/
public static function from_storage( array $row ): self {
return new self(
- isset( $row['id'] ) ? self::coerce_int( $row['id'] ) : null,
- self::coerce_string( $row['name'] ?? null ),
- self::coerce_nullable_string( $row['merchant_code'] ?? null ),
+ isset( $row['id'] ) ? ScalarCoercion::coerce_int( $row['id'] ) : null,
+ ScalarCoercion::coerce_string( $row['name'] ?? null ),
+ ScalarCoercion::coerce_nullable_string( $row['merchant_code'] ?? null ),
is_array( $row['options_display'] ?? null ) ? $row['options_display'] : array(),
- self::coerce_nullable_string( $row['extension_slug'] ?? null )
+ ScalarCoercion::coerce_nullable_string( $row['extension_slug'] ?? null )
);
}
diff --git a/packages/php/woocommerce-subscriptions-engine/src/Core/Support/MoneyScale.php b/packages/php/woocommerce-subscriptions-engine/src/Core/Support/MoneyScale.php
index 3f4b4a5f651..234d7612ce9 100644
--- a/packages/php/woocommerce-subscriptions-engine/src/Core/Support/MoneyScale.php
+++ b/packages/php/woocommerce-subscriptions-engine/src/Core/Support/MoneyScale.php
@@ -23,17 +23,25 @@ defined( 'ABSPATH' ) || exit;
/**
* Money-scale normalization helper.
+ *
+ * @internal Engine implementation detail. Not part of the supported extension API.
*/
-trait MoneyScale {
+final class MoneyScale {
- use ScalarCoercion;
+ /**
+ * Static helper only.
+ *
+ * @internal Engine implementation detail. Not part of the supported extension API.
+ */
+ private function __construct() {}
/**
* Normalize a money value to the storage scale (8 decimals).
*
* @param mixed $value Money value (decimal string or number).
+ * @internal Engine implementation detail. Not part of the supported extension API.
*/
- private static function normalize_money( $value ): string {
- return number_format( self::coerce_float( $value ?? '0' ), 8, '.', '' );
+ public static function normalize_money( $value ): string {
+ return number_format( ScalarCoercion::coerce_float( $value ?? '0' ), 8, '.', '' );
}
}
diff --git a/packages/php/woocommerce-subscriptions-engine/src/Core/Support/ScalarCoercion.php b/packages/php/woocommerce-subscriptions-engine/src/Core/Support/ScalarCoercion.php
index 818db9f5098..107ca7a8b1f 100644
--- a/packages/php/woocommerce-subscriptions-engine/src/Core/Support/ScalarCoercion.php
+++ b/packages/php/woocommerce-subscriptions-engine/src/Core/Support/ScalarCoercion.php
@@ -16,16 +16,26 @@ defined( 'ABSPATH' ) || exit;
/**
* Scalar coercion helpers for hydration boundaries.
+ *
+ * @internal Engine implementation detail. Not part of the supported extension API.
*/
-trait ScalarCoercion {
+final class ScalarCoercion {
+
+ /**
+ * Static helper only.
+ *
+ * @internal Engine implementation detail. Not part of the supported extension API.
+ */
+ private function __construct() {}
/**
* Coerce a value to a string, falling back to a default when it is not scalar.
*
* @param mixed $value The raw value.
* @param string $fallback Returned when $value is not a scalar.
+ * @internal Engine implementation detail. Not part of the supported extension API.
*/
- private static function coerce_string( $value, string $fallback = '' ): string {
+ public static function coerce_string( $value, string $fallback = '' ): string {
return is_scalar( $value ) ? (string) $value : $fallback;
}
@@ -33,8 +43,9 @@ trait ScalarCoercion {
* Coerce a value to a string, or null when it is not a scalar.
*
* @param mixed $value The raw value.
+ * @internal Engine implementation detail. Not part of the supported extension API.
*/
- private static function coerce_nullable_string( $value ): ?string {
+ public static function coerce_nullable_string( $value ): ?string {
return is_scalar( $value ) ? (string) $value : null;
}
@@ -45,8 +56,9 @@ trait ScalarCoercion {
*
* @param mixed $value The raw value.
* @param int $fallback Returned when $value is not an integer.
+ * @internal Engine implementation detail. Not part of the supported extension API.
*/
- private static function coerce_int( $value, int $fallback = 0 ): int {
+ public static function coerce_int( $value, int $fallback = 0 ): int {
if ( is_int( $value ) ) {
return $value;
}
@@ -63,8 +75,9 @@ trait ScalarCoercion {
* forms are rejected rather than truncated.
*
* @param mixed $value The raw value.
+ * @internal Engine implementation detail. Not part of the supported extension API.
*/
- private static function coerce_nullable_int( $value ): ?int {
+ public static function coerce_nullable_int( $value ): ?int {
if ( is_int( $value ) ) {
return $value;
}
@@ -81,8 +94,9 @@ trait ScalarCoercion {
*
* @param mixed $value The raw value.
* @param float $fallback Returned when $value is not numeric.
+ * @internal Engine implementation detail. Not part of the supported extension API.
*/
- private static function coerce_float( $value, float $fallback = 0.0 ): float {
+ public static function coerce_float( $value, float $fallback = 0.0 ): float {
return is_numeric( $value ) ? (float) $value : $fallback;
}
}
diff --git a/packages/php/woocommerce-subscriptions-engine/src/Core/ValueObject/BillingPolicy.php b/packages/php/woocommerce-subscriptions-engine/src/Core/ValueObject/BillingPolicy.php
index 66a354ffedf..25d45f173e6 100644
--- a/packages/php/woocommerce-subscriptions-engine/src/Core/ValueObject/BillingPolicy.php
+++ b/packages/php/woocommerce-subscriptions-engine/src/Core/ValueObject/BillingPolicy.php
@@ -36,8 +36,6 @@ defined( 'ABSPATH' ) || exit;
*/
final class BillingPolicy {
- use ScalarCoercion;
-
/**
* Period unit: 'day' | 'week' | 'month' | 'year'.
*
@@ -126,8 +124,8 @@ final class BillingPolicy {
return new self(
(string) $data['period'],
(int) $data['interval'],
- self::coerce_nullable_int( $data['min_cycles'] ?? null ),
- self::coerce_nullable_int( $data['max_cycles'] ?? null ),
+ ScalarCoercion::coerce_nullable_int( $data['min_cycles'] ?? null ),
+ ScalarCoercion::coerce_nullable_int( $data['max_cycles'] ?? null ),
$trial
);
}
diff --git a/packages/php/woocommerce-subscriptions-engine/src/Core/ValueObject/PlanSnapshot.php b/packages/php/woocommerce-subscriptions-engine/src/Core/ValueObject/PlanSnapshot.php
index ed1e64bc5a0..6126e1d7cac 100644
--- a/packages/php/woocommerce-subscriptions-engine/src/Core/ValueObject/PlanSnapshot.php
+++ b/packages/php/woocommerce-subscriptions-engine/src/Core/ValueObject/PlanSnapshot.php
@@ -29,8 +29,6 @@ defined( 'ABSPATH' ) || exit;
*/
final class PlanSnapshot {
- use ScalarCoercion;
-
/**
* The plan terms payload, as stored on the snapshot row.
*
@@ -99,7 +97,7 @@ final class PlanSnapshot {
* A weak link back to the source plan; a missing key surfaces here as null.
*/
public function get_selling_plan_id(): ?int {
- return isset( $this->data['selling_plan_id'] ) ? self::coerce_int( $this->data['selling_plan_id'] ) : null;
+ return isset( $this->data['selling_plan_id'] ) ? ScalarCoercion::coerce_int( $this->data['selling_plan_id'] ) : null;
}
/**
diff --git a/packages/php/woocommerce-subscriptions-engine/src/Integration/Checkout/ContractFactory.php b/packages/php/woocommerce-subscriptions-engine/src/Integration/Checkout/ContractFactory.php
index f0baf5a90a0..18fc655947e 100644
--- a/packages/php/woocommerce-subscriptions-engine/src/Integration/Checkout/ContractFactory.php
+++ b/packages/php/woocommerce-subscriptions-engine/src/Integration/Checkout/ContractFactory.php
@@ -35,8 +35,6 @@ defined( 'ABSPATH' ) || exit;
*/
final class ContractFactory {
- use ScalarCoercion;
-
/**
* The repository the factory persists through.
*
@@ -95,10 +93,10 @@ final class ContractFactory {
// First renewal date: cycle 1's period end and the contract's next-bill cache.
$next_payment = isset( $overrides['next_payment_gmt'] )
- ? self::coerce_string( $overrides['next_payment_gmt'] )
+ ? ScalarCoercion::coerce_string( $overrides['next_payment_gmt'] )
: $plan->get_billing_policy()->compute_first_renewal_from( $anchor )->format( 'Y-m-d H:i:s' );
- $expected_total = isset( $overrides['billing_total'] ) ? self::coerce_string( $overrides['billing_total'] ) : (string) $order->get_total();
+ $expected_total = isset( $overrides['billing_total'] ) ? ScalarCoercion::coerce_string( $overrides['billing_total'] ) : (string) $order->get_total();
$currency = $order->get_currency();
$contract_defaults = array(
diff --git a/packages/php/woocommerce-subscriptions-engine/src/Integration/Storage/ContractRepository.php b/packages/php/woocommerce-subscriptions-engine/src/Integration/Storage/ContractRepository.php
index 7f0745612a3..a158749ac14 100644
--- a/packages/php/woocommerce-subscriptions-engine/src/Integration/Storage/ContractRepository.php
+++ b/packages/php/woocommerce-subscriptions-engine/src/Integration/Storage/ContractRepository.php
@@ -32,8 +32,6 @@ defined( 'ABSPATH' ) || exit;
*/
final class ContractRepository {
- use ScalarCoercion;
-
/**
* Address columns persisted to the addresses table.
*
@@ -814,13 +812,13 @@ final class ContractRepository {
SchemaInstaller::get_table_name( SchemaInstaller::TABLE_CONTRACT_ITEMS ),
array(
'contract_id' => $contract_id,
- 'item_name' => self::coerce_string( $item['item_name'] ?? null ),
- 'item_type' => self::coerce_string( $item['item_type'] ?? null, 'line_item' ),
- 'product_id' => isset( $item['product_id'] ) ? self::coerce_int( $item['product_id'] ) : null,
- 'variation_id' => isset( $item['variation_id'] ) ? self::coerce_int( $item['variation_id'] ) : null,
- 'quantity' => self::coerce_string( $item['quantity'] ?? null, '1' ),
- 'subtotal' => self::coerce_string( $item['subtotal'] ?? null, '0' ),
- 'total' => self::coerce_string( $item['total'] ?? null, '0' ),
+ 'item_name' => ScalarCoercion::coerce_string( $item['item_name'] ?? null ),
+ 'item_type' => ScalarCoercion::coerce_string( $item['item_type'] ?? null, 'line_item' ),
+ 'product_id' => isset( $item['product_id'] ) ? ScalarCoercion::coerce_int( $item['product_id'] ) : null,
+ 'variation_id' => isset( $item['variation_id'] ) ? ScalarCoercion::coerce_int( $item['variation_id'] ) : null,
+ 'quantity' => ScalarCoercion::coerce_string( $item['quantity'] ?? null, '1' ),
+ 'subtotal' => ScalarCoercion::coerce_string( $item['subtotal'] ?? null, '0' ),
+ 'total' => ScalarCoercion::coerce_string( $item['total'] ?? null, '0' ),
'taxes' => isset( $item['taxes'] ) ? wp_json_encode( $item['taxes'] ) : null,
)
);
@@ -843,7 +841,7 @@ final class ContractRepository {
);
foreach ( self::ADDRESS_COLUMNS as $column ) {
- $record[ $column ] = isset( $address[ $column ] ) ? self::coerce_string( $address[ $column ] ) : null;
+ $record[ $column ] = isset( $address[ $column ] ) ? ScalarCoercion::coerce_string( $address[ $column ] ) : null;
}
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
@@ -909,7 +907,7 @@ final class ContractRepository {
$by_type = array();
foreach ( is_array( $rows ) ? $rows : array() as $row ) {
if ( is_array( $row ) ) {
- $by_type[ self::coerce_string( $row['address_type'] ?? null ) ] = self::as_string_keyed( $row );
+ $by_type[ ScalarCoercion::coerce_string( $row['address_type'] ?? null ) ] = self::as_string_keyed( $row );
}
}
@@ -935,7 +933,7 @@ final class ContractRepository {
$meta = array();
foreach ( is_array( $rows ) ? $rows : array() as $row ) {
if ( is_array( $row ) ) {
- $meta[ self::coerce_string( $row['meta_key'] ?? null ) ] = self::coerce_string( $row['meta_value'] ?? null );
+ $meta[ ScalarCoercion::coerce_string( $row['meta_key'] ?? null ) ] = ScalarCoercion::coerce_string( $row['meta_value'] ?? null );
}
}
@@ -956,13 +954,13 @@ final class ContractRepository {
foreach ( $items as $item ) {
$signature[] = array(
- 'item_name' => self::coerce_string( $item['item_name'] ?? null ),
- 'item_type' => self::coerce_string( $item['item_type'] ?? null, 'line_item' ),
- 'product_id' => isset( $item['product_id'] ) ? (string) self::coerce_int( $item['product_id'] ) : null,
- 'variation_id' => isset( $item['variation_id'] ) ? (string) self::coerce_int( $item['variation_id'] ) : null,
- 'quantity' => number_format( self::coerce_float( $item['quantity'] ?? 1 ), 4, '.', '' ),
- 'subtotal' => number_format( self::coerce_float( $item['subtotal'] ?? 0 ), 8, '.', '' ),
- 'total' => number_format( self::coerce_float( $item['total'] ?? 0 ), 8, '.', '' ),
+ 'item_name' => ScalarCoercion::coerce_string( $item['item_name'] ?? null ),
+ 'item_type' => ScalarCoercion::coerce_string( $item['item_type'] ?? null, 'line_item' ),
+ 'product_id' => isset( $item['product_id'] ) ? (string) ScalarCoercion::coerce_int( $item['product_id'] ) : null,
+ 'variation_id' => isset( $item['variation_id'] ) ? (string) ScalarCoercion::coerce_int( $item['variation_id'] ) : null,
+ 'quantity' => number_format( ScalarCoercion::coerce_float( $item['quantity'] ?? 1 ), 4, '.', '' ),
+ 'subtotal' => number_format( ScalarCoercion::coerce_float( $item['subtotal'] ?? 0 ), 8, '.', '' ),
+ 'total' => number_format( ScalarCoercion::coerce_float( $item['total'] ?? 0 ), 8, '.', '' ),
'taxes' => $this->taxes_signature( $item['taxes'] ?? null ),
);
}
@@ -986,7 +984,7 @@ final class ContractRepository {
foreach ( $addresses as $type => $address ) {
$record = array();
foreach ( self::ADDRESS_COLUMNS as $column ) {
- $value = isset( $address[ $column ] ) ? self::coerce_string( $address[ $column ] ) : '';
+ $value = isset( $address[ $column ] ) ? ScalarCoercion::coerce_string( $address[ $column ] ) : '';
$record[ $column ] = '' !== $value ? $value : null;
}
diff --git a/packages/php/woocommerce-subscriptions-engine/src/Integration/Storage/PlanRepository.php b/packages/php/woocommerce-subscriptions-engine/src/Integration/Storage/PlanRepository.php
index cc0e1821158..9982bbd1ebb 100644
--- a/packages/php/woocommerce-subscriptions-engine/src/Integration/Storage/PlanRepository.php
+++ b/packages/php/woocommerce-subscriptions-engine/src/Integration/Storage/PlanRepository.php
@@ -19,8 +19,6 @@ defined( 'ABSPATH' ) || exit;
*/
final class PlanRepository {
- use ScalarCoercion;
-
/**
* Policy columns stored as JSON.
*
@@ -135,8 +133,8 @@ final class PlanRepository {
$table = SchemaInstaller::get_table_name( SchemaInstaller::TABLE_PLANS );
$order = $this->build_order_clause( $args );
- $limit = max( 1, self::coerce_int( $args['limit'] ?? null, 50 ) );
- $offset = max( 0, self::coerce_int( $args['offset'] ?? null, 0 ) );
+ $limit = max( 1, ScalarCoercion::coerce_int( $args['limit'] ?? null, 50 ) );
+ $offset = max( 0, ScalarCoercion::coerce_int( $args['offset'] ?? null, 0 ) );
// phpcs:ignore Generic.Arrays.DisallowShortArraySyntax.Found
[
@@ -293,7 +291,7 @@ final class PlanRepository {
? array_unique(
array_map(
static function ( $matched_id ): int {
- return self::coerce_int( $matched_id );
+ return ScalarCoercion::coerce_int( $matched_id );
},
$matched_ids
)
@@ -335,7 +333,7 @@ final class PlanRepository {
$clauses = array();
$params = array();
- $status = self::coerce_string( $args['status'] ?? null );
+ $status = ScalarCoercion::coerce_string( $args['status'] ?? null );
if ( '' !== $status ) {
$clauses[] = 'status = %s';
$params[] = $status;
@@ -381,7 +379,7 @@ final class PlanRepository {
}
}
- $search = self::coerce_string( $args['search'] ?? null );
+ $search = ScalarCoercion::coerce_string( $args['search'] ?? null );
if ( '' !== $search ) {
$like = '%' . $wpdb->esc_like( $search ) . '%';
$clauses[] = '(name LIKE %s OR description LIKE %s)';
@@ -408,11 +406,11 @@ final class PlanRepository {
* @param array<string, mixed> $args Query args.
*/
private function build_order_clause( array $args ): string {
- $orderby_arg = self::coerce_string( $args['orderby'] ?? null );
+ $orderby_arg = ScalarCoercion::coerce_string( $args['orderby'] ?? null );
$orderby = isset( self::ORDERBY_COLUMNS[ $orderby_arg ] )
? self::ORDERBY_COLUMNS[ $orderby_arg ]
: 'sort_order';
- $order = 'desc' === strtolower( self::coerce_string( $args['order'] ?? null ) ) ? 'DESC' : 'ASC';
+ $order = 'desc' === strtolower( ScalarCoercion::coerce_string( $args['order'] ?? null ) ) ? 'DESC' : 'ASC';
if ( 'sort_order' === $orderby ) {
return "ORDER BY sort_order {$order}, id ASC";
diff --git a/packages/php/woocommerce-subscriptions-engine/tests/integration/Integration/Storage/SchemaInstallerTest.php b/packages/php/woocommerce-subscriptions-engine/tests/integration/Integration/Storage/SchemaInstallerTest.php
index 7b2e110b635..2c42673bffd 100644
--- a/packages/php/woocommerce-subscriptions-engine/tests/integration/Integration/Storage/SchemaInstallerTest.php
+++ b/packages/php/woocommerce-subscriptions-engine/tests/integration/Integration/Storage/SchemaInstallerTest.php
@@ -18,8 +18,6 @@ use Automattic\WooCommerce\SubscriptionsEngine\Integration\Storage\SchemaInstall
*/
class SchemaInstallerTest extends EngineIntegrationTestCase {
- use ScalarCoercion;
-
/**
* The baseline tables the installer owns, including the cycle tables.
*
@@ -459,7 +457,7 @@ class SchemaInstallerTest extends EngineIntegrationTestCase {
$names = array();
foreach ( is_array( $rows ) ? $rows : array() as $row ) {
if ( is_array( $row ) ) {
- $names[] = self::coerce_string( $row['Key_name'] ?? null );
+ $names[] = ScalarCoercion::coerce_string( $row['Key_name'] ?? null );
}
}
@@ -483,8 +481,8 @@ class SchemaInstallerTest extends EngineIntegrationTestCase {
usort(
$rows,
static function ( $a, $b ): int {
- $a_seq = is_array( $a ) ? self::coerce_int( $a['Seq_in_index'] ?? null ) : 0;
- $b_seq = is_array( $b ) ? self::coerce_int( $b['Seq_in_index'] ?? null ) : 0;
+ $a_seq = is_array( $a ) ? ScalarCoercion::coerce_int( $a['Seq_in_index'] ?? null ) : 0;
+ $b_seq = is_array( $b ) ? ScalarCoercion::coerce_int( $b['Seq_in_index'] ?? null ) : 0;
return $a_seq <=> $b_seq;
}
@@ -493,7 +491,7 @@ class SchemaInstallerTest extends EngineIntegrationTestCase {
$columns = array();
foreach ( $rows as $row ) {
if ( is_array( $row ) ) {
- $columns[] = self::coerce_string( $row['Column_name'] ?? null );
+ $columns[] = ScalarCoercion::coerce_string( $row['Column_name'] ?? null );
}
}
@@ -519,7 +517,7 @@ class SchemaInstallerTest extends EngineIntegrationTestCase {
foreach ( $rows as $row ) {
// Non_unique = 0 marks a UNIQUE index.
- if ( is_array( $row ) && '0' !== self::coerce_string( $row['Non_unique'] ?? null ) ) {
+ if ( is_array( $row ) && '0' !== ScalarCoercion::coerce_string( $row['Non_unique'] ?? null ) ) {
return false;
}
}
diff --git a/packages/php/woocommerce-subscriptions-engine/tests/integration/Integration/Storage/SnapshotStoreTest.php b/packages/php/woocommerce-subscriptions-engine/tests/integration/Integration/Storage/SnapshotStoreTest.php
index 6b2ce8129d0..85c21eff967 100644
--- a/packages/php/woocommerce-subscriptions-engine/tests/integration/Integration/Storage/SnapshotStoreTest.php
+++ b/packages/php/woocommerce-subscriptions-engine/tests/integration/Integration/Storage/SnapshotStoreTest.php
@@ -19,8 +19,6 @@ use Automattic\WooCommerce\SubscriptionsEngine\Integration\Storage\SnapshotStore
*/
class SnapshotStoreTest extends EngineIntegrationTestCase {
- use ScalarCoercion;
-
/**
* The System Under Test.
*
@@ -80,10 +78,10 @@ class SnapshotStoreTest extends EngineIntegrationTestCase {
$row = $this->snapshot_row( $id );
$this->assertNotNull( $row );
- $this->assertSame( '100', self::coerce_string( $row['contract_id'] ?? null ) );
+ $this->assertSame( '100', ScalarCoercion::coerce_string( $row['contract_id'] ?? null ) );
$this->assertSame( SnapshotStore::TYPE_PLAN, $row['snapshot_type'] );
- $this->assertSame( '7', self::coerce_string( $row['parent_id'] ?? null ) );
- $this->assertSame( '2', self::coerce_string( $row['schema_version'] ?? null ) );
+ $this->assertSame( '7', ScalarCoercion::coerce_string( $row['parent_id'] ?? null ) );
+ $this->assertSame( '2', ScalarCoercion::coerce_string( $row['schema_version'] ?? null ) );
}
/**
@@ -111,7 +109,7 @@ class SnapshotStoreTest extends EngineIntegrationTestCase {
$row = $this->snapshot_row( $id );
$this->assertNotNull( $row );
- $this->assertSame( $payload, json_decode( self::coerce_string( $row['payload'] ?? null ), true ) );
+ $this->assertSame( $payload, json_decode( ScalarCoercion::coerce_string( $row['payload'] ?? null ), true ) );
}
/**