Safely coding with constants

In PHP there is a tendency to assume that the code we are working on is the only code that is running. The global and transactional nature of the language’s past has made this easy to do.

This tendency naturally leads us to use global state, and while there does seem to be a resistance to using global variables, I’ve only seen increased usage of constants created using define(). I would like to suggest that constants created in this way are actually just global variables by another name. Worse, perhaps, they are global variables whose values cannot be changed during runtime. If that last bit seems like an advantage rather than a disadvantage, read on.

First let’s briefly cover the risk of global state. As I’ve seen in my experience over and over again, the nature of code is to proliferate over time and when code starts to have complexity, global state becomes a liability. Anything the programmer cannot see directly when coding is something they have to keep in their head. And there is only so much we can keep in our heads, especially when large periods of time pass and when many different people are involved. A local variable (local state) is much easier to reason about, because it’s “right there” in front of us and less likely to be affected by things “somewhere else”.

There’s more to it than just making code more readable, though. Another way we learn about and protect code from bugs is by testing, both automated and manual. The nature of testing is to try some code, then change the state and try it again to see what the effects are. Remember how I said that constants created by define() cannot change? Let’s look at an example,

function getGroceries(): string {
  if (defined('LIKES_PEAS')) {
    return purchasePeas();
  }
  return purchaseCarrots();
}
// ...
echo "I will buy some " . getGroceries();

How would we write a test case for this function? The risk of having a condition on a defined constant is that apart from manual testing it is impossible to test it. Constants by their very nature cannot change, and defined constants are global in scope so there is nowhere they will not be. Once we define it, we cannot change it during the same runtime and most PHP test runners execute all their test cases in the same runtime.

That’s not to say that all constants are problematic. It’s more an issue of how we use them. Essentially, if we just stop writing if (defined())… in our code, the problem goes away.

But it’s not that easy, is it? The argument that I’ve heard most often to keep checking for defined constants is that they already exist in most large projects and so we have no choice. But this is not quite true.

The values stored in these constants are not themselves constant, and can be injected into the code that uses them. Here’s that previous example, written a different way,

function getGroceries(bool $likesPeas): string {
  if ($likesPeas) {
    return purchasePeas();
  }
  return purchaseCarrots();
}
// ...
echo "I will buy some " . getGroceries(defined('LIKES_PEAS'));

Suddenly it’s very easy to test the function! We just change its input. This is called dependency injection.

This technique is a time honored way to isolate dependence from logic. It may be less convenient because we must admit that when we need to check a constant we are actually adding a new dependency. However, noticing this gives us information, not penance. It may force us to find other ways of structuring our logic to avoid those dependencies altogether.

There is no doubt in my mind that the isolation of dependencies is critical to write code that is easy to reason about, and that doing so will reduce bugs, stress, and confusion as the code grows. When we recognize that constants created using define() are just another dependency, we can use them sparingly and isolate their usage in the same way we would for any other global or remote state which we cannot control.

(Photo by Simon Matzinger on Unsplash)

Author: Payton Swick

Vegan. Digital craftsman. Tea explorer. Avid learner of things. Writes code @automattic.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

w

Connecting to %s