73

I noticed that you can't have abstract constants in PHP.

Is there a way I can force a child class to define a constant (which I need to use in one of the abstract class internal methods) ?

Jon Surrell
  • 9,444
  • 8
  • 48
  • 54
Alex
  • 66,732
  • 177
  • 439
  • 641
  • A constant it full scope once set its available in every class, function method or what-not. it makes no sense, please provide some code to explain abit more. – Lawrence Cherone Apr 29 '12 at 00:10
  • 2
    Either define a constant in the abstract class (thus every child class has a constant, even if it does not define its own), or use an abstract function (which forces every child class to define its own). Edit: the short answer to your question is no. – halfer Apr 29 '12 at 00:17
  • If you a value must be set at runtime than it is, by definition, a variable. – Francisco Luz Dec 24 '18 at 02:42

7 Answers7

87

This may be a bit of a ‘hack’, but does the job with very little effort, but just with a different error message if the constant is not declared in the child class.

A self-referential constant declaration is syntactically correct and parses without problem, only throwing an error if that declaration is actually executed at runtime, so a self-referential declaration in the abstract class must be overridden in a child class else there will be fatal error: Cannot declare self-referencing constant.

In this example, the abstract, parent class Foo forces all its children to declare the variable NAME. This code runs fine, outputting Donald. However, if the child class Fooling did not declare the variable, the fatal error would be triggered.

<?php

abstract class Foo {

    // Self-referential 'abstract' declaration
    const NAME = self::NAME;

}

class Fooling extends Foo {

    // Overrides definition from parent class
    // Without this declaration, an error will be triggered
    const NAME = 'Donald';

}

$fooling = new Fooling();

echo $fooling::NAME;
WebSmithery
  • 1,060
  • 1
  • 8
  • 5
  • 1
    Thanks @WebSmithery – Dos Jun 26 '17 at 11:54
  • 2
    This is the most elegant solution in my opinion - thank you! – Mike Harrison Oct 03 '18 at 17:23
  • 18
    Just an FYI, I tried this just now in php 7.2 and it's not working. Throwing exception: Fatal error: Uncaught Error: Cannot declare self-referencing constant 'self::RAW_DATA_CACHE_KEY' – Chris Flannagan Nov 07 '18 at 01:30
  • 5
    I've just tested with PHP 7.2.19 and it works like a charm. Please notice that you can't call self::NAME inside the class, but $this::NAME. – StR Aug 05 '19 at 20:25
  • 4
    @ChrisFlannagan try `static::NAME` – Lucas Bustamante Sep 11 '19 at 21:09
  • Bizarrely, it does not work if the extended class's constant value comes from a `define`, even though it is normally possible to specify a class constant via a `define` (PHP 7.3.11). – Jake Aug 06 '20 at 13:49
  • 1
    @LucasBustamante this results in "PHP Fatal error: "static::" is not allowed in compile-time constants". – dirdi Dec 06 '20 at 01:17
  • 4
    PhpStorm 2019.3.4 incorrectly marks this as an error "Cannot declare self-referencing constant", however, it's a bug, this is valid syntax. Vote for the bug report: https://youtrack.jetbrains.com/issue/WI-58073 – Lucas Bustamante Jan 18 '21 at 16:57
  • 4
    This solution doesn't work in PHP 7.4, during runtime the exception "Cannot declare self-referencing constant 'self::MY_VAR'" is thrown. – Moongazer Sep 20 '21 at 16:11
  • This is actually very clever! – Ruben Oct 18 '21 at 06:56
  • doesn't work in 8.0 – Pancho Jan 10 '22 at 22:29
  • The inconsistent behavior people mention seems to be due to a long-standing bug in PHP compiling, which is reportedly fixed in 8.1: https://bugs.php.net/bug.php?id=76725 . Regardless, if you're going to use this, take note of the last sentence in the final comment on that bug report: "whether or not an error gets thrown in this case is unspecified, and the behavior may change in the future." – Andron Feb 20 '22 at 17:37
  • PHPstan does not like it. – Yevgeniy Afanasyev Mar 24 '23 at 00:29
33

A constant is a constant; there is no abstract or private constants in PHP as far as I know, but you can have a work around:

Sample Abstract Class

abstract class Hello {
    const CONSTANT_1 = 'abstract'; // Make Abstract
    const CONSTANT_2 = 'abstract'; // Make Abstract
    const CONSTANT_3 = 'Hello World'; // Normal Constant
    function __construct() {
        Enforcer::__add(__CLASS__, get_called_class());
    }
}

This would run fine

