PHP 8.2: Constants are supported in traits

Version8.2
TypeNew Feature

PHP Traits provide a mechanism to reuse code in PHP classes and Enums without using class inheritance chains, interfaces, or abstract classes. Traits are similar to PHP classes, in that they both PHP classes and traits support properties, methods, autoloading, magic constants, and other PHP class semantics.

Traits are meant to be used by PHP classes. When a trait is used in a PHP class, all the methods and properties declared in the trait are available within the PHP class.

However, prior to PHP 8.2, it was not possible to declare constants in PHP traits. Attempting to declare a constant in a Trait resulted in a fatal error at the compile time.

trait FooBar {
    const FOO = 'bar';
}
Fatal error: Traits cannot have constants in ... code on line ...

Although traits were not allowed to declare constants, it was possible to access constants in PHP classes that the traits are used in. This resulted in incomplete leaking implementations because there was no way for the trait to ensure the classes that use the trait declare the referenced constants, and there was no way to programmatically enforce it.

trait VersionDependent {
    protected function ensureVersion(): void {
        if (self::CURRENT_VERSION < self::MIN_VERSION) {
            throw new Exception('Current version is too old');
        }
    }
}

The VersionDependent declared above assumes that the classes that use VersionDependent trait will declare CURRENT_VERSION and MIN_VERSION constants, but there is no way for the VersionDependent trait to ensure those constants are declared.


Since PHP 8.2, declaring constants in traits is supported. Trait constants can also be declared with visibility modifiers and as final (since PHP 8.1). All of the following constant declarations are valid in PHP 8.2 and later:

trait FooBar {
    const FOO = 'foo';
    private const BAR = 'bar';
    final const BAZ = 'baz';
    final protected const QUX = 'qux';
}

class Test {
    use FooBar;
}

echo Test::BAZ; // 'bar'

It is not mandatory for the composing class to declare the constants declared in the trait. However, the constants declared in the compositing class must be compatible with the constants declared in the trait.

Overriding Trait Constants

Overriding is not allowed in directly composed classes

Composing a PHP class with same name, visibility, and value as the constants declared in the trait is allowed:

trait FooBar {
    const FOO = 'foo';
    private const BAR = 'bar';
    final const BAZ = 'baz';
    final protected const QUX = 'qux';
}

class ComposingClass {
    use FooBar;

    const FOO = 'foo';
    private const BAR = 'bar';
    final const BAZ = 'baz';
    final protected const QUX = 'qux';
}

All four constants declared in the ComposingClass are considered compatible with the FooBar trait because all of them have exact the same visibility, final flag, and most importantly, the value.

If the composing class declares an incompatible constant, PHP throws a fatal error at compile-time:

trait FooBar {
    const FOO = 'foo';
    private const BAR = 'bar';
    final const BAZ = 'baz';
    final protected const QUX = 'qux';
}

class ComposingClass {
    use FooBar;

    const FOO = 'zzz'; // Value is different.
    protected const BAR = 'bar'; // Visibility is different
    const BAZ = 'baz'; // Final flag is removed
}
Fatal error: ComposingClass and FooBar define the same constant (FOO) in the composition of ComposingClass. However, the definition differs and is considered incompatible. Class was composed in ... on line ...
Fatal error: ComposingClass and FooBar define the same constant (BAR) in the composition of ComposingClass. However, the definition differs and is considered incompatible. Class was composed in ... on line ...
Fatal error: ComposingClass and FooBar define the same constant (BAZ) in the composition of ComposingClass. However, the definition differs and is considered incompatible. Class was composed in ... on line ...

Overriding is allowed on derived classes

Classes that extend a composing class can override the constants if the constant is not declared final:

trait VersionDependent {

    protected const CURRENT_VERSION = '2.6';
    protected const MIN_VERSION     = '2.5';

    protected function ensureVersion(): void {
        if (self::CURRENT_VERSION < self::MIN_VERSION) {
            throw new Exception('Current version is too old');
        }
    }
}

class Application {
    use VersionDependent;
}

class MockApplication extends Application {

    protected const CURRENT_VERSION = '2.6-testing';
    protected const MIN_VERSION     = '2.4';

}

All of the constants declared in the snippet above are valid, and allowed since PHP 8.2.

Overriding is not allowed for final constants

If the constant in the trait is declared with the final flag, attempting to override it results in a fatal error:

trait Test {
    final protected const FOO = 'foo';
}
class BaseContainer {
    use Test;
}
class ApplicationContainer extends BaseContainer{
    protected const FOO = 'bar';
}
Fatal error: ApplicationContainer::FOO cannot override final constant BaseContainer::FOO in ... on line ...

Directly accessing trait constants is not allowed

PHP traits are meant to be used with classes and Enums, and thus, directly accessing constants is not allowed. The following snippet results in an Error

trait Test {
    public const FOO = 'foo';
}
echo Test::FOO;
constant('Test::FOO');
Error: Cannot access trait constant Test::FOO directly in ...:6

Further, the defined function that returns whether a constant exists returns false when a trait constant is passed:

defined('Test::FOO'); // false

Trait Conflicts

When a class is composed with multiple traits that declare the same constants, both declarations must match the value, visibility, and the final flag.

trait Foo {
    final protected const ALPHA = 1;
}

trait Bar {
    final protected const ALPHA = 1;
}

class Test {
    use Foo;
    use Bar;
}

Although both Foo and Bar traits declare an ALPHA constant, composing the Test class is allowed because both traits declare the constant with exact same values, visibility, and finality.

Attempting to composer a class with mismatching constant declarations result in a compile-time fatal error:

trait Foo {
    protected const ALPHA = 1;
    protected const BETA = 1;
    protected const GAMMA = 1;
}

trait Bar {
    protected const ALPHA       = 2; // Different value
    public const BETA           = 1; // Different visibility
    final protected const GAMMA = 1; // Different finality
}

class Test {
    use Foo;
    use Bar;
}
Fatal error: Foo and Bar define the same constant (ALPHA) in the composition of Test. However, the definition differs and is considered incompatible. Class was composed in ... on line ...
Fatal error: Foo and Bar define the same constant (BETA) in the composition of Test. However, the definition differs and is considered incompatible. Class was composed in ... on line ...
Fatal error: Foo and Bar define the same constant (GAMMA) in the composition of Test. However, the definition differs and is considered incompatible. Class was composed in ... on line ...

Backwards Compatibility Impact

PHP versions prior to PHP 8.2 did not allow declaring constants in traits, and existing applications should not cause any issues in PHP 8.2. However, applications that declare constants in traits will not work in older PHP versions, and result in a fatal error in PHP 8.1 and older versions:

trait FooBar {
    const FOO = 'bar';
}
Fatal error: Traits cannot have constants in ... code on line ...

There is no way to back-port this feature to older PHP versions.


RFC Discussion Implementation