Skip to content

Commit 531f046

Browse files
authored
Fix PHP 8.2 deprecation warnings caused by dynamic properties on the $order object (#786)
* Add Dynamic_Props class to store dynamic properties for order object. * Replace dynamic props usage with Dynamic_Props class. * Minor doc updates. * Docs improvements. * Update namespace to use v6_0_0 * Added "sv_wc_plugin_framework_use_dynamic_props_class" filter to make use of Dynamic_Props class opt-in only. * Add OrderHelper Class to simplify property get/set on order object. * Update OrderHelper methods to follow the WP case.
1 parent bc185e2 commit 531f046

10 files changed

+615
-174
lines changed
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
<?php
2+
/**
3+
* Helper class for managing WooCommerce order properties.
4+
*
5+
* This class provides helper methods for getting and setting order properties
6+
* in a standardized way using the Dynamic_Props class. It handles payment details,
7+
* customer information, and custom order properties.
8+
*
9+
* @package SkyVerge/WooCommerce/Helpers
10+
* @since x.x.x
11+
*/
12+
13+
namespace SkyVerge\WooCommerce\PluginFramework\v6_0_0\Helpers;
14+
15+
use SkyVerge\WooCommerce\PluginFramework\v6_0_0\Payment_Gateway\Dynamic_Props;
16+
17+
/**
18+
* OrderHelper class
19+
*
20+
* @since x.x.x
21+
*/
22+
class OrderHelper {
23+
24+
/**
25+
* Gets the payment object associated with an order.
26+
*
27+
* Retrieves the payment details stored as a dynamic property on the order object.
28+
* If no payment object exists, returns an empty stdClass instance.
29+
*
30+
* @since x.x.x
31+
*
32+
* @param \WC_Order $order The order object.
33+
* @return \stdClass The payment object containing payment details.
34+
*/
35+
public static function get_payment( \WC_Order $order ) {
36+
return Dynamic_Props::get( $order, 'payment', null, new \stdClass() );
37+
}
38+
39+
/**
40+
* Gets the payment total for an order.
41+
*
42+
* Retrieves the total payment amount stored as a dynamic property on the order.
43+
*
44+
* @since x.x.x
45+
*
46+
* @param \WC_Order $order The order object.
47+
* @return mixed The payment total amount, or null if not set.
48+
*/
49+
public static function get_payment_total( \WC_Order $order ) {
50+
return Dynamic_Props::get( $order, 'payment_total' );
51+
}
52+
53+
/**
54+
* Gets the customer ID associated with an order.
55+
*
56+
* Retrieves the customer ID stored as a dynamic property on the order.
57+
*
58+
* @since x.x.x
59+
*
60+
* @param \WC_Order $order The order object.
61+
* @return mixed The customer ID, or null if not set.
62+
*/
63+
public static function get_customer_id( \WC_Order $order ) {
64+
return Dynamic_Props::get( $order, 'customer_id' );
65+
}
66+
67+
/**
68+
* Gets a dynamic property from an order.
69+
*
70+
* Provides a generic way to retrieve any dynamic property stored on an order object.
71+
* Supports nested properties through the optional nested_key parameter.
72+
*
73+
* @since x.x.x
74+
*
75+
* @param \WC_Order $order The order object.
76+
* @param string $key The property key to retrieve.
77+
* @param string $nested_key Optional. A nested key within the property. Default null.
78+
* @param mixed $default Optional. The default value if the property doesn't exist. Default null.
79+
* @return mixed The property value if found, or the default value if not found.
80+
*/
81+
public static function get_property( \WC_Order $order, string $key, $nested_key = null, $default = null ): mixed {
82+
return Dynamic_Props::get( $order, $key, $nested_key, $default );
83+
}
84+
85+
/**
86+
* Sets the payment object for an order.
87+
*
88+
* Stores payment details as a dynamic property on the order object.
89+
* This method uses pass-by-reference to modify the order object directly.
90+
*
91+
* @since x.x.x
92+
*
93+
* @param \WC_Order $order The order object (passed by reference).
94+
* @param \stdClass $payment The payment object containing payment details.
95+
*/
96+
public static function set_payment( \WC_Order &$order, \stdClass $payment ) {
97+
Dynamic_Props::set( $order, 'payment', $payment );
98+
}
99+
100+
/**
101+
* Sets the payment total for an order.
102+
*
103+
* Stores the payment total as a dynamic property on the order object.
104+
* This method uses pass-by-reference to modify the order object directly.
105+
*
106+
* @since x.x.x
107+
*
108+
* @param \WC_Order $order The order object (passed by reference).
109+
* @param float|string $payment_total The payment total amount.
110+
*/
111+
public static function set_payment_total( \WC_Order &$order, $payment_total ) {
112+
Dynamic_Props::set( $order, 'payment_total', $payment_total );
113+
}
114+
115+
/**
116+
* Sets the customer ID for an order.
117+
*
118+
* Stores the customer ID as a dynamic property on the order object.
119+
* This method uses pass-by-reference to modify the order object directly.
120+
*
121+
* @since x.x.x
122+
*
123+
* @param \WC_Order $order The order object (passed by reference).
124+
* @param mixed $customer_id The customer ID to set.
125+
*/
126+
public static function set_customer_id( \WC_Order &$order, $customer_id ) {
127+
Dynamic_Props::set( $order, 'customer_id', $customer_id );
128+
}
129+
130+
/**
131+
* Sets a dynamic property on an order.
132+
*
133+
* Provides a generic way to store any dynamic property on an order object.
134+
* This method uses pass-by-reference to modify the order object directly.
135+
*
136+
* @since x.x.x
137+
*
138+
* @param \WC_Order $order The order object (passed by reference).
139+
* @param string $key The property key to set.
140+
* @param mixed $value The value to set for the property.
141+
*/
142+
public static function set_property( \WC_Order &$order, string $key, $value ) {
143+
Dynamic_Props::set( $order, $key, $value );
144+
}
145+
}
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
<?php
2+
/**
3+
* Dynamic property storage handler for WooCommerce order objects.
4+
*
5+
* Provides a PHP 8.2+ compatible way to store dynamic properties on order objects
6+
* while maintaining backwards compatibility with PHP 7.4+.
7+
*
8+
* @package SkyVerge/WooCommerce/Payment-Gateway/Classes
9+
* @since x.x.x
10+
*/
11+
12+
namespace SkyVerge\WooCommerce\PluginFramework\v6_0_0\Payment_Gateway;
13+
14+
/**
15+
* Dynamic property storage handler for WooCommerce order objects.
16+
*
17+
* This class provides a way to store dynamic properties on order objects without using
18+
* dynamic properties (deprecated in PHP 8.2+) while maintaining backwards compatibility
19+
* with PHP 7.4+. It uses WeakMap for PHP 8.0+ and falls back to dynamic properties
20+
* for PHP 7.4+.
21+
*
22+
* @since x.x.x
23+
*
24+
* @example
25+
* ```php
26+
* // Store properties
27+
* Dynamic_Props::set($order, 'customer_id', 123);
28+
* Dynamic_Props::set($order, 'payment_total', 99.99);
29+
*
30+
* // Retrieve properties
31+
* $customer_id = Dynamic_Props::get($order, 'customer_id');
32+
* $total = Dynamic_Props::get($order, 'payment_total');
33+
* ```
34+
*/
35+
class Dynamic_Props {
36+
/**
37+
* Storage container for dynamic properties using WeakMap in PHP 8.0+.
38+
*
39+
* Uses WeakMap to store properties without memory leaks, as WeakMap allows garbage
40+
* collection of its keys when they're no longer referenced elsewhere.
41+
*
42+
* @since x.x.x
43+
* @var \WeakMap<object, \stdClass>|null
44+
*/
45+
private static ?\WeakMap $map = null;
46+
47+
/**
48+
* Sets a property on the order object.
49+
*
50+
* Stores a dynamic property either using WeakMap (PHP 8.0+) or direct property
51+
* access (PHP 7.4+). The storage method is automatically determined based on
52+
* PHP version and WeakMap availability.
53+
*
54+
* @since x.x.x
55+
*
56+
* @param \WC_Order $order The order object to store data on.
57+
* @param string $key The property key.
58+
* @param mixed $value The value to store.
59+
* @return void
60+
*
61+
* @example
62+
* ```php
63+
* Dynamic_Props::set($order, 'customer_id', 123);
64+
* Dynamic_Props::set($order, 'payment_total', '99.99');
65+
* ```
66+
*/
67+
public static function set( \WC_Order &$order, string $key, mixed $value ): void {
68+
if ( self::use_weak_map() ) {
69+
self::init_weak_map();
70+
if ( ! isset( self::$map[ $order ] ) ) {
71+
self::$map[ $order ] = new \stdClass();
72+
}
73+
self::$map[ $order ]->{ $key } = $value;
74+
} else {
75+
$order->{ $key } = $value;
76+
}
77+
}
78+
79+
/**
80+
* Gets a property from the order object.
81+
*
82+
* Retrieves a stored dynamic property using the appropriate storage method
83+
* based on PHP version. Supports nested property access.
84+
*
85+
* @since x.x.x
86+
*
87+
* @param \WC_Order $order The order object to retrieve data from.
88+
* @param string $key The property key.
89+
* @param string $nested_key Optional. The nested property key. Default null.
90+
* @param mixed $default Optional. Default value if not found. Default null.
91+
* @return mixed The stored value or default if not found.
92+
*
93+
* @example
94+
* ```php
95+
* $customer_id = Dynamic_Props::get($order, 'customer_id');
96+
* $total = Dynamic_Props::get($order, 'payment_total');
97+
* $token = Dynamic_Props::get($order, 'payment', 'token', 'DEFAULT_TOKEN');
98+
* ```
99+
*/
100+
public static function get( \WC_Order $order, string $key, $nested_key = null, $default = null ): mixed {
101+
if ( self::use_weak_map() ) {
102+
self::init_weak_map();
103+
if ( is_null( $nested_key ) ) {
104+
return self::$map[ $order ]->{ $key } ?? $default;
105+
} else {
106+
return self::$map[ $order ]->{ $key }->{ $nested_key } ?? $default;
107+
}
108+
}
109+
if ( is_null( $nested_key ) ) {
110+
return $order->{ $key } ?? $default;
111+
} else {
112+
return $order->{ $key }->{ $nested_key } ?? $default;
113+
}
114+
}
115+
116+
/**
117+
* Unsets a property on the order object.
118+
*
119+
* Removes a stored dynamic property using the appropriate storage method
120+
* based on PHP version.
121+
*
122+
* @since x.x.x
123+
*
124+
* @param \WC_Order $order The order object to unset data from.
125+
* @param string $key The property key to remove.
126+
* @return void
127+
*/
128+
public static function unset( \WC_Order &$order, string $key ): void {
129+
if ( self::use_weak_map() ) {
130+
self::init_weak_map();
131+
unset( self::$map[ $order ]->{ $key } );
132+
} else {
133+
unset( $order->{ $key } );
134+
}
135+
}
136+
137+
/**
138+
* Checks if Dynamic_Props class should be used based on the filter.
139+
*
140+
* @return bool True if Dynamic_Props class should be used, false otherwise.
141+
*/
142+
private static function use_dynamic_props_class(): bool {
143+
static $use_dynamic_props_class = null;
144+
if ( null === $use_dynamic_props_class ) {
145+
/**
146+
* Filters whether to use Dynamic_Props class for storing order data.
147+
*
148+
* @since x.x.x
149+
*
150+
* @var bool Whether to Dynamic_Props class for storing order data.
151+
*/
152+
$use_dynamic_props_class = apply_filters( 'sv_wc_plugin_framework_use_dynamic_props_class', false );
153+
}
154+
return $use_dynamic_props_class;
155+
}
156+
157+
/**
158+
* Checks if WeakMap should be used based on PHP version.
159+
*
160+
* Determines whether to use WeakMap storage based on PHP version (8.0+)
161+
* and WeakMap class availability. Result is cached for performance.
162+
*
163+
* @since x.x.x
164+
* @return bool True if WeakMap should be used, false otherwise.
165+
*/
166+
private static function use_weak_map(): bool {
167+
static $use_weak_map = null;
168+
169+
if ( null === $use_weak_map ) {
170+
$use_weak_map = version_compare( PHP_VERSION, '8.0', '>=' ) && self::use_dynamic_props_class();
171+
}
172+
173+
return $use_weak_map;
174+
}
175+
176+
/**
177+
* Initializes WeakMap storage if not already initialized.
178+
*
179+
* Ensures the WeakMap storage is initialized only once when needed.
180+
* This lazy initialization helps with performance and memory usage.
181+
*
182+
* @since x.x.x
183+
* @return void
184+
*/
185+
private static function init_weak_map(): void {
186+
if ( null === self::$map ) {
187+
self::$map = new \WeakMap();
188+
}
189+
}
190+
}

woocommerce/payment-gateway/Handlers/Abstract_Hosted_Payment_Handler.php

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
namespace SkyVerge\WooCommerce\PluginFramework\v6_0_0\Payment_Gateway\Handlers;
2626

2727
use SkyVerge\WooCommerce\PluginFramework\v6_0_0 as FrameworkBase;
28+
use SkyVerge\WooCommerce\PluginFramework\v6_0_0\Helpers\OrderHelper;
2829

2930
if ( ! class_exists( '\\SkyVerge\\WooCommerce\\PluginFramework\\v6_0_0\\Payment_Gateway\\Handlers\\Abstract_Hosted_Payment_Handler' ) ) :
3031

@@ -325,20 +326,25 @@ protected function get_order_from_response( FrameworkBase\SV_WC_Payment_Gateway_
325326

326327
$order = $this->get_gateway()->get_order( $order );
327328

328-
$order->payment->account_number = $response->get_account_number();
329+
$payment = OrderHelper::get_payment( $order );
330+
331+
$payment->account_number = $response->get_account_number();
329332

330333
if ( $response instanceof FrameworkBase\SV_WC_Payment_Gateway_API_Payment_Notification_Credit_Card_Response ) {
331334

332-
$order->payment->exp_month = $response->get_exp_month();
333-
$order->payment->exp_year = $response->get_exp_year();
334-
$order->payment->card_type = $response->get_card_type();
335+
$payment->exp_month = $response->get_exp_month();
336+
$payment->exp_year = $response->get_exp_year();
337+
$payment->card_type = $response->get_card_type();
335338

336339
} elseif ( $response instanceof FrameworkBase\SV_WC_Payment_Gateway_API_Payment_Notification_eCheck_Response ) {
337340

338-
$order->payment->account_type = $response->get_account_type();
339-
$order->payment->check_number = $response->get_check_number();
341+
$payment->account_type = $response->get_account_type();
342+
$payment->check_number = $response->get_check_number();
340343
}
341344

345+
// Set payment info on the order object.
346+
OrderHelper::set_payment( $order, $payment );
347+
342348
return $order;
343349
}
344350

0 commit comments

Comments
 (0)