Make Impossible States Impossible (in Drupal Theming)
Passing intent to Twig instead of implementation details helps prevent breaking out of the design system
The phrase “Make Impossible States Impossible” is often used in typed languages such as Elm and Haskell. Since these are typed languages, the typical approach is to first “Model the problem” and then implement the solution, which usually feels natural after modeling. The concept behind “Make Impossible States Impossible” is to design your logic so that it prevents the possibility of ending up in an incorrect or invalid state.
Imagine this function:
function isAllowedAccess(bool $is_trial, bool $is_paid) : bool
This function nearly makes sense, but it has a flaw in its design.
is_trial | is_paid | Meaning | Valid? |
---|---|---|---|
false | false | No subscription | โ |
true | false | Trial user | โ |
false | true | Paid subscriber | โ |
true | true | Trial and Paid ๐ค | โ |
In the example above, having both ’trial’ and ‘paid’ set to TRUE is an impossible situation. Currently, the isAllowedAccess
function does not prevent us from reaching this impossible state.
Fixing it with PHP’s Enum is easy.
enum SubscriptionState: string {
case None = 'none';
case Trial = 'trial';
case Paid = 'paid';
}
function isAllowedAccess(SubscriptionState $state): bool {
return match ($state) {
SubscriptionState::None => FALSE,
SubscriptionState::Trial => TRUE
SubscriptionState::Paid => TRUE,
};
}
Theming with Type Safety
When it comes to theming, type safety should ensure we adhere strictly to the design system’s definitions. For example, if a designer specifies border widths of 1px and 2px, we should prevent an element from having a 3px border. However, designers can also make mistakes. If a developer sees a Figma design with a 3px border, they would likely recognize it as a mistake just by examining the related Enum.
To provide this safety, we are using the Pluggable Entity View Builder (PEVB) module in combination with the concept of “Theme Traits.” Watch the video if you’re not familiar with it.
Consider a typical scenario: We wanted to display an image with a border. We might have used a flexible theme call like this:
trait ElementWrapThemeTrait {
protected function wrapBorder(array $element, int $border_width, string $border_color): array {
return [
'#theme' => 'server_theme_border',
'#items' => $element,
'#border_width' => $border_width,
'#border_color' => $border_color,
];
}
}
I see border width and color as “just in case” parameters, which can create endless permutations. Allowing all combinations, like 1px red, 2px blue, or 4px transparent, can cause problems over time. While these small variations are manageable individually, they can add up quickly. Most of these combinations are not part of the design system and ideally shouldn’t be used. They exist only because the code permits them. To prevent this, we can implement Enums to clearly specify the intent, helping developers avoid unintentional deviations from the design system.
Consider this approach instead, where we pass the border type (intention), rather than the border width and color (implementation details).
enum BorderTypeEnum: string {
case HeroImage = 'hero-image';
case Paragraph = 'paragraph';
case ProfileImage = 'profile';
}
protected function wrapBorder(array $element, BorderTypeEnum $border_type): array {
return [
'#theme' => 'server_theme_border',
'#items' => $element,
'#border_type' => $border_type->value,
];
}
Then the twig file would set the border based on the type.
{% set border_classes = {
'hero-image': 'border-4 border-gray-500',
'paragraph': 'border-2 border-amber-400',
'profile': 'border border-blue-500'
} %}
<div class="{{ border_classes[border_type] }}">
{{ items }}
</div>
Side Step to the Wonders of Truly Typed Language
This section isn’t about theme-based type safety, but I would regret not telling you how languages like Elm and Haskell significantly help prevent impossible states.
Let’s take this example, where we want to get the discount amount for a user.
function getDiscountAmount(bool $has_coupon, int $percent = 0): int {
return $has_coupon ? $percent : 0;
}
has_coupon | percent | Meaning | Valid? |
---|---|---|---|
false | 0 | No discount | โ |
false | 15 | ๐คจ Discount % w/o coupon | โ |
true | 0 | ๐ค Coupon with 0% | โ |
true | 15 | 15% coupon applied | โ |
Once more, there are invalid states. How would we solve this in PHP? We could implement solutions that throw an exception if the states are incorrect, but that wouldn’t be type safety; it would be more like guarding or babysitting the code. Here’s how we would define it in Elm:
type Discount
= NoDiscount
| Coupon Int
The key point is that the Coupon type “wraps” the Int
value. When pattern matching, this allows us to retrieve the percent. It’s impossible to have a Coupon
without an associated Int
- the compiler enforces this restriction.
getDiscountAmount : Discount -> Int
getDiscountAmount discount =
case discount of
None ->
0
Coupon percent ->
percent
Back to Loosely Typed Languages
By shifting our mindset from implementation details to intent, we not only reduce the chances of design drift but also make our code easier to read and maintain. Typed languages can enforce this by design, but even in a loosely typed environment like PHP + Twig, we can borrow the same philosophy. With Enums and clear theming contracts, we make impossible states impossible - and that’s the kind of discipline that keeps a design system consistent and resilient over time.


Amitai Burstein
@amitaibu