PHP 8.3: Typed Class Constants
PHP 8.3 and later support declaring a type for PHP Class constants. This ensures type compatibility of the constants when child classes and interface implementation override them.
Prior to PHP 8.3, it was not possible to programmatically enforce type compatibility.
In PHP 8.3 and later, class constants can declare a type after the const
keyword:
class Test {
const string TEST_CONSTANT = 'test';
}
The necessity for types in class constants is in enforcing all subclasses that override class constants to not change the type of the constants.
When a class constant is declared with a type, PHP enforces it on the declaration itself and subclasses/implementations following covariance. Further, PHP does not coerce constant values, and is always considered strictly typed.
PHP fatal errors if a class constant is declared with a different type that the declaration:
class Test {
const string CONST = 1;
}
Fatal error: Cannot use int as value for class constant Test::CONST of type string
The following are valid examples of class constants that use constant visibility modifiers (added in PHP 7.1), final
class constants (added in PHP 8.1), and support for constants in traits (added in PHP 8.3). Further, constants declared in Enums also support adding types to them.
Classes with typed constants
class Test {
// public constant with "string" type
const string FOO = 'test';
// protected constant with "string" type
protected const string GARPLY = 'test';
// final protected constant with "string" type
final protected const string WALDO = 'test';
}
Traits with typed constants
trait TestTrait {
// final protected constant with "string" type
final protected const string WALDO = 'test';
}
Interfaces with typed constants
interface TestInterface {
public const string TEST = 'test';
}
Enums with typed constants
enum TestEnum: string {
public const string TEST = 'test';
}
Supported Types and Type Coercion
Class constants support standard PHP standalone types, nullable types, union types, intersection types, and DNF types.
The following types are not supported for class constant types:
void
andnever
types: These types are only meant to be used asreturn
types.callable
: This type is context-dependent, and is not supported in typed properties either. However, theClosure
type is allowed.
The following declarations are not allowed:
class Test {
const void FOO = 'test';
const never FOO = 'test';
const callable FOO = 'test';
}
Fatal error: Class constant Test::FOO cannot have type void
Fatal error: Class constant Test::FOO cannot have type never
Fatal error: Class constant Test::FOO cannot have type callable
Strict Typing Behavior
Regardless of the strict_types
behavior declared in the PHP script, class constants are always evaluated strictly.
declare(strict_types=0);
class Test {
const string CONST = 1;
}
Fatal error: Cannot use int as value for class constant Test::CONST of type string in /mnt/w/localhost/test/test.php on line 4
Variance
Similar to return types, class constant types can also be "narrowed", or kept the same in a sub-class or an implementation. This follows LSP.
class ParentClass {
public const string|int VALUE = 'MyValue';
}
class ChildClass extends ParentClass {
public const string VALUE = 'MyValue';
}
If a class constant attempts to change or widen the declaration (which means they are incompatible), PHP emits a fatal error at compile time:
class ParentClass {
public const string|int VALUE = 'MyValue';
}
class ChildClass extends ParentClass {
public const string|int|float VALUE = 'MyValue';
}
Fatal error: Type of ChildClass::VALUE must be compatible with ParentClass::VALUE of type string|int
Omitting constant type declaration
One noteworthy point is that if a parent class declares a constant type, all sub-classes must also declare a compatible type. Omitting the class constant type is considered an incompatible type.
For example, in the snippet below, ChildClass::VALUE
is declared without a type. Because ParentClass::VALUE
constant is declared with a type, this causes a fatal error:
class ParentClass {
public const string|int VALUE = 'MyValue';
}
class ChildClass extends ParentClass {
public const VALUE = 'MyValue';
}
Fatal error: Type of ChildClass::VALUE must be compatible with ParentClass::VALUE of type string|int
Declaring a type for non-provide class constants is a backward-incompatible change, because all sub-classes must also declare compatible types.
In PHP 8.3, Zip, SNMP, and Phar core extensions declare class constants, which is a breaking change.
Reflection API Changes
It is possible to retrieve the type of class constants using the Reflection API.
The ReflectionClassConstant
class supports two additional methods in PHP 8.3:
class ReflectionClassConstant implements Reflector {
//...
public function getType(): ?ReflectionType {}
public function hasType(): bool {}
}
The hasType()
method returns whether the constant is declared with a constant.
The getType()
method returns null
if the class constant is declared with no type, or a ReflectionType
object if a type is declared.
Usage example
class Test {
const string MY_CONST = 'MyConst';
}
$reflector = new ReflectionClassConstant('Test', 'MY_CONST');
$reflector->hasType(); // true
$reflector->getType(); // "string"
Related Changes
Backward Compatibility Impact
Class constant type syntax is a backward incompatible change. PHP code that use class constant types do not compile in PHP at all, and results in a Parse error.
Note that declaring a class constant type in a PHP class can be an API-incompatible change, because all sub-classes must also declare the constant type, or PHP emits a fatal error otherwise.