7

Is there a way to create a class's constants dynamically? I know this sounds a bit odd but let me explain what I'm trying to do:

  • I have a Enum class who's attributes are defined by static const definitions
  • This class extends the PHP SplEnum class
  • Rather than type in each of these definitions in code I'd like to have a static initialiser go out to the database and pull the enumerated values

Maybe somethings like this:

class myEnum extends SplEnum {
    public static function init () {
        $myNameValuePair = DB_Functions::get_enum_list();
        foreach ( $myNameValuePair as $name => $value) {
            $const = array ( self , $name );
            $const = $value;
        }
    }
}

I recognise that this won't actually work as it doesn't set CONST's but rather static variables. Maybe my whole idea is hair brained and there's a better technique to this. Anyway, any method to achieve the end goal is greatly appreciated.

UPDATE

I think it might be helpful to be a little more clear on my goals because I think it's entirely possibly that my use of Constants is not a good one. Basically I want to achieve is typical of the Enumerated list's requirements:

  1. Constrain function signatures. I want to be able to ask for a "set" of values as an input to a function. For instance:

    public function do_something ( ENUM_Types $type ) {}

  2. Simple and Compact. Allow for a simple and compact syntax when used in code. For instance with the use of constants I might write a conditional statement something like:

    if ( $my_var === ENUM_Types::TypeA ) {}

  3. Dynamic enumeration. I'd like this enumeration to be managed through the frontend and stored in the database (I'm using wordpress admin screens for this in case anyone cares). At run time this "list" should be pulled out of the DB and made available to the code as an enumeration (or similar structure that achieves the goals above).

PeeHaa
  • 71,436
  • 58
  • 190
  • 262
ken
  • 8,763
  • 11
  • 72
  • 133

4 Answers4

9

Wrap your "enum" values in a singleton and implement the (non-static) magic __get method:

<?php
class DynamicEnums {

  private static $singleton;

  private $enum_values;

  public static function singleton() {
    if (!self::$singleton) {
        self::$singleton = new DynamicEnums();
    }
    return self::$singleton;
  }

  function __construct() {
    $this->enum_values = array( //fetch from somewhere
        'one' => 'two',
        'buckle' => 'my shoe!',
    );
  }

  function __get($name) {
    return $this->enum_values[$name]; //or throw Exception?
  }

  public static function values() {
    return self::singleton()->enum_values; //warning... mutable!
  }
}

For bonus points, create a (non-OO) function that returns the singleton:

function DynamicEnums() {
    return DynamicEnums::singleton();
}

Consumers of "DynamicEnums" would look like:

echo DynamicEnums::singleton()->one;
echo DynamicEnums()->one;            //can you feel the magic?
print_r(DynamicEnums::values());

[edit] More enum-like.

EthanB
  • 4,239
  • 1
  • 28
  • 46
  • This looks good. I believe it meets all my requirements. Many thanks for the magic. :^) – ken Aug 25 '12 at 18:07
  • sadly i have run into a big limitation with this approach ... namely if you build a set of helper functions into a base class and then subsclass it for your various Enum's you find that they all share the singleton from the static base. Damn, damn, damn. I need to find a way out of this box I find myself in. – ken Aug 28 '12 at 18:33
  • Need help calling a static method dynamically? http://stackoverflow.com/questions/2108795/dynamic-static-method-call-in-php – EthanB Aug 28 '12 at 18:42
7

Q: Is there a way to create a class's constants dynamically?

The answer is 'Yes', but don't do that :)

class EnumFactory {

   public static function create($class, array $constants) {
       $declaration = '';
       foreach($constants as $name => $value) {
           $declaration .= 'const ' . $name . ' = ' . $value . ';';
       }
       eval("class $class { $declaration }");
   }

}

EnumFactory::create('darkSide', array('FOO' => 1, 'BAR' => 2));
echo darkSide::FOO . ' ' . darkSide::BAR;

Next question...

Q: Constrain function signatures. I want to be able to ask for a "set" of values as an input to a function. For instance: public function do_something ( ENUM_Types $type ) {}

