In order to have an example, assume we are dealing with some kind of  Order  class that can have a "normal" or "high" order priority status like so:

class Order
{
    const PRIORITY_NORMAL = 'normal';
    const PRIORITY_HIGH = 'high';

    // ...
}

A method accepting one of these constants usually looks like this:

class Order
{
    /**
     * Create a new order instance
     */ 
    public static function place(ItemList $items, string $priority): self
    { ... }
}

Problems with the scalar-typed approach

Although there are two special string values defined as the two constant values in the above example, clients of this method may pass in literal string values (a plain  'normal'  or  'high' ) directly. For example, this may happen if they are taking the value directly from request parameters, which obviously is a risky practice.

In general, as the  Order::place()  method above accepts a string, nothing prevents you from passing in arbitrary string values.

Another possible mistake is that parameter order is messed up and arbitrary values are passed in places where constant values are expected.

To make your software design robust, defensive and fail-fast, in the above example you should check that the  $priority is indeed one of the available priorities.

As you can imagine, this can quickly get impractical when the constant values are used a lot and passed on between methods, as each method would need to check them again and again. Adding a new constant value would also require you to update all those checks.

When working with  int  constants ( 0, 1, 2, ... ), the mistake of messing up the parameter ordering might be so subtle that you cannot catch it by checking the parameter value.

Constant value objects

So, here is another way how we can design this. I will be building upon two OOD concepts:

  • Value objects describes the approach of modelling plain values as objects.

  • Often, it is benefical as well to design such objects as being immutable.

You can find valuable chapters on both ideas in Eric Evans' book "Domain Driven Design".

Bringing both together, we can come up with an  OrderPriorty  class as follows. Let's call this a constant value class:

class OrderPriority
{
    private const NORMAL = 'normal';
    private const HIGH = 'high';

    private $priority;

    public static function NORMAL(): self
    {
        return new self(self::NORMAL);
    }

    public static function HIGH(): self
    {
        return new self(self::HIGH);
    }

    private function __construct($priority)
    {
        $this->priority = $priority;
    }
}

As the  OrderPriority  constructor is private, the only way of creating  OrderPriority  instances is through the static construction methods  OrderPriority::NORMAL()  and  OrderPriority::HIGH() .

We can now write our method as

class Order
{
    public static function place(ItemList $items, OrderPriority $priority): self
    { ... }
}

Since PHP is type-checking the  $priority  parameter, you can now be sure you're dealing with some kind of  OrderPriority . Without further checks or safeguards, the  place(...)  method can now be sure that  $priority  is one of the valid priority levels.

There is one required change for clients passing in one of these values: Instead of writing  Order::PRIORITY_NORMAL  or  Order::PRIORITY_HIGH , we will now use  OrderPriority::NORMAL()  or  OrderPriority::HIGH() . This is necessary to create the instances of our constant value class.

Note that I chose to name these methods all caps to resemble the convention of constant identifier naming.

Checking for constant values

When checking a parameter like  $priority  for one of the constant values, we can write the check as  $priority == OrderPriority::HIGH() . This already works for the  ==  loose comparison since the values of both constant class instances – the one in  $priority  and the one created on the fly – are the same.

As a cautious programmer, however, you're probably using the strict comparison operator  ===  whenever possible. With a constant value class as shown above, this will not work, since this operator also checks for object identity and we're using two different object instances.

So, let's fix that.

Using flyweights

The Flyweight is a structural pattern from the classic "Gang of Four" book. Part of the pattern is the idea to re-use object instances when they don't differ in state, which is perfect for our immutable constant values.

Also described in the pattern is the need to have some kind of factory to obtain flyweight object instances without creating them again and again.

So, let's add a new method to our class that takes care of this. We'll call it  constant()  since it returns the constant value object instance for a given value.

This method will be  private  as well, so clients still have to use the construction methods ( OrderPriority::NORMAL()  and  OrderPriority::HIGH() ) as before.

class OrderPriority
{
    private const NORMAL = 'normal';
    private const HIGH = 'high';

    private static $instances = [];

    private $priority;

    public static function NORMAL(): self
    {
        return self::constant(self::NORMAL);
    }

    public static function HIGH(): self
    {
        return self::constant(self::HIGH);
    }

