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_trialis_paidMeaningValid?
falsefalseNo subscriptionโœ…
truefalseTrial userโœ…
falsetruePaid subscriberโœ…
truetrueTrial 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_couponpercentMeaningValid?
false0No discountโœ…
false15๐Ÿคจ Discount % w/o couponโŒ
true0๐Ÿค” Coupon with 0%โŒ
true1515% 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.

Make Impossible States Impossible (in Drupal Theming)
Amitai Burstein

Amitai Burstein

@amitaibu
Drupal-planet