Commit dad98f94cff for woocommerce
commit dad98f94cff17a89315971aaf281e622c5d99ec6
Author: daledupreez <dale@automattic.com>
Date: Tue Jun 30 17:14:08 2026 +0200
Require explicit class constant visibility for woocommerce-subscriptions-engine (#66066)
- Add the PSR12 ConstantVisibility sniff as a PHPCS error
- Set explicit visibility on all engine class constants
- Make version and log-source constants non-public with public accessors
diff --git a/packages/php/woocommerce-subscriptions-engine/changelog/add-phpcs-sniff-to-require-const-visibility b/packages/php/woocommerce-subscriptions-engine/changelog/add-phpcs-sniff-to-require-const-visibility
new file mode 100644
index 00000000000..bdf197f0d58
--- /dev/null
+++ b/packages/php/woocommerce-subscriptions-engine/changelog/add-phpcs-sniff-to-require-const-visibility
@@ -0,0 +1,4 @@
+Significance: patch
+Type: dev
+
+Require explicit visibility for class constants
diff --git a/packages/php/woocommerce-subscriptions-engine/phpcs.xml b/packages/php/woocommerce-subscriptions-engine/phpcs.xml
index 0c10b25c26d..22a3255832d 100644
--- a/packages/php/woocommerce-subscriptions-engine/phpcs.xml
+++ b/packages/php/woocommerce-subscriptions-engine/phpcs.xml
@@ -6,6 +6,12 @@
<!-- Define files and folders to scan -->
<file>.</file>
+ <!-- Require class constants to have explicit visibility -->
+ <rule ref="PSR12.Properties.ConstantVisibility.NotFound">
+ <type>error</type>
+ <exclude-pattern>tests/</exclude-pattern>
+ </rule>
+
<!-- Exclude test files from FileName rules -->
<rule ref="WordPress.Files.FileName">
<exclude-pattern>tests/*</exclude-pattern>
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 67c5dbe9d84..8e47e0ae920 100644
--- a/packages/php/woocommerce-subscriptions-engine/src/Core/Entity/Contract.php
+++ b/packages/php/woocommerce-subscriptions-engine/src/Core/Entity/Contract.php
@@ -39,11 +39,11 @@ defined( 'ABSPATH' ) || exit;
*/
final class Contract {
- const SCHEDULE_SOURCE_PRIMITIVE = 'primitive';
- const SCHEDULE_SOURCE_GATEWAY = 'gateway';
+ public const SCHEDULE_SOURCE_PRIMITIVE = 'primitive';
+ public const SCHEDULE_SOURCE_GATEWAY = 'gateway';
- const ADDRESS_BILLING = 'billing';
- const ADDRESS_SHIPPING = 'shipping';
+ public const ADDRESS_BILLING = 'billing';
+ public const ADDRESS_SHIPPING = 'shipping';
/**
* Contract id, or null before it is persisted.
diff --git a/packages/php/woocommerce-subscriptions-engine/src/Core/Entity/ContractStatus.php b/packages/php/woocommerce-subscriptions-engine/src/Core/Entity/ContractStatus.php
index bf9fdcb55d6..d4e10fd4db7 100644
--- a/packages/php/woocommerce-subscriptions-engine/src/Core/Entity/ContractStatus.php
+++ b/packages/php/woocommerce-subscriptions-engine/src/Core/Entity/ContractStatus.php
@@ -21,11 +21,11 @@ defined( 'ABSPATH' ) || exit;
*/
final class ContractStatus {
- const ACTIVE = 'active';
- const ON_HOLD = 'on-hold';
- const PENDING_CANCELLATION = 'pending-cancellation';
- const CANCELLED = 'cancelled';
- const EXPIRED = 'expired';
+ public const ACTIVE = 'active';
+ public const ON_HOLD = 'on-hold';
+ public const PENDING_CANCELLATION = 'pending-cancellation';
+ public const CANCELLED = 'cancelled';
+ public const EXPIRED = 'expired';
/**
* All known statuses.
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 420aee73873..be41fc42d19 100644
--- a/packages/php/woocommerce-subscriptions-engine/src/Core/Entity/Cycle.php
+++ b/packages/php/woocommerce-subscriptions-engine/src/Core/Entity/Cycle.php
@@ -32,7 +32,7 @@ defined( 'ABSPATH' ) || exit;
*/
final class Cycle {
- const KIND_BILLING = 'billing';
+ public const KIND_BILLING = 'billing';
/**
* Cycle id, or null before it is persisted.
diff --git a/packages/php/woocommerce-subscriptions-engine/src/Core/Entity/CycleStatus.php b/packages/php/woocommerce-subscriptions-engine/src/Core/Entity/CycleStatus.php
index 672d7f7ff9e..59f081d03db 100644
--- a/packages/php/woocommerce-subscriptions-engine/src/Core/Entity/CycleStatus.php
+++ b/packages/php/woocommerce-subscriptions-engine/src/Core/Entity/CycleStatus.php
@@ -28,10 +28,10 @@ defined( 'ABSPATH' ) || exit;
*/
final class CycleStatus {
- const PENDING = 'pending';
- const BILLED = 'billed';
- const FAILED = 'failed';
- const CANCELLED = 'cancelled';
+ public const PENDING = 'pending';
+ public const BILLED = 'billed';
+ public const FAILED = 'failed';
+ public const CANCELLED = 'cancelled';
/**
* The status string this value wraps.
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 92d6a3e2acf..43d02403c09 100644
--- a/packages/php/woocommerce-subscriptions-engine/src/Core/Entity/Plan.php
+++ b/packages/php/woocommerce-subscriptions-engine/src/Core/Entity/Plan.php
@@ -26,17 +26,17 @@ defined( 'ABSPATH' ) || exit;
*/
final class Plan {
- const DEFAULT_CATEGORY = 'SUBSCRIPTION';
+ public const DEFAULT_CATEGORY = 'SUBSCRIPTION';
- const DEFAULT_STATUS = 'active';
+ public const DEFAULT_STATUS = 'active';
- const STATUS_ACTIVE = 'active';
+ public const STATUS_ACTIVE = 'active';
- const STATUS_ARCHIVED = 'archived';
+ public const STATUS_ARCHIVED = 'archived';
- const ALLOWED_STATUSES = array( self::STATUS_ACTIVE, self::STATUS_ARCHIVED );
+ public const ALLOWED_STATUSES = array( self::STATUS_ACTIVE, self::STATUS_ARCHIVED );
- const ALLOWED_POLICY_TYPES = array( 'percentage', 'fixed_amount', 'price' );
+ public const ALLOWED_POLICY_TYPES = array( 'percentage', 'fixed_amount', 'price' );
/**
* Plan id, or null before it is persisted.
diff --git a/packages/php/woocommerce-subscriptions-engine/src/Core/Gateway/GatewayCapabilities.php b/packages/php/woocommerce-subscriptions-engine/src/Core/Gateway/GatewayCapabilities.php
index dd72b6f0479..02cf899ee70 100644
--- a/packages/php/woocommerce-subscriptions-engine/src/Core/Gateway/GatewayCapabilities.php
+++ b/packages/php/woocommerce-subscriptions-engine/src/Core/Gateway/GatewayCapabilities.php
@@ -34,13 +34,13 @@ final class GatewayCapabilities {
* The most fundamental flag: an engine-scheduled contract on a gateway
* lacking this capability would create renewals nobody runs.
*/
- const RECURRING = 'recurring';
+ public const RECURRING = 'recurring';
/**
* Customer can change the payment method on an active contract. All modern
* gateways support this; the flag exists for the rare manual-only case.
*/
- const PAYMENT_METHOD_CHANGE = 'payment_method_change';
+ public const PAYMENT_METHOD_CHANGE = 'payment_method_change';
/**
* Gateway tolerates variable charge amounts (volume-tier upgrades, tax-rate
@@ -48,13 +48,13 @@ final class GatewayCapabilities {
* amount-changing flows can refuse on an incapable gateway rather than
* silently mis-charging.
*/
- const AMOUNT_CHANGES = 'amount_changes';
+ public const AMOUNT_CHANGES = 'amount_changes';
/**
* One customer can hold N active contracts. Default for modern gateways;
* the flag exists for single-mandate gateways.
*/
- const MULTIPLE_PER_CUSTOMER = 'multiple_per_customer';
+ public const MULTIPLE_PER_CUSTOMER = 'multiple_per_customer';
/**
* Gateway schedules and fires renewals itself; the engine only tracks
@@ -62,7 +62,7 @@ final class GatewayCapabilities {
* a gateway can declare this capability without every contract on it being
* gateway-scheduled.
*/
- const GATEWAY_SCHEDULED_RENEWALS = 'gateway_scheduled_renewals';
+ public const GATEWAY_SCHEDULED_RENEWALS = 'gateway_scheduled_renewals';
/**
* In-memory declarations, keyed by gateway id => list of declared capability
diff --git a/packages/php/woocommerce-subscriptions-engine/src/Integration/Checkout/OrderLinkage.php b/packages/php/woocommerce-subscriptions-engine/src/Integration/Checkout/OrderLinkage.php
index c1003e13d2e..dd47640f9b2 100644
--- a/packages/php/woocommerce-subscriptions-engine/src/Integration/Checkout/OrderLinkage.php
+++ b/packages/php/woocommerce-subscriptions-engine/src/Integration/Checkout/OrderLinkage.php
@@ -29,32 +29,32 @@ final class OrderLinkage {
*
* Stored as a stringified integer (order meta is a flat string table).
*/
- const META_CONTRACT_ID = '_subscription_contract_id';
+ public const META_CONTRACT_ID = '_subscription_contract_id';
/**
* Order meta key holding the relation type - see the `RELATION_*` constants.
*/
- const META_RELATION_TYPE = '_subscription_relation_type';
+ public const META_RELATION_TYPE = '_subscription_relation_type';
/**
* The order whose checkout created the contract (the contract's `origin_order_id`).
*/
- const RELATION_PARENT = 'parent';
+ public const RELATION_PARENT = 'parent';
/**
* A renewal order - created by the renewal engine when a cycle bills.
*/
- const RELATION_RENEWAL = 'renewal';
+ public const RELATION_RENEWAL = 'renewal';
/**
* A switch order - customer moved between plans.
*/
- const RELATION_SWITCH = 'switch';
+ public const RELATION_SWITCH = 'switch';
/**
* A resubscribe order - customer restarted a previously-cancelled contract.
*/
- const RELATION_RESUBSCRIBE = 'resubscribe';
+ public const RELATION_RESUBSCRIBE = 'resubscribe';
/**
* All recognized relation types.
diff --git a/packages/php/woocommerce-subscriptions-engine/src/Integration/Contracts/Cancellation.php b/packages/php/woocommerce-subscriptions-engine/src/Integration/Contracts/Cancellation.php
index de28dffc92d..c484fd103ba 100644
--- a/packages/php/woocommerce-subscriptions-engine/src/Integration/Contracts/Cancellation.php
+++ b/packages/php/woocommerce-subscriptions-engine/src/Integration/Contracts/Cancellation.php
@@ -31,7 +31,7 @@ final class Cancellation {
/**
* Action fired after a contract is cancelled, with `( $contract )`.
*/
- const CONTRACT_CANCELLED_ACTION = 'woocommerce_subscriptions_engine_contract_cancelled';
+ public const CONTRACT_CANCELLED_ACTION = 'woocommerce_subscriptions_engine_contract_cancelled';
/**
* Contract repository.
diff --git a/packages/php/woocommerce-subscriptions-engine/src/Integration/Gateway/CapabilityRegistry.php b/packages/php/woocommerce-subscriptions-engine/src/Integration/Gateway/CapabilityRegistry.php
index 158e2d3c919..975b974b07c 100644
--- a/packages/php/woocommerce-subscriptions-engine/src/Integration/Gateway/CapabilityRegistry.php
+++ b/packages/php/woocommerce-subscriptions-engine/src/Integration/Gateway/CapabilityRegistry.php
@@ -40,7 +40,7 @@ final class CapabilityRegistry {
* gateways (where capability resolution depends on the merchant account
* routing the order) flip a capability on or off here per order.
*/
- const CAPABILITY_CHECK_FILTER = 'woocommerce_subscriptions_engine_gateway_capability_check';
+ public const CAPABILITY_CHECK_FILTER = 'woocommerce_subscriptions_engine_gateway_capability_check';
/**
* Action fired once capability resolution is stable for the request.
@@ -50,7 +50,7 @@ final class CapabilityRegistry {
* Consumers that resolve capabilities should wait for this action so the
* live-gateway step has a populated registry to read.
*/
- const CAPABILITIES_READY_ACTION = 'woocommerce_subscriptions_engine_capabilities_ready';
+ public const CAPABILITIES_READY_ACTION = 'woocommerce_subscriptions_engine_capabilities_ready';
/**
* `woocommerce_loaded` priority for the ready dispatch.
@@ -59,7 +59,7 @@ final class CapabilityRegistry {
* capability flags onto the live gateway instance, so the live-gateway step
* sees those flags by the time the ready action fires.
*/
- const READY_HOOK_PRIORITY = 20;
+ public const READY_HOOK_PRIORITY = 20;
/**
* Whether hooks have already been registered, to keep registration
diff --git a/packages/php/woocommerce-subscriptions-engine/src/Integration/Renewal/RenewalEngine.php b/packages/php/woocommerce-subscriptions-engine/src/Integration/Renewal/RenewalEngine.php
index 4652042de0c..bba17af32b2 100644
--- a/packages/php/woocommerce-subscriptions-engine/src/Integration/Renewal/RenewalEngine.php
+++ b/packages/php/woocommerce-subscriptions-engine/src/Integration/Renewal/RenewalEngine.php
@@ -50,23 +50,23 @@ final class RenewalEngine {
* Action fired after a contract is scheduled, with `( $contract, $when )`.
* Listeners observe a scheduled state, not an in-flight one.
*/
- const RENEWAL_SCHEDULED_ACTION = 'woocommerce_subscriptions_engine_renewal_scheduled';
+ public const RENEWAL_SCHEDULED_ACTION = 'woocommerce_subscriptions_engine_renewal_scheduled';
/**
* Action fired after a renewal order is created, with `( $renewal_order, $contract )`.
*/
- const RENEWAL_ORDER_CREATED_ACTION = 'woocommerce_subscriptions_engine_renewal_order_created';
+ public const RENEWAL_ORDER_CREATED_ACTION = 'woocommerce_subscriptions_engine_renewal_order_created';
/**
* Action fired after a renewal cycle is billed and the schedule advanced, with
* `( $contract, $cycle, $renewal_order )`.
*/
- const RENEWAL_BILLED_ACTION = 'woocommerce_subscriptions_engine_renewal_billed';
+ public const RENEWAL_BILLED_ACTION = 'woocommerce_subscriptions_engine_renewal_billed';
/**
* Logger source tag.
*/
- const LOG_SOURCE = 'woocommerce-subscriptions-engine';
+ protected const LOG_SOURCE = 'woocommerce-subscriptions-engine';
/**
* Repository for loading and persisting contracts, and targeted cycle access.
diff --git a/packages/php/woocommerce-subscriptions-engine/src/Integration/Renewal/RenewalScheduler.php b/packages/php/woocommerce-subscriptions-engine/src/Integration/Renewal/RenewalScheduler.php
index 068538acc0c..4233936c96e 100644
--- a/packages/php/woocommerce-subscriptions-engine/src/Integration/Renewal/RenewalScheduler.php
+++ b/packages/php/woocommerce-subscriptions-engine/src/Integration/Renewal/RenewalScheduler.php
@@ -38,13 +38,13 @@ final class RenewalScheduler {
* Public so tooling and tests can inspect or cancel pending actions via
* `as_has_scheduled_action()` and friends.
*/
- const HOOK = 'woocommerce_subscriptions_engine_process_renewal';
+ public const HOOK = 'woocommerce_subscriptions_engine_process_renewal';
/**
* Action Scheduler group - used for admin filterability (Tools ->
* Scheduled Actions) and bulk teardown.
*/
- const GROUP = 'woocommerce_subscriptions_engine';
+ public const GROUP = 'woocommerce_subscriptions_engine';
/**
* Enqueue an AS action for `$contract_id` at `$when`.
diff --git a/packages/php/woocommerce-subscriptions-engine/src/Integration/Storage/SchemaInstaller.php b/packages/php/woocommerce-subscriptions-engine/src/Integration/Storage/SchemaInstaller.php
index f9cbb84efa0..5a8f3b55e5d 100644
--- a/packages/php/woocommerce-subscriptions-engine/src/Integration/Storage/SchemaInstaller.php
+++ b/packages/php/woocommerce-subscriptions-engine/src/Integration/Storage/SchemaInstaller.php
@@ -40,24 +40,24 @@ final class SchemaInstaller {
* on an earlier schema must drop and recreate the tables (and clear VERSION_OPTION)
* to pick up such changes - in-place ALTERs and backfills arrive with the freeze.
*/
- const VERSION = '2.1.1';
+ private const VERSION = '2.1.1';
/**
* Option key tracking the installed schema version.
*/
- const VERSION_OPTION = 'wc_subscriptions_engine_db_version';
+ private const VERSION_OPTION = 'wc_subscriptions_engine_db_version';
/**
* Logical table identifiers - keys map to unprefixed table names.
*/
- const TABLE_PLAN_GROUPS = 'plan_groups';
- const TABLE_PLANS = 'plans';
- const TABLE_CONTRACTS = 'contracts';
- const TABLE_CONTRACT_ITEMS = 'contract_items';
- const TABLE_CONTRACT_ADDRESSES = 'contract_addresses';
- const TABLE_CONTRACT_META = 'contract_meta';
- const TABLE_CYCLES = 'cycles';
- const TABLE_SNAPSHOTS = 'snapshots';
+ public const TABLE_PLAN_GROUPS = 'plan_groups';
+ public const TABLE_PLANS = 'plans';
+ public const TABLE_CONTRACTS = 'contracts';
+ public const TABLE_CONTRACT_ITEMS = 'contract_items';
+ public const TABLE_CONTRACT_ADDRESSES = 'contract_addresses';
+ public const TABLE_CONTRACT_META = 'contract_meta';
+ public const TABLE_CYCLES = 'cycles';
+ public const TABLE_SNAPSHOTS = 'snapshots';
/**
* Resolve a logical identifier to its prefixed table name.
@@ -125,10 +125,28 @@ final class SchemaInstaller {
}
/**
- * Whether the installed schema version matches SchemaInstaller::VERSION.
+ * Whether the installed schema version matches SchemaInstaller::get_version().
*/
public static function is_current(): bool {
- return self::VERSION === get_option( self::VERSION_OPTION );
+ return self::get_version() === self::get_database_version();
+ }
+
+ /**
+ * Get the schema version for this class.
+ */
+ public static function get_version(): string {
+ return self::VERSION;
+ }
+
+ /**
+ * Get the installed schema version from the database.
+ */
+ public static function get_database_version(): ?string {
+ $database_version = get_option( self::VERSION_OPTION );
+ if ( ! is_string( $database_version ) ) {
+ return null;
+ }
+ return $database_version;
}
/**
diff --git a/packages/php/woocommerce-subscriptions-engine/src/Integration/Storage/SnapshotStore.php b/packages/php/woocommerce-subscriptions-engine/src/Integration/Storage/SnapshotStore.php
index 0e52a58cf5e..5c74f21719e 100644
--- a/packages/php/woocommerce-subscriptions-engine/src/Integration/Storage/SnapshotStore.php
+++ b/packages/php/woocommerce-subscriptions-engine/src/Integration/Storage/SnapshotStore.php
@@ -25,8 +25,8 @@ defined( 'ABSPATH' ) || exit;
*/
final class SnapshotStore {
- const TYPE_PLAN = 'plan';
- const TYPE_ITEMS = 'items';
+ public const TYPE_PLAN = 'plan';
+ public const TYPE_ITEMS = 'items';
/**
* Insert a typed snapshot row and return its id. The payload (the value object's
diff --git a/packages/php/woocommerce-subscriptions-engine/src/Package.php b/packages/php/woocommerce-subscriptions-engine/src/Package.php
index 236adeb6978..7d9a39e303b 100644
--- a/packages/php/woocommerce-subscriptions-engine/src/Package.php
+++ b/packages/php/woocommerce-subscriptions-engine/src/Package.php
@@ -26,7 +26,7 @@ final class Package {
/**
* Package version.
*/
- const VERSION = '0.0.1';
+ protected const VERSION = '0.0.1';
/**
* Boot the package's integration layer.
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 2c42673bffd..43b54a7bcb9 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
@@ -54,14 +54,14 @@ class SchemaInstallerTest extends EngineIntegrationTestCase {
public function test_version_option_is_set_after_install(): void {
$this->assertTrue( SchemaInstaller::is_current() );
- $this->assertSame( SchemaInstaller::VERSION, get_option( SchemaInstaller::VERSION_OPTION ) );
+ $this->assertSame( SchemaInstaller::get_version(), SchemaInstaller::get_database_version() );
}
public function test_install_is_idempotent(): void {
// Running install again must not error or change the recorded version.
SchemaInstaller::install();
- $this->assertSame( SchemaInstaller::VERSION, get_option( SchemaInstaller::VERSION_OPTION ) );
+ $this->assertSame( SchemaInstaller::get_version(), SchemaInstaller::get_database_version() );
}
public function test_unknown_table_identifier_throws(): void {