108

PHP interfaces allow the definition of constants in an interface, e.g.

interface FooBar
{
    const FOO = 1;
    const BAR = 2;
}
echo FooBar::FOO; // 1

Any implementing class will automatically have these constants available, e.g.

class MyFooBar implement FooBar
{
}
echo MyFooBar::FOO; // 1

My own take on this is that anything Global is Evil. But I wonder if the same applies to Interface Constants. Given that Coding against an Interface is considered good practise in general, is using Interface Constants the only constants that are acceptable to use outside a class context?

While I am curious to hear your personal opinion and whether you use Interface constants or not, I'm mainly looking for objective reasons in your answers. I dont want this to be a Poll Type question. I'm interested in what effect using interface constants has on Maintainability. Coupling. Or Unit Testing. How does it relate to SOLID PHP? Does it violate any coding principles that are considered Good Practise in PHP? You get the idea …

Note: there is a similar question for Java that listed some quite good reasons why they are Bad Practice, but since Java isn't PHP, I felt it justified to ask it within the PHP tag again.

Community
  • 1
  • 1
Gordon
  • 296,205
  • 68
  • 508
  • 534
  • 1
    Hmm, I'd never come across a need to define constants in an interface before. It's worth knowing that classes *implementing the interface* can not override the constants, while classes merely extending each other *can* override constants. – Charles Mar 23 '11 at 18:34
  • 1
    I believe constants are not bad since they have predictable values even when we are concerned with unit testability. The global variables are evil since anybody can change it since it is a variable and everything has scope on it but constants will never change its value thus the term constant. – mdprotacio Feb 01 '12 at 19:09

2 Answers2

144

Well, I think that it boils down to the difference between good and good enough.

