Site icon Leonid Mamchenkov

Sharing constants between PHP classes

When writing larger applications, it is often useful to have some constants defined, which can then be shared between different parts of the application.  There are several ways to do this, and there is no real rocket science here.

However, the question is: what’s the best way to do so?

Until now I haven’t seen an elegant solution to this problem.  The old school way was defining the constants in the separate include file or in a class, and then accessing them through the global scope or via a specific class.  With a moderately modern PHP version, class constants can also be redefined by an inheriting class, which is, I think, more useful than painful.

Today, I came across an implementation that I really liked.  It’s simple and elegant: define constants in the interface and let classes implement it.  I saw it in the HTTP Message Util library, which defines HTTP methods, status codes and the like for the libraries and applications implementing PSR-7 standard (HTTP message interface).  Look here, for example.

I played around for a few minutes with this and it worked really well.  Until I stumbled upon something.  PHP classes can override the constants from the classes they inherit.  But not from the interface.  Here’s what PHP manual on Object Interfaces has to say about that:

It’s possible for interfaces to have constants. Interface constants works exactly like class constants except they cannot be overridden by a class/interface that inherits them.

This seems reasonable.  After all, the interface is a contract, and whatever is part of it should be precisely so in the implementing classes.  If you try to redefine the constants in the class:

<?php
interface FooInterface
{
    const FOO = 1;
}

class Foo implements FooInterface
{
    const FOO = 1;
}

echo "Foo = " . Foo::FOO . "\n";

you get an error:

PHP Fatal error: Cannot inherit previously-inherited or override constant FOO from interface FooInterface in foo.php on line 7

That sounds about right.  But!  It looks like you can overwrite the interface constants by extending the class.  The following snippet happily prints out two and throws no errors or warnings (my Fedora 25 runs PHP 7.0.14, if you were wondering):

<?php
interface FooInterface
{
    const FOO = 1;
}

class Foo implements FooInterface
{
}
class Bar extends Foo
{
    const FOO = 2;
}

echo "Foo = " . Bar::FOO . "\n";

Weird, right?  But there is more.  If the extending class implements the interface, rather than inherit its implementation from the parent, then the error is back:

<?php
interface FooInterface
{
    const FOO = 1;
}

class Foo implements FooInterface
{
}
class Bar extends Foo implements FooInterface
{
    const FOO = 2;
}

echo "Foo = " . Bar::FOO . "\n";

results in the familiar error, with the adjusted line number:

PHP Fatal error: Cannot inherit previously-inherited or override constant FOO from interface FooInterface in foo.php on line 10

Additionally, PHP provides the final keyword, which can be used to prevent overwriting of the inherited method by the child classes.  However, final keyword is not allowed on the constants, which makes results a bit more difficult to predict.  Especially so with the late static binding.  Here is an example:

<?php
interface FooInterface
{
    const FOO = 1;

    public function hello();
}

class Foo implements FooInterface
{
    public function hello()
    {   
        echo __FUNCTION__ . ": Self " . self::FOO . "\n";
        echo __FUNCTION__ . ": Static " . static::FOO . "\n";
    }   

    final public function bye()
    {   
        echo __FUNCTION__ . ": Self " . self::FOO . "\n";
        echo __FUNCTION__ . ": Static " . static::FOO . "\n";
    }   
}
class Bar extends Foo 
{
    const FOO = 2;
}

echo "\nFrom Foo\n";
$foo = new Foo();
$foo->hello();
$foo->bye();

echo "\nFrom Bar\n";
$bar = new Bar();
$bar->hello();
$bar->bye();

Which results in:


From Foo
hello: Self 1
hello: Static 1
bye: Self 1
bye: Static 1

From Bar
hello: Self 1
hello: Static 2
bye: Self 1
bye: Static 2

I can’t say for sure whether overwriting interface constants in inherited classes is a bug or not, but the current behavior does feel weird.

Exit mobile version