75

What is the best way to define constants that may be used by a number of classes within a namespace? I'm trying to avoid too much inheritance, so extending base classes is not an ideal solution, and I'm struggling to find a good solution using traits. Is this in any way possible in PHP 5.4 or should a different approach be taken?

I have the following situation:

trait Base
{
    // Generic functions
}

class A 
{
    use Base;
}

class B 
{
    use Base;
}

The problem is that it is not possible to define constants in PHP traits. Ideally, I would want something like the following:

trait Base
{
    const SOME_CONST = 'someconst';
    const SOME_OTHER_CONST = 'someotherconst';

    // Generic functions
}

Then these could be accessed though the class that applies the trait:

echo A::SOME_CONST;
echo B::SOME_OTHER_CONST;

But due to the limitations of traits this isn't possible. Any ideas?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
t j
  • 7,026
  • 12
  • 46
  • 66
  • 8
    You **can** use static properties, it's not a constant but it's also only one character different in calling it... `public static $SOME_VAR = 'someconst';` and `echo B::$SOME_VAR` – scrowler Jun 23 '14 at 03:25
  • Thanks for the tip. That is one option although this is a refactor job, and not new code so I'd prefer the behavior to stay the same rather than update all the constant calls (there are a lot of them). – t j Jun 23 '14 at 03:37
  • 11
    You could use interface to declare consts. – sectus Jun 23 '14 at 04:48
  • 3
    https://bugs.php.net/bug.php?id=70986 – bishop Apr 04 '16 at 16:40
  • 3
    https://bugs.php.net/bug.php?id=70986 has been superseded by https://bugs.php.net/bug.php?id=75060 now – caw Feb 06 '18 at 21:49
  • 2
    Accepted RFC, may be available in PHP 8.2 : https://wiki.php.net/rfc/constants_in_traits – SeeoX Jul 20 '22 at 07:27

7 Answers7

108

I ended up using user sectus's suggestion of interfaces as it feels like the least-problematic way of handling this. Using an interface to store constants rather than API contracts has a bad smell about it though so maybe this issue is more about OO design than trait implementation.

interface Definition
{
    const SOME_CONST = 'someconst';
    const SOME_OTHER_CONST = 'someotherconst';
}

trait Base
{
    // Generic functions
}

class A implements Definition
{
    use Base;
}

class B implements Definition
{
    use Base;
}

Which allows for:

