I\'ve heard many times on this forum that using global variable is a dead sin and implementing singleton is a crime.
It just came to my mind that old good constants
The primary reason why global variables are considered bad practice is because they can be modified in one part of a system and used in another part, with no direct link between those two pieces of code.
This leads to potential bugs because it is possible to write code that uses a global variable without knowing (or considering) all the places where it is used and ways in which it could be changed. Or vice-versa, write code that makes a change to a global, without realising the impact that change may have in other unrelated parts of your code.
Constants do not share this issue, because they are... well, constant. Once they're defined, they can't be changed, and thus the issued described in the above paragraph cannot occur.
Therefore, they are fine to use globally.
That said, I have seen some poorly written PHP code that uses define
to create constants, but declares the constants differently in different circumstances. This is a mis-use of constants: A constant should be an absolutely fixed value; it should only ever be a single value. If you have something that could potentially be different values on different runs through the program, then it shouldn't be defined as a constant. That sort of thing should indeed be a variable, and then should follow the same rules as other variables.
This sort of mis-use can only happen in a scripted language like PHP; it couldn't happen in a compiled language because you can only define a constant once, in one place and to a fixed value.
Generally speaking yes, avoid constants. They introduce coupling from the consumers to the global scope. That is, the consumers rely on something outside. This is unobvious, e.g.
class Foo
{
public function doSomething()
{
if (ENV === ENV_DEV) {
// do something this way
} else {
// do something that way
}
}
}
Without knowing the internals of doSomething
, you will not know there is a dependency on the global scope having that constant. So in addition of making your code somewhat harder to understand, you are also limiting how it can be reused.
The above is also true for constants that have only one value, e.g.
public function log($message)
{
fwrite(LOGFILE, $message);
}
Here the constant would point to a file resource defined somewhere outside as
define('LOGFILE', fopen('/path/to/logfile'));
And this is just as unobvious as using ENV
. It's a dependency that requires something outside the class to exist. And I have to know that in order to work with that object. Since the class using this constant hides this detail, I might try to log something without making sure the constant exists and then I'd wonder why it doesn't work. It doesn't even have to be a resource, LOGFILE
could simply contain the path as a string. Same result.
Relying on global constants in your consumers will also require you to setup global state in your unit-tests. This is something you generally want to avoid, even if the constants are fixed value, because the point of the unit-test is to test the unit in isolation and having to put the environment into a certain state hinders this.
Moreover, using global constants always poses the threat of constants of different libraries clashing. As a rule of thumb, don't put anything into the global scope. Use namespaces to cluster constants if you have to use them.
However, note that namespaced constants still have the same issues regarding coupling, so do class constants. As long as this coupling is within the same namespace it's less critical, but once you start to couple to constants from various namespaces you are again hampering reuse. For that matter, consider any constants public API.
An alternative to using constants would be to use immutable Value Objects, for instance:
class Environment
{
private $value;
public function __construct($value)
{
$this->assertValueIsAllowedValue($value);
$this->value = $value;
}
public function getValue() {
// …
This way, you can pass around these values to the objects that need them, in addition to making sure the values are valid. Like always, YMMV. This is just an option. A single constant will not make your code unusable, but relying largely on constants will have a detrimental effect, so as a rule of thumb, try to keep them down to a minimum.
On a related side note, you might also be interested in:
There's a big difference between a global variable and a global constant.
The main reason a global variable is shunned is because it can be modified by anything at any time. It can introduce all sorts of hidden dependencies on call/execution order, and can result in identical code working sometimes and not others, depending on if and how the global has been changed. Obviously the bad mojo can be ramped-up even more if you're dealing with concurrency or parallelism.
A global constant is (or should be) exactly the same throughout your code at all times. Once your code starts executing, it's guaranteed that every bit of code viewing it will see the same thing every time. That means there's no danger of introducing accidental dependencies. The use of constants can in fact be very good for improving reliability, as it means you don't need to update the value in several locations if you need to change your code. (Never underestimate human error!)
Singletons are a whole other issue. It's an often-abused design pattern which can basically end up as an object oriented version of global variables. In some languages (such as C++), it can also go very wrong if you're not careful of initialisation order. However, it can be a useful pattern on occasion, although there are usually better alternatives (albeit sometimes requiring slightly more work).
EDIT: Just to expand briefly, you mentioned in your question that global constants introduce the "globallest state ever". That's not really accurate, because a global constant is (or should be) fixed in the same way as the source code is fixed. It defines the static nature of the program, whereas "state" is typically understood as a dynamic run-time concept (i.e. the stuff that can change).