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 ) );
 	}

 	/**