    private static function constant($value)
    {
        return self::$instances[$value] ?? self::$instances[$value] = new self($value);
    }

    private function __construct($priority)
    {
        $this->priority = $priority;
    }
}

Now, for every different constant value, only one single object instance will be created. The same instance will be returned for every call to methods like  OrderPriority::NORMAL() . And since there is only one instance per value, the  === strict comparison now works.

Constant definitions in interfaces

One tiny drawback of the approach shown above is that you cannot put such constant definitions into interfaces. The approach is based on object instances, which are a runtime concept. Interfaces, however, do not contain implementation code and thus cannot provide the necessary methods.

You can, of course, have a "constant value class" to provide the allowed values and then have your interface use it, for example as part of method signatures.

A nice trait

In our current draft of the  OrderPriority  class, everything besides the actual two constant values and the construction methods is pretty much boilerplate and would be the same for every "constant value class". So, let's move that to a reusable trait:

trait ConstantClassTrait
{
    private static $instances = [];

    private $value;

    final private function __construct($value)
    {
        $this->value = $value;
    }

    private static function constant($value)
    {
        return self::$instances[$value] ?? self::$instances[$value] = new self($value);
    }
}

With this,  OrderPriority  becomes:

class OrderPriority
{
    use ConstantClassTrait;

    private const NORMAL = 'normal';
    private const HIGH = 'high';

    public static function NORMAL(): self
    {
        return self::constant(self::NORMAL);
    }

    public static function HIGH(): self
    {
        return self::constant(self::HIGH);
    }
}

Casting to and from strings

Sooner or later, you might find yourself in a situation where you need to render an HTML form with something like a select list or radio button for an  OrderPriority . Or, you need to accept the  OrderPriorty  from a form or the command line as input.

Since we're now dealing with the  OrderPriority  class, we now have a perfect place to keep such additional methods:

class OrderPriority
{
    // Omitted trait and construction methods (like before)
    
    public static function fromString(string $value): self
    {
        if ($value !== self::NORMAL && $value !== self::HIGH) {
            throw new InvalidArgumentException();
        }
        
        return self::constant($value);
    }
    
    public function __toString()
    {
        return $this->value;
    }
}

You can now easily cast an  OrderPriority  instance to a string, for example when using it as the  <input value="..."> .

Now assume your task is to write the code that accepts a new  Order  from a web request or the command line. At your UI/code boundary, the order priority will clearly be available as a string. The  Order::create()  method, however, needs an  OrderPriority  instance.

Even somebody new to your project will quickly figure out that there are only a few ways to actually create  OrderPriority instances. They will probably find the  OrderPriority::fromString()  method and write code like this:

    // Somehow obtain $itemList
    $order = Order::place($itemList, OrderPriority::fromString($_REQUEST['priority']));
    // ... 

The bottom line is that the way we have written our  Order  and  OrderPriority  classes here makes it almost impossible to use them in a wrong way or to forget checking our inputs.

Even more value object perks

For a moment, assume your business comes up with a requirement to add a new  expedited  priority class. This service level is above the  normal  level, but does not yet make a  high  priority order.

I'll leave it to you as an exercise to add the  EXPEDITED  constant definition, a creation method and to update the  fromString()  method in our  OrderPriority  class.

What is more interesting is that we can now add an additional method to compare two priorities:

class OrderPriority 
{
    // ... as before

    public function atLeast(OrderPriority $other): bool
    {
        // Return true if $this->value is a priority equal to or above $other->value.
    }
}

With this, our new business rule might be something along the lines of

class ShippingFeeCalculator 
{
    public function computeShippingFees(Order $order): Money
    {
        // ...

        if ($order->getPriority()->atLeast(OrderPriority::EXPEDITED()) {
            // ... do what business requested
        }
    }
}

Being a value object, the  OrderPriority  class is a nice place to keep such additional comparison and computation methods which make it even more expressive.

Summary

This article described how simple class constants can be replaced with instances of a "constant value class". Methods that accept such constants can use type hints to make sure only valid constant values can be passed, without having to perform any additional checks. By reusing object instances, comparisons of constant values can also be written using the  ===  strict syntax check, with only minimal changes to code being necessary.

In addition to that, the constant value objects are a great place for keeping a particular kind of business logic.

Was this helpful for you? Let @webfactory or @mpdude_de know!