PHP 8.1 enums provide a first-class alternative to the class-constant pattern that WordPress plugins have used for decades. Enums are type-safe, support methods and interfaces, and integrate cleanly with WordPress’s hooks API, sanitize_*() family, and database layers — while making intent far more explicit in code reviews and static analysis.
Problem: WordPress plugins use class constants to represent fixed sets of values — post statuses, payment states, notification types — but plain string constants are not type-safe, can be compared incorrectly, and are not easily serialisable to REST API responses.
Solution: Replace class constants with PHP 8.1 Enums — backed enums (enum Status: string { case Active = 'active'; }) are type-safe, serialisable to their backing value, and can implement interfaces. Use Status::from() for trusted input and Status::tryFrom() for user input. Enums work with match expressions for exhaustive handling.
The examples below show a pure enum, a backed enum for post status, using enums in WordPress hook callbacks and meta sanitisation, and a helper that converts a database string value back into an enum case safely.
<?php
// ── 1. Pure enum for payment methods ─────────────────────────────────────
enum PaymentMethod {
case CreditCard;
case PayPal;
case BankTransfer;
case Crypto;
public function label(): string {
return match( $this ) {
self::CreditCard => 'Credit / Debit Card',
self::PayPal => 'PayPal',
self::BankTransfer => 'Bank Transfer',
self::Crypto => 'Cryptocurrency',
};
}
}
// ── 2. Backed string enum for WooCommerce order status ───────────────────
enum OrderStatus: string {
case Pending = 'pending';
case Processing = 'processing';
case Completed = 'completed';
case Refunded = 'refunded';
case Cancelled = 'cancelled';
/** Safely convert a DB string to an enum; returns null on invalid value */
public static function fromDb( string $value ): ?self {
return self::tryFrom( $value );
}
public function isTerminal(): bool {
return match( $this ) {
self::Completed, self::Refunded, self::Cancelled => true,
default => false,
};
}
}
// ── 3. Type-safe hook callback ────────────────────────────────────────────
add_action( 'woocommerce_order_status_changed',
function ( int $order_id, string $from_str, string $to_str ) {
$to = OrderStatus::fromDb( $to_str );
if ( null === $to ) {
return; // unknown status — skip silently
}
if ( $to->isTerminal() ) {
// Send confirmation email, update inventory, etc.
do_action( 'my_plugin_order_finalised', $order_id, $to );
}
}, 10, 3
);
// ── 4. Sanitise a POST value into an enum ─────────────────────────────────
function sanitize_order_status( mixed $raw ): OrderStatus {
$string = is_string( $raw ) ? $raw : '';
return OrderStatus::fromDb( $string ) ?? OrderStatus::Pending;
}
// ── 5. Store and retrieve enum values as post meta ─────────────────────────
function save_order_status_meta( int $post_id, OrderStatus $status ): void {
update_post_meta( $post_id, '_order_status', $status->value );
}
function get_order_status_meta( int $post_id ): OrderStatus {
$raw = (string) get_post_meta( $post_id, '_order_status', true );
return OrderStatus::fromDb( $raw ) ?? OrderStatus::Pending;
}
NOTE: Use tryFrom() (returns null on invalid input) rather than from() (throws ValueError) whenever you are handling database or user-supplied strings — it avoids fatal errors from stale or unexpected values stored in older data.