fbpx

Better, smarter enums in PHP (part 1)

While developing our application Nuvola, we often find ourselves having to rely on some sort of property representing some state of an entity. A simplified example would be the representation of what a student state in a given day is. They could be attending school, they could be absent, they could be late, they could leave early, or they could both be late and leave early. In order to represent this, we used to have a string property, something like this:

This is the very basic scenario, but there are a few concerns that we need to address:

  • Every possibile status must have some sort of human-readable description along with the basic internal representation.
  • Working with Symfony, we need to find a way of having proper form and validation support.
  • The entity must know how to handle the various states, so the type’s concern becomes the entity’s concern.
  • Unless we use a list of allowed values whenever we try and change it, there is no inherent validation, as it’s just a string.

Let’s break it down.

Every possibile status must have some sort of human-readable description along with the basic internal representation

Well, that’s easy. Let’s just create an array where the key is the internal representation and the value is a readable description:

Ok, that’s good. One issue with this approach, though, is that it doesn’t scale that well. This is a very simple example, but imagine this happening with dozens of allowed types, and multiple properties in the entity. The boilerplate quickly becomes quite noisy, and the actual domain code is buried in there.

Working with Symfony, we need to find a way of having proper form and validation support

While we are trying to move away from annotation-based validation in favor of having entities which are always valid (where validation occurs whenever values are changed), Nuvola is quite vast and there is a lot of code that needs to be updated without having it go through a whole refactoring process. For this reason we need to work with @Assert-style annotations from Symfony, and our student status must have a way of inferring which types are allowed. The @Assert\Choice validator accepts a list of allowed values, but we don’t have that defined anywhere besides as keys of the TYPES constant. A quick and dirty solution is then to have a callback to a static function which does this:

That’s quite ugly if you ask me, especially because that function has no domain purpose and only exists to allow integration with the Symfony validator.

Along with that, we also need to find a way of having forms properly display a list of allowed options to be chosen from a multiple choice input field. Again, in order to do that we have to “bend” our needs in order for things to work properly, therefore we make the TYPES constant public and we use it in the form type:

That looks quite ugly to me. What was a simple need in the beginning has turned out to be a complete breakage of encapsulation.

The entity must know how to handle the various states, so the type’s concern becomes the entity’s concern

In our example, a student will need a parent’s note in order to explain why they weren’t at school. But of course, that is not required if the type is StudentDailyStatus::TYPE_ATTENDING, which means the entity will now need to understand that one of the constants has a special meaning.

Unless we use a list of allowed values whenever we try and change it, there is no inherent validation, as it’s just a string

In order to comply with the form’s setter inner workings, we need to be have a setType method which takes any (nullable) string, therefore we’ll have to expose the actual type constants in order to have them used from the outside. Encapsulation is over, data is everywhere. Ouch.

A better way?

If we go back to the initial situation, our $type is what is referred to as an enumerated type, commonly known as enum, which (simply put) is data which can assume only one of a predetermined set of values. PHP does not natively support them (sadly) but there are some solutions available. My problem with existing libraries is that they focus solely on the enum representation (with no native solution for having some sort of string representation which does not break encapsulation), they rely a lot on magic in order to have named constructors (which you have to annotate to have IDE support) and even though they may support it, they are not designed for having enum objects have further knowledge besides the simple value they represent.

With this in mind, I started working on a custom solution. I wanted something that had no magic at all, something which natively supported some form of text representation that did not break encapsulation, and something which in its design would take into account the possibility of being more than a simple state object. In part two I will show what we ended up with, and although it’s quite simple, I believe to be a rather nice solution. See you soon!

Matteo Moretti
Matteo Moretti
Articoli: 30

Lascia una risposta

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *

Questo sito usa Akismet per ridurre lo spam. Scopri come i tuoi dati vengono elaborati.