Understand the Bounds Enum in Period v5

The Problem

Whenever I have to do some reporting I always end up having a lengthy conversation with the people who will use the reports. Because more than once, we disagree on how to calculate the reports. The disagreement stem from the fact that a report is a calculation/selection of things over an specified interval which requires a starting endpoint and an ending endpoint. but what people always fail to realise is that a report also needs some bounds in other words we as developers need to know how to handle the report bounds. Should they be included or excluded of the report. In a previous post I have explain that I prefer to always include the start endpoint and exclude the end endpoint, to me it is the most reasonable default behaviour to chose.

The solution

However as in everything there’s no single right response and context matter. Meaning that depending on the context the type of bounds chosen can change and thus affect the interval and subsequently the reports. In order to avoid or at least to reduce the changes, we need a way to express those bounds and to make sure our code can be made aware of them in an efficient and predicable way.

The initial implementation

Luckily for us with the addition of Enumerations or Enums in PHP8.1 we can easily do that as seen in the example below:

The terms lower and upper derived from how interval are expressed in mathematics.

The reason we choose Enumeration over a list of scalar values is because Enumeration represents a list of finite states. In our examples bound intervals only exist in four different types. By avoiding string or integer or boolean you get a concise, readable and less error prone structure to use. You could easily make a typo with integer or with string and it would go unnoticed whilst with an Enum the PHP engine feedback loop will warn your directly of your mistake.

With the defined Enums we have listed all possible bounds. When used in combination of any type of interval you can be sure that your are covering and expressing all types of bounded intervals.

For instance let’s create an interval value objects which deals with integer ranges.

We will be instantiating and using the immutable value object as follow

Adding some behaviours

Now that we have the Bounds Enum at our disposal we can attach some behaviour to it, to improve bounds interaction.

Here’s an example to illustrate, let’s say we have two NumberInterval , $intervalOne and $intervalTwo and we need to know if both intervals meets (ie: if they are touching each other). In order to satisfy those requirements, the following rules needs to be checked:

  • $intervalOne upper number must be equal to $intervalTwo lower number
  • $intervalOne upper bound must be inclusive
  • $intervalTow lower bound must be inclusive

Or

  • $intervalTwo upper number must be equal to $intervalOne lower number
  • $intervalTwo upper bound must be inclusive
  • $intervalOne lower bound must be inclusive

Let’s expose the inclusion rules on the Bounds enum.

And add the missing relation methods to the NumberInterval class.

We now have a more powerful public API which is easier to read and to predict.

As you can see the NumberInterval acts like an orchestrator who uses each of its inner property to handle basic operations to expose more powerful features to the end users.

What about representation ?

As you may have notice our Enum is not backed. Which means that no string representation is currently associated with it. But then again, this is not a limitation as we can work around the missing feature. Once again, the Bounds Enumeration will be responsible for the formatting while The NumberInterval will be responsible for calling the right methods on each of its properties to generate the expected string representation.

The Iso 80000 is the standard to represents style guide for mathematical and scientific notation

And we will add to NumberInterval a toString conversion method

Now we can represents the interval as a string.

If, for instance, you are using PostgreSQL you can use this string representation as part of your SQL query to select some range in your persistence layer. The same could be achieved for MySQL/MariaDB
with a different method, of course.

Final thoughts

While the Bounds feature could have been implemented in previous version of PHP, by using PHP8+ feature like constructor promotion, Enum, readonly properties and the match construct, to name but a few, makes the implementation easier to reason with and improved its maintenance costs. And last but not least it makes for a better DX.

TL;DR

If you liked the presented Bounds Enum you can find an improved and more complete version in the codebase of the League\Period v5 package which will be released soon. In the previous version of League\Period the bounds when constants attached to the Period class whilst in the new version they are completely separated of the class and upgraded to a proper structure to better reason with. As the Enumeration object is now fully decoupled from the rest of the package you can re-use it in your next PHP8.1+ project.

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.