Abstract constants in PHP - Force a child class to define a constant

PhpConstantsAbstract

Php Problem Overview


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) ?

Php Solutions


Solution 1 - Php

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;

Solution 2 - Php

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);
			}
		}
	}
}

Solution 3 - Php

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.

Solution 4 - Php

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

Solution 5 - Php

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 ...

Solution 6 - Php

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();

Solution 7 - Php

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionAlexView Question on Stackoverflow
Solution 1 - PhpWebSmitheryView Answer on Stackoverflow
Solution 2 - PhpBabaView Answer on Stackoverflow
Solution 3 - PhpJamie RumbelowView Answer on Stackoverflow
Solution 4 - PhpSongoView Answer on Stackoverflow
Solution 5 - PhpCode SpiritView Answer on Stackoverflow
Solution 6 - Phpuser17231569View Answer on Stackoverflow
Solution 7 - PhpAdam BerryView Answer on Stackoverflow