Integrating with large enterprise solutions like SAP or Salesforce led me to lean heavily on Enums. This wasn't just a stylistic choice; it was a necessity when I had to manage a high volume of master data. Creating dozens of database tables for static values didn't feel scalable and optimal. Also have an API call or Hana DB query everywhere didn't seem performant. Actually it was a suggestion from the junior developer, and we adapted it.
Things were working smoothly until we encountered a common lifecycle challenge: a specific data value needed to be retired and replaced with a new one. In a live application with thousands of historical records, simply deleting an Enum entry is catastrophic. Tbh, I removed the old case, it immediately broke view pages because the application could no longer "map" the archived data.
The Scenario
Imagine a subscription platform with three tiers: Bronze, Silver, and Gold. As the business scales, the company decides to retire the "Bronze" tier and introduce a "Diamond" package.
While adding the new "Diamond" case is easy, deleting "Bronze" raises critical risks:
-
Historical Data: Existing invoices or user records referencing "Bronze" will trigger errors when loaded.
-
Asynchronous Tasks: Queued jobs or background processes still holding the "Bronze" value will fail.
-
UI Integrity: Dashboards attempting to render old data will crash.
The Solution: The "Parallel Change" Pattern
Instead of a destructive change, we can implement a bridge. By adding an isActive() method directly into the Enum, we create a layer of backward compatibility.
enum Package: string
{
case Bronze = 'bronze'; // Deprecated
case Silver = 'silver';
case Gold = 'gold';
case Diamond = 'diamond'; // New
public function isActive(): bool
{
return match($this) {
self::Bronze => false,
default => true,
};
}
}
And when we need to present the options in the UI, we can use a helper method to filter only the "active" packages. This way system will still be able to read "inactive" historical data.
public static function activeAll(): array
{
return array_reduce(self::cases(), function ($carry, $case) {
if ($case->isActive()) {
$carry[$case->value] = $case->description();
}
return $carry;
}, []);
}
That's it! We have added the new subscription and removed the old one, without breaking the system.