A::SOME_CONST;
B::SOME_CONST;
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
t j
  • 7,026
  • 12
  • 46
  • 66
  • 106
    So interfaces can have constants...but traits can't? PHP gets weirder and weirder. – Dylan Pierce Jun 10 '15 at 19:17
  • @DylanPierce yes but annoyingly unlike when extending from a class, you don't seem to have the option of overriding an inherited/extended constant from an interface. :( – Adambean Dec 06 '17 at 10:19
  • @Adambean is this still true with PHP 7.0 and above? Just curious – Dylan Pierce Dec 06 '17 at 20:24
  • 8
    @Adambean overriding a constant defeats the purpose of the `constant`, it isn't meant to change. – aknosis Feb 08 '18 at 18:55
  • 1
    Yet if I want to use a value within the trait's code implementation, it's not possible. – SOFe May 04 '18 at 15:28
  • 11
    @Aknosis I personally see it more than a constant doesn't change value during its lifecycle. That doesn't mean it cannot be redefined with another value, when extending a class (and use [e.g.] `static::VALIDATION_REGEX` to get the [late-static-bound](https://secure.php.net/manual/en/language.oop5.late-static-bindings.php) value) – Kamafeather Aug 29 '18 at 15:06
  • @DylanPierce Looks that way, even up to PHP 7.3. And I'm not aware of this issue having even been considered for PHP 8.0. However, it's not too late... – Jake Jun 21 '20 at 00:58
  • 2
    PHP 'bug' [#75060](https://bugs.php.net/bug.php?id=75060) – Jake Jun 21 '20 at 01:13
  • exactly as @Kamafeather I can see the constant being e.g. some class name(path) that is being used and you'd want to enforce such constant as e.g. `abstract` (which is not currently possible) but you get valid code while using it in the Trait, this is a generic issue of PHP not allowing abstract properties. – jave.web Sep 13 '22 at 12:47
  • @aknosis that is not really the purpose of constants. Overriding the value AT RUNTIME defeats the purpose of a constant. Overriding the value in metaprogramming such as traits is not remotely in conflict with the purpose of constants, and is achievable in many languages such as C/C++ – That Realty Programmer Guy Dec 04 '22 at 10:57
  • PHP 8.2 allows constants in traits, so accepted answer should now rather be [Ivan Yarych's](https://stackoverflow.com/a/73472963/2812189) – fbastien May 10 '23 at 16:47
  • 1
    @fbastien Nice. I've updated the accepted answer. – t j Jun 01 '23 at 00:08
49

You could also use static variables. They can be used in the class or the trait itself. - Works fine for me as a replacement for const.

trait myTrait {
    static $someVarA = "my specific content";
    static $someVarB = "my second specific content";
}

class myCustomClass {
    use myTrait;

    public function hello()
    {
        return self::$someVarA;
    }
}
Michael
  • 1,922
  • 1
  • 19
  • 17
13

PHP 8.2 (Dec 2022) has Constants in Traits1. To quote from the PHP Manual:

Traits can, as of PHP 8.2.0, also define constants.

Compare with Example #14 Defining Constants and see the example:

trait Foo {
    public const FLAG_1 = 1;
    protected const FLAG_2 = 2;
    private const FLAG_3 = 2;
 
    public function doFoo(int $flags): void {
        if ($flags & self::FLAG_1) {
            echo 'Got flag 1';
        }
        if ($flags & self::FLAG_2) {
            echo 'Got flag 2';
        }
        if ($flags & self::FLAG_3) {
            echo 'Got flag 3';
        }
    }
}

  1. PHP 8.2 New Features: Constants in Traits (RFC, CHANGE)
hakre
  • 193,403
  • 52
  • 435
  • 836
Ivan Yarych
  • 1,931
  • 17
  • 15
12

To limit the scope of your constants, you can define them inside a namespace:

namespace Test;

const Foo = 123;

// generic functions or classes

echo Foo;
echo namespace\Foo;

A downside of this approach is that autoloading won't work for constants, at least not for 5.4; the typical way around this is to wrap those constants in a static class, i.e.:

namespace Test;

class Bar
{
    const Foo = 123;
}
Ja͢ck
  • 170,779
  • 38
  • 263
  • 309
  • Interesting idea. I didn't realise you could access namespaced constants directly. The problem is this library is PSR-0 autoloaded so I'm afraid this approach wouldn't work. Thanks for the answer though. – t j Jun 24 '14 at 03:03
  • 1
    @orciny Well, autoloading would work if you wrap those constants inside a static class; not the shiniest example of beautiful code, but it would work :) – Ja͢ck Jun 24 '14 at 03:06
9

Not a good one, but maybe...

trait Base
{
    public static function SOME_CONST()
    {
        return 'value1';
    }

    public static function SOME_OTHER_CONST()
    {
        return 'value2';
    }

    // generic functions
}

class A
{
    use Base;
}

class B
{
    use Base;
}

echo A::SOME_CONST();
echo B::SOME_OTHER_CONST();
Jekis
  • 4,274
  • 2
  • 36
  • 42
  • Note to those that didn't follow, these are static methods, not constants.. they look a bit like constants when used though. If you were to do this and you wanted these methods to behave more like constants, you would want to prefix the methods with the keyword `final` to ensure they couldn't be overriden (same behaviour as constants). – John Hunt Mar 05 '20 at 14:50
  • 2
    @John Hunt, `final` doesn't work for traits, see https://stackoverflow.com/a/33481511/251735 – Jekis Mar 09 '20 at 05:43
4

Starting from PHP 8.1, it is possible to use readonly properties in traits.

<?php
trait A
{
    public readonly int $variable;
    
    protected function initA(int $newValue){
        $this->variable = $newValue;
    }

    public function changeVariable(int $newValue){
        $this->variable = $newValue;
    }
}

class B {
    use A;

    public function __construct() {
        $this->initA(1);
    }
}

$b = new B();
$b->changeVariable(5); // should faild: Fatal error: Uncaught Error: Cannot modify readonly property B::$variable
Weld0s
  • 41
  • 1
-4

Something else to consider is whether or not you can use an abstract class instead, and then inherit.

abstract class Base
{
    const A = 1;
    const B = 2;
}

class Class1 extends Base {}
class Class2 extends Base {}

echo Class1::A;
echo Class2::B;

Of course, part of the reason for traits is replace complex inheritance trees with composition. Depends on the situation.

Osan
  • 187
  • 1
  • 3
  • 13