According to the manual, in that case $type is must be an instance of the ENUM_Types class. But for constant it is impossible (they can't contain objects).

But wait... We can use such trick:

class Enum {

    protected static $_constantToClassMap = array();
    protected static function who() { return __CLASS__; }

    public static function registerConstants($constants) {
        $class = static::who();
        foreach ($constants as $name => $value) {
            self::$_constantToClassMap[$class . '_' . $name] = new $class();
        }
    }

    public static function __callStatic($name, $arguments) {
        return self::$_constantToClassMap[static::who() . '_' . $name];
    }

}

class EnumFactory {

    public static function create($class, $constants) {
        $declaration = '';
        foreach($constants as $name => $value) {
            $declaration .= 'const ' . $name . ' = ' . $value . ';';
        }

        eval("class $class extends Enum { $declaration protected static function who() { return __CLASS__; } }");
        $class::registerConstants($constants);
    }

}

EnumFactory::create('darkSide', array('FOO' => 1, 'BAR' => 2));
EnumFactory::create('aaa', array('FOO' => 1, 'BAR' => 2));

echo (aaa::BAR() instanceof aaa) ? 'Yes' : 'No'; // Yes
echo (aaa::BAR() instanceof darkSide) ? 'Yes' : 'No'; // No

And after that we can use a "type hinting":

function doSomething(darkSide $var) {
    echo 'Bu!';
}

doSomething(darkSide::BAR());
doSomething(aaa::BAR());

Q: Simple and Compact. Allow for a simple and compact syntax when used in code. For instance with the use of constants I might write a conditional statement something like: if ( $my_var === ENUM_Types::TypeA ) {}

You can use values of your pseudo-constants in such form:

if (darkSide::FOO === 1) {}

Q: Dynamic enumeration. I'd like this enumeration to be managed through the frontend and stored in the database (I'm using wordpress admin screens for this in case anyone cares). At run time this "list" should be pulled out of the DB and made available to the code as an enumeration (or similar structure that achieves the goals above).

You can init your enumeration by passing array to the EnumFactory::create($class, $constants):

EnumFactory::create('darkSide', array('FOO' => 1, 'BAR' => 2));
Vladimir Posvistelik
  • 3,843
  • 24
  • 28
  • 3
    many thanks. Great to see how one might achieve this if you're willing to walk on the dark side (aka, the eval side). – ken Aug 25 '12 at 18:06
0

You could do something like Const = $$constant. Then you could set $contant = whatever. OR you could use a protected property since you want it to be dynamic and Constants are not. Example:

class Foo {

protected $test = '';

function set($bar){
    $this->test = $bar;
}
function get($bar){
    return $this->test;
}


}

$foobar = new Foo();
$foobar->set('test');
echo $foobar->get('test');
Alex Reynolds
  • 6,264
  • 4
  • 26
  • 42
  • By definition, a constant cannot be dynamic. It *must* be set to a literal value. – Matt Aug 24 '12 at 17:00
  • How about using a property that is protected and using it like a Psuedo constant? – Alex Reynolds Aug 24 '12 at 17:13
  • Can you describe what you mean in your answer? – Matt Aug 24 '12 at 17:14
  • Me? I'm just saying that Constants can be used statically so you can't do them in a constructor method. Really what he may want is just a property that can be set and then protected. Because in his use, it's more like a protected property than a constant. – Alex Reynolds Aug 24 '12 at 17:17
  • *"...you can't do them in a constructor method"*...what about `self::$var = "value";`? It may not make *sense* to do it that way, but it's possible. – Matt Aug 24 '12 at 17:17
  • Also, I meant, "can you describe what you mean in that comment, by editing your answer?" Not "can you describe what your answer means?" – Matt Aug 24 '12 at 17:19
0

I do not recommend it, but eval() ... please don't.

I've modified autoloaders to automatically define Exception types that are missing or misspelled. Reason: You can catch an uncaught exception, but you cannot recover from the PHP_FATAL when instantiating a typo in your exception class.

pestilence669
  • 5,698
  • 1
  • 23
  • 35