Skip to main content

PHP Enumerations

Updated 6 min readMarkdown

Enumerations were added to PHP in PHP 8.1 so I decided to give them a try, and I quite like them.

Here’s the most basic form

enum MaritalStatus {
  case Single;
  case Married;
  case Divorced;
  case Separated;
  case Widowed;
}

So now you can do

$employeeMaritalStatus = MaritalStatus::Married;

and

if ($employeeMaritalStatus == MaritalStatus::Married) {
  echo 'Employee is married';
}

You can’t do

echo $employeeMaritalStatus;

because an enum isn’t a string, and it’s not automatically convertible into a string, but you can do

echo $employeeMaritalStatus->name;

which will get you a string value like “Married”, “Single”, etc.

The first thing you might want to do is produce a human-readable display version of that, e.g. “Divorced/Civil Partnership Dissolved” instead of “Divorced”.

That’s called a backed enumeration, so it becomes this

enum MaritalStatus: string {
  case Single = 'Single';
  case Married = 'Married/Civil Partner';
  case Divorced = 'Divorced/Civil Partnership Dissolved';
  case Separated = 'Separated';
  case Widowed = 'Widowed/Surviving Civil Partner';
}

You still can’t do

echo $employeeMaritalStatus;

But you can now do

echo $employeeMaritalStatus->name;

which will get you “Married”, or

echo $employeeMaritalStatus->value;

which gets you “Married/Civil Partner”;

The next thing you might want to do is convert a string to an enum value. Say if an API has returned a string value “Married”, you want to be able to convert that to a proper enum in your PHP object.

So we add a couple of static methods

enum MaritalStatus: string {
  case Single = 'Single';
  case Married = 'Married/Civil Partner';
  case Divorced = 'Divorced/Civil Partnership Dissolved';
  case Separated = 'Separated';
  case Widowed = 'Widowed/Surviving Civil Partner';

  public static function fromString(string $name): MaritalStatus {
    try {
      return self::{$name};
    } catch (\Error) {
      throw new \ValueError('"'.$name.'" is not a valid value for '.self::class);
    }
  }

  public static function fromNullableString(?string $name): ?MaritalStatus {
    return $name === null ? null : self::fromString($name);
  }
}

So now you can do

$myString = 'Married';
$employeeMaritalStatus = MaritalStatus::fromString($myString);

The next problem we need to solve is a problem I ran into when serializing objects containing the enum.

Say I've got a variable called $employee that contains a property $maritalStatus that I want to pass into an API

I do

$json = json_encode($employee);

to convert it to JSON to send to the API. If the marital status is “Single” or “Separated” it works, but if I send a “Married” one it falls over. The API complains that that’s not a valid MaritalStatus.

What’s happening is that by default, when you do json_encode on a backed enum, it converts it to the string value, not the enum value. So for a Single one, the API is receiving this

{
  "employeeId": 2645,
  "forename": "Jane",
  "surname": "Jones",
  "maritalStatus": "Single"
}

That’s fine because for a Single one, the enum and its string equivalent are identical, but for a Married one we’re sending

{
  "employeeId": 2645,
  "forename": "Jane",
  "surname": "Jones",
  "maritalStatus": "Married/Civil Partner"
}

The API doesn’t recognise “Married/Civil Partner” as a valid status.

So we have to control the serialisation behaviour ourselves. You implement the JsonSerializable interface.

enum MaritalStatus: string implements \JsonSerializable {
  case Single = 'Single';
  case Married = 'Married/Civil Partner';
  case Divorced = 'Divorced/Civil Partnership Dissolved';
  case Separated = 'Separated';
  case Widowed = 'Widowed/Surviving Civil Partner';

  public static function fromString(string $name): MaritalStatus {
    try {
      return self::{$name};
    } catch (\Error) {
      throw new \ValueError('"'.$name.'" is not a valid value for '.self::class);
    }
  }

  public static function fromNullableString(?string $name): ?MaritalStatus {
    return $name === null ? null : self::fromString($name);
  }

  public function jsonSerialize(): string {
    return $this->name;
  }
}

So now a Married one gets converted to JSON as “Married” instead of “Married/Civil Partner”. The API receives this

{
  "employeeId": 2645,
  "forename": "Jane",
  "surname": "Jones"
  "maritalStatus": "Married"
}

and it’s all happy.

Then to take it a step further. The fromString() and fromNullableString() both return a value of MaritalStatus, i.e. of the type that we’re currently in. Where that’s the case, you can have a return type of self, i.e.

enum MaritalStatus: string implements \JsonSerializable {
  case Single = 'Single';
  case Married = 'Married/Civil Partner';
  case Divorced = 'Divorced/Civil Partnership Dissolved';
  case Separated = 'Separated';
  case Widowed = 'Widowed/Surviving Civil Partner';

  public static function fromString(string $name): self {
    try {
      return self::{$name};
    } catch (\Error) {
      throw new \ValueError('"'.$name.'" is not a valid value for '.self::class);
    }
  }

  public static function fromNullableString(?string $name): ?self {
    return $name === null ? null : self::fromString($name);
  }

  public function jsonSerialize(): string {
    return $this->name;
  }
}

So now those three methods don’t have anything in them that refers to a specific enum type. They will work for any enum type. So you’d think that’s an obvious candidate for an abstract base class, but you can’t do inheritance on enums.

So there’s a thing called a Trait in PHP which I’d heard of but never actually looked into. It’s a bit like a quick and dirty base class. Or like one of those classes that’s got nothing but static methods, except the methods don’t have to be static.

trait Multiplying {
  public function multiplyBy2() {
    return $this->myVal * 2;
  }

  public function multiplyBy4() {
    return $this->myVal * 4;
  }
}

So once you’ve defined it, any class that uses that trait gets those methods.

class MyClass {
  use Multiplying;

  public int $myVal;
}

$myObject = new MyClass();
$myObject->myVal = 5;

echo $myObject->multiplyBy2();
echo '<br>';
echo $myObject->multiplyBy4();

That’s a rubbish example, but you get the idea.

So if we define a trait like this

trait CommonEnumMethods {
  public static function fromString(string $name): self {
    try {
      return self::{$name};
    } catch (\Error $e) {
      throw new \ValueError('"'.$name.'" is not a valid value for '.self::class);
    }
  }

  public static function fromNullableString(?string $name): ?self {
    return $name === null ? null : self::fromString($name);
  }

  public function jsonSerialize(): string {
    return $this->name;
  }
}

Our actual enum can look like this

enum MaritalStatus: string implements \JsonSerializable {
  use CommonEnumMethods;

  case Single = 'Single';
  case Married = 'Married/Civil Partner';
  case Divorced = 'Divorced/Civil Partnership Dissolved';
  case Separated = 'Separated';
  case Widowed = 'Widowed/Surviving Civil Partner';
}

And it gets all the methods from the trait. So now all new enums that we add can just use the trait to get all the functionality that we need.

No comments yet. Be the first to comment.

More from this blog