class Foo extends Hello {
    const CONSTANT_1 = 'HELLO_A';
    const CONSTANT_2 = 'HELLO_B';
}
new Foo();

Bar would return Error

class Bar extends Hello {
    const CONSTANT_1 = 'BAR_A';
}
new Bar();

Songo would return Error

class Songo extends Hello {

}
new Songo();

Enforcer Class

class Enforcer {
    public static function __add($class, $c) {
        $reflection = new ReflectionClass($class);
        $constantsForced = $reflection->getConstants();
        foreach ($constantsForced as $constant => $value) {
            if (constant("$c::$constant") == "abstract") {
                throw new Exception("Undefined $constant in " . (string) $c);
            }
        }
    }
}
tvkanters
  • 3,519
  • 4
  • 29
  • 45
Baba
  • 94,024
  • 28
  • 166
  • 217
  • 4
    That's not a constant, that's a function. – Zombaya Apr 29 '12 at 00:07
  • @Zombaya thanks for the observation .. i clicked on submit too early .. hope you can re consider the down vote now .. thanks ... – Baba Apr 29 '12 at 00:19
  • 1
    @Baba good work around :) But does that mean that for every constant there is going to be an if statement? – Songo Apr 29 '12 at 00:38
  • @Baba hahahaha :D u put me into the code!!! :D nice but now there is no way to differentiate between normal constants and the abstract ones :D – Songo Apr 29 '12 at 00:51
  • There is ... all `constant` define in the `abstract` class are treated as `abstract` but all `constant` define in the child are just not forced ... I got you this time @Songo – Baba Apr 29 '12 at 00:55
  • @Baba I'm getting cornered here :)))) OK My last chance How would you add normal constants to the abstract class? ;) work around this and I'll declare my defeat :P – Songo Apr 29 '12 at 01:29
  • 1
    @Songo .. you can see updated code .. its already working for normal and abstract .... i change the keywords so you can understand – Baba Apr 29 '12 at 22:16
  • 1
    @Baba Aha! I get it now :D Nice job ;) Really should up vote it twice :D – Songo Apr 29 '12 at 22:23
  • lol ... too bad you can not do that .... as i said .. i don't give up easily ... :D – Baba Apr 29 '12 at 22:30
  • 12
    While this works, I feel that this solution is rather cumbersome, unreliable and introduces unneeded complexity. If the Abstract class depends on runtime information, @Alex should either use the *Template Method* pattern with *Constant Methods* or check inside the processing methods for the existence of the default constant value. Also, the hardcoded dependency on the Enforcer in the ctor is easily overlooked. A subclass could override the ctor and then all that dirty fiddling with the Reflection API wouldn't work anymore. – Gordon Oct 27 '12 at 10:08
  • The first line says `A constant is a constant .. there is no abstract or private` this is just an example for for a work around .. not perfect, no to be use in production ... just for educational purpose .... If you have a better solution just add it ... – Baba Oct 27 '12 at 10:46
  • 5
    My comment includes two suggestions for a better solution. Also, if you know your solution is only a Proof-of-Concept and *not to be used in production*, the answer should say so. People coming to SO usually seek help for real problems they face in production. But they don't necessarily know something is bad practise, a hack or just for "educational purpose". Unless told so. That's why I am pointing it out. – Gordon Oct 27 '12 at 11:04
  • I have small issue with if (constant("$c::$constant") == "abstract"). I am getting an Exception if value of constant = 0. Integer 0!. the "===" comparison fixed the issue. – printminion Oct 22 '13 at 09:38
  • I am using a trait as a Chain of Responsibility pattern which requires an object to register a constant CHAIN_HANDLER to pass into the chain registration method. This answer was pretty useful to this end, +1. – mopsyd Nov 26 '14 at 06:53
  • +1 @Gordon, using a template method design pattern for this purpose seems to be the cleanest option. Is clear that subtypes must implement it and I can always use it in the base code. https://sourcemaking.com/design_patterns/template_method/php – Onema Jun 09 '15 at 21:06
  • @WebSmithery has provided a much better answer and should be the accepted answer – MC57 Aug 22 '18 at 12:07
22

Unfortunately not... a constant is exactly what it says on the tin, constant. Once defined it can't be redefined, so in that way, it is impossible to require its definition through PHP's abstract inheritance or interfaces.

However... you could check to see if the constant is defined in the parent class's constructor. If it doesn't, throw an Exception.

abstract class A
{
    public function __construct()
    {
        if (!defined('static::BLAH'))
        {
            throw new Exception('Constant BLAH is not defined on subclass ' . get_class($this));
        }
    }
}