While in most cases you can avoid the use of constants by implementing other patterns (strategy or perhaps flyweight), there is something to be said for not needing a half dozen other classes to represent a concept. I think what it boils down to, is how likely is there a need for other constants. In other words, is there a need to extend the ENUM provided by the constants on the interface. If you can foresee needing to expand it, then go with a more formal pattern. If not, then it may suffice (it'll be good enough, and hence be less code to write and test). Here's an example of a good enough and a bad use:

Bad:

interface User {
    const TYPE_ADMINISTRATOR = 1;
    const TYPE_USER          = 2;
    const TYPE_GUEST         = 3;
}

Good Enough:

interface HTTPRequest_1_1 {
    const TYPE_CONNECT = 'connect';
    const TYPE_DELETE  = 'delete';
    const TYPE_GET     = 'get';
    const TYPE_HEAD    = 'head';
    const TYPE_OPTIONS = 'options';
    const TYPE_POST    = 'post';
    const TYPE_PUT     = 'put';

    public function getType();
}

Now, the reason that I chose those examples is simple. The User interface is defining an enum of user types. This is very likely to expand over time and would be better suited by another pattern. But the HTTPRequest_1_1 is a decent use-case, since the enum is defined by RFC2616 and will not change for the lifetime of the class.

In general, I don't see the problem with constants and class constants as being a global problem. I see it as a dependency problem. It's a narrow distinction, but a definite one. I see global problems as in global variables which are not enforced, and as such create a soft global dependency. But a hard-coded class creates an enforced dependency, and as such create a hard global dependency. So both are dependencies. But I consider the global to be far worse since it's not enforced... Which is why I don't like to lump class dependencies with global dependencies under the same banner...

If you write MyClass::FOO, you're hard-coded to the implementation details of MyClass. This creates a hard-coupling, which makes your code less flexible, and as such should be avoided. However, interfaces exist to permit exactly this type of coupling. Therefore MyInterface::FOO doesn't introduce any concrete coupling. With that said, I wouldn't introduce an interface just to add a constant to it.

So if you're using interfaces, and you're very sure that you (or anyone else for that matter) won't need additional values, then I don't really see a huge issue with the interface constants... The best designs wouldn't include any constants or conditionals or magic-numbers or magic-strings or hard-coded anything. However, that adds additional time to the development, as you must consider the uses. My view is that most times it's absolutely worth taking the additional time to build a great solid design. But there are times when good enough really is acceptable (and it takes an experienced developer to understand the difference), and in those cases it's fine.

Again, that's just my view on it...

NikiC
  • 95,987
  • 31
  • 182
  • 219
ircmaxell
  • 155,647
  • 33
  • 256
  • 309
  • 4
    What would you suggest as a different pattern for User in this case? – Jacob Mar 24 '11 at 00:35
  • @Jacob: I would abstract it away. Depending on your needs, I'd likely build an Access class that would get its data from a database table. That way adding a new level is as easy as inserting a new row. Another option would be to build an ENUM class set (where you have one class for each permission role). Then you can extend classes where necessary to provide the appropriate permissions. But there are other methods that will work too – ircmaxell Mar 24 '11 at 14:05
  • 3
    Very solid and well articulated answer! +1 – Decent Dabbler Mar 27 '11 at 00:05
  • 1
    class with public constants shouldn't have any methods. It should be only data structure or only object - not both. – OZ_ Sep 23 '11 at 11:56
  • @OZ_: Well, it sort of depends upon the use. If you're building an ENUM style class, then having methods relating to enforcing the constraints (such as [SPLEnum](http://php.net/splenum)). If you're not, and you're just representing a domain of possible values that could change (to eliminate magic numbers and strings), then it's in the air whether or not it should be class only or not. It really depends on the reason for both the class and the constants. So no, I'll disagree that classes with public constants shouldn't have methods. I normally would put them on an interface, but it depends – ircmaxell Sep 23 '11 at 12:41
  • "The best designs wouldn't include any constants or conditionals or magic-numbers or magic-strings or hard-coded anything." - That sounds great. How would you avoid conditionals, which is kind-of the heart of any decision making process? – Frederik Krautwald Feb 27 '13 at 21:23
  • 2
    @FrederikKrautwald: you can avoid conditionals with polymorphism (in most cases): Check [this answer](http://stackoverflow.com/a/7264192/338665) out as well as [watch this Clean Code talk](http://www.youtube.com/watch?v=4F72VULWFvc)... – ircmaxell Feb 27 '13 at 22:44
  • @ircmaxell, Thanks for the link. I agree that by refactoring conditionals, in many and most cases they can be eliminated. However, I can’t see how they possibly can be completely avoided, but maybe that’s just my lack of logic. – Frederik Krautwald Oct 10 '13 at 14:14
  • @FrederikKrautwald you can't completely avoid them. You can completely avoid them in business logic. They will still be necessary in factories and builders and configuration logic, but the actual "business work" can be completely conditional free... I'm not saying *should* be **completely** free, but can. – ircmaxell Oct 10 '13 at 14:17
  • @ircmaxell, good. That’s the conclusion I arrived at too, and I won’t struggle my brain any further to pursue how to remove all and one. – Frederik Krautwald Oct 10 '13 at 14:20
  • @ircmaxell I have two questions: 1. "The best designs wouldn't include any constants..." so then how do you manage when having 10 files with the same string, wouldn't you want a single point of change? 2. "...the actual "business work" can be completely conditional free.." do you refer to the Core Domain as business work (entities/value objects etc.). and if so, do you suggest the usage of arrays etc? How do we implement a Specifications and Validators if that is true? – ziGi Jul 21 '15 at 01:28
10

I think that its usually better to handle constants, specially enumerated constants, as a separate type ("class") from your interface:

define(TYPE_CONNECT, 'connect');
define(TYPE_DELETE , 'delete');
define(TYPE_GET    , 'get');
define(TYPE_HEAD   , 'head');
define(TYPE_OPTIONS, 'options');
define(TYPE_POST   , 'post');
define(TYPE_PUT    , 'put');

interface IFoo
{
  function /* int */ readSomething();
  function /* void */ ExecuteSomething(/* int */ param);
}

class CBar implements IFoo
{
  function /* int */ readSomething() { ...}
  function /* void */ ExecuteSomething(/* int */ param) { ... }
}

or, if you want to use a class as a namespace:

class TypeHTTP_Enums
{
  const TYPE_CONNECT = 'connect';
  const TYPE_DELETE  = 'delete';
  const TYPE_GET     = 'get';
  const TYPE_HEAD    = 'head';
  const TYPE_OPTIONS = 'options';
  const TYPE_POST    = 'post';
  const TYPE_PUT     = 'put';
}

interface IFoo
{
  function /* int */ readSomething();
  function /* void */ ExecuteSomething(/* int */ param);
}

class CBar implements IFoo
{
  function /* int */ readSomething() { ...}
  function /* void */ ExecuteSomething(/* int */ param) { ... }
}

Its not that you are using just constants, you are using the concept of enumerated values or enumerations, which a set of restricted values, are considered a specific type, with a specific usage ("domain" ? )

umlcat
  • 3,978
  • 3
  • 17
  • 28