class B extends A
{
    const BLAH = 'here';
}

$b = new B();

This is the best way I can think of doing this from your initial description.

Jamie Rumbelow
  • 4,967
  • 2
  • 30
  • 42
  • 8
    A constant's value can be overridden in a child class. Constants are not immune to that sort of redefinition. – Brilliand Feb 12 '15 at 19:21
  • @Brilliand yeah that is not a constant then. – Alfonso Fernandez-Ocampo Jun 13 '20 at 08:28
  • 1
    @AlfonsoFernandez-Ocampo If it's in a different context (i.e. a child class), then it's effectively a different constant, not a change to the first constant. Having "constant" mean that nothing can be defined elsewhere that obscures the constant would be rather extreme. – Brilliand Jun 14 '20 at 12:05
  • 1
    This is the most elegant solution in PHP 7.4 and higher, in my opinion. Could even make an array with constant names and associated error messages to print and loop through it in the constructor, if you have a lot of them. – A. MacGillivray Jul 01 '22 at 18:51
18

No, yet you could try other ways such as abstract methods:

abstract class Fruit
{
    abstract function getName();
    abstract function getColor();

    public function printInfo()
    {
        echo "The {$this->getName()} is {$this->getColor()}";
    }
}

class Apple extends Fruit
{
    function getName() { return 'apple'; }
    function getColor() { return 'red'; }

    //other apple methods
}

class Banana extends Fruit
{
    function getName() { return 'banana'; }
    function getColor() { return 'yellow'; }

    //other banana methods
}  

or static members:

abstract class Fruit
{
    protected static $name;
    protected static $color;

    public function printInfo()
    {
        echo "The {static::$name} is {static::$color}";
    }
}

class Apple extends Fruit
{
    protected static $name = 'apple';
    protected static $color = 'red';

    //other apple methods
}

class Banana extends Fruit
{
    protected static $name = 'banana';
    protected static $color = 'yellow';

    //other banana methods
} 

Source

k0pernikus
  • 60,309
  • 67
  • 216
  • 347
Songo
  • 5,618
  • 8
  • 58
  • 96
  • 8
    If you copy code from elsewhere on the web, make sure you are also citing the source. The above code was taken from http://www.sitepoint.com/forums/showthread.php?629565-Abstract-constants – Gordon Oct 27 '12 at 10:16
  • 3
    This is better then the accepted answer. If an abstract class depends on the child class, define an abstract method to declare this dependency and use the method to get the value from the implementing class. – Kwebble Jun 09 '16 at 07:43
2

Tested in php 7.2 but should since 5.3 you can leverage late static binding to archive this behaviour. It will throw an Fatal Error achchieving the same as an Exception because in most causes you dont want to handle Fatal Errors at runtime. If you want so you can easily implement a custom error handler.

So the following works for me:

<?php

abstract class Foo {

    public function __construct() {
        echo static::BAR;
    }

}


class Bar extends Foo {
    const BAR = "foo bar";
}

$bar = new Bar();    //foo bar

If you remove the const you will get an:

Fatal error: Uncaught Error: Undefined class constant 'BAR' in ...
Code Spirit
  • 3,992
  • 4
  • 23
  • 34
  • 2
    Unfortunatly you don't get a compile time error - you only get an error when you execute `echo static::BAR;`. An IDE or static analyser won't tell the author of class Bar that they have to define the constant. – bdsl Aug 04 '20 at 11:44
2

Maybe I'm missing something, but using late static binding worked for me. Does this work for your question?

abstract class A{
    const NAME=null;

    static function f(){
        return static::NAME;
    }
}

class B extends A{
    const NAME='B';    
}

B::f();
  • This is arguably the safest way to do it, especially if NAME has to be a particular type. You can enforce the proper type in the function, and you can catch the error or let it fail outright if the child class didn't redeclare it. As much as I like the most popular "self-referential" answer, it seems too unstable. – Andron Feb 20 '22 at 17:33
0

PHP Interfaces support constants. It's not as ideal because you would have to remember to implement the interface on every child class so it kind of defeats the purpose partially.

Adam Berry
  • 108
  • 1
  • 3
  • Since php 8.1, this should probably be the accepted answer now. It's now possible to declare an interface constant and override it in an implementing class. – Optimum Apr 29 '22 at 13:59
  • Having an Interface constant defined doesn't enforce the implementing class to redefine the constant. So this is not a solution to the problem as described by the author of the question. – JHoffmann Sep 08 '22 at 09:07