Dynamic constants in PHP? - php

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:
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 ) {}
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 ) {}
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).

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.

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

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

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.

Related

Why PHP `__invoke` Not Working When Triggered from an Object Property

I wonder whether this is a bug or normal. Let’s say I have a class with some magical functions:
class Foo {
public function __toString() {
return '`__toString` called.';
}
public function __get($key) {
return '`__get(' . $key . ')` called.';
}
public function __invoke($x = "") {
return '`__invoke(' . $x . ')` called.';
}
}
And then create an instance in an object property like this:
$object = (object) [
'foo' => 'bar',
'baz' => new Foo
];
Then test it:
echo $object->baz;
echo $object->baz->qux;
echo $object->baz('%'); // :(
It is broken in the last echo: Call to undefined method stdClass::baz()
Currently, the only solution I can do is to store the __invoke part in a temporary variable and then call that variable as a function like this:
$x = $object->baz;
echo $x('%'); // :)
It works fine when I instantiate the class in an array property:
$array = [
'baz' => new Foo
];
echo $array['baz'];
echo $array['baz']->qux;
echo $array['baz']('%'); // :)
By the way, I need this ability on my object for something related to API:
$foo = (object) ['bar' => new MyClass];
echo $foo->bar; → should trigger __toString
echo $foo->bar->baz; → should trigger __get
echo $foo->bar(); → should trigger __invoke
echo $foo->bar->baz(); → should trigger __call
All of them should return a string.
Can this be done in PHP completely? Thanks.
No can do.
The line in question is simply ambigous, and the error message shows you how ... It is more logical to try to access the baz() method of your $object object.
That's just the context given by the parser when it sees $object->baz()
As already mentioned in the comments, you can remove that ambiguity, help the parser by telling it that $object->baz is itself an expression that needs to be executed first:
($object->baz)('arg');
PHP is also itself a program, and has to know how to execute something before executing it. If it could blindly try every possible "magic" method on every object in a $foo->bar->baz->qux chain, then it wouldn't be able to tell you what the error is when it is encountered - it would just silently crash.
I have solved my problem by detecting the existence of an __invoke method inside the __call method of a class.
class MyStdClass extends stdClass {
protected $data = [];
public function __construct(array $array) {
$this->data = $array;
}
public function __get($key) {
return isset($this->data[$key]) ? $this->data[$key] : null;
}
public function __call($key, $args = []) {
if (isset($this->data[$key])) {
$test = $this->data[$key];
// not an object = not an instance, skip!
if (!is_object($test)) {
return $this->__get($key);
}
if (!empty($args) && get_class($test) && method_exists($test, '__invoke')) {
// or `return $test(...$args)`
return call_user_func([$test, '__invoke'], ...$args);
}
}
return $this->__get($key);
}
public function __set($key, $value = null) {
$this->data[$key] = $value;
}
public function __toString() {
return json_encode($this->data);
}
public function __isset($key) {}
public function __unset($key) {}
}
So, instead of converting the array into object with (object), here I use:
$object = new MyStdClass([
'foo' => 'bar',
'baz' => new Foo
]);

Modifying a PHP array element via magic methods (__get and __set)

I have the following class:
/**
* #property int $barMagic
*/
class Foo
{
public $barNormal;
private $attributes = [];
public function __get($name) {
return isset($this->attributes[$name]) ? $this->attributes[$name] : null;
}
public function __set($name, $value)
{
$this->attributes[$name] = $value;
}
}
As you can see, the $barMagic public property is not defined explicitly, it's accessed via the magic methods.
When setting and then modifying an array element in the normal attribute, it works fine:
$foo = new Foo();
$foo->barNormal = ['baz' => 1];
echo $foo->barNormal['baz'];
$foo->barNormal['baz'] = 2;
echo ',' . $foo->barNormal['baz'];
It outputs "1,2", just as intended.
But when using the magic property, it does not:
$foo = new Foo();
$foo->barMagic = ['baz' => 1];
echo $foo->barMagic['baz'];
$foo->barMagic['baz'] = 2;
echo ',' . $foo->barMagic['baz'];
It outputs "1,1"!
Is there a way in PHP to access array elements in magic properties the same way as normal ones?
The ArrayAccess interface seems to deal with array access one level higher than I need it.
The real answer is tricky and involves some bug/inconsistency in the PHP engine. As commentors suggested, I added the "&" (return by reference) character before __get(). So new code:
public function &__get($name) {
return isset($this->attributes[$name]) ? $this->attributes[$name] : null;
}
but this gives
Notice: Only variable references should be returned by reference in ....
I had to change it to
public function &__get($name) {
if (isset($this->attributes[$name])) {
return $this->attributes[$name];
} else {
return null;
}
}
and now it works. Note that the two snippets should be completely equivalent, but they are not. Thank you all for the contribution, you took me halfway there.

Accessing private/protected properties of an object in anonymous function in PHP

I'm trying to dump elements of an object's private property through an anonymous function - of course I could achieve this in any number of other ways, but this highlights a PHP conundrum I can't solve off the top of my head, short of $foo = $this and using $foo - but THAT won't give me the private stuff, so... suggestions ?
Sample code:
class MyClass
{
private $payload = Array( 'a' => 'A element', 'b' => 'B element');
static $csvOrder = Array('b','a');
public function toCSV(){
$values = array_map(
function($name) use ($this) { return $this->payload[$name]; },
self::$csvOrder
);
return implode(',',$values);
}
}
$mc = new MyClass();
print $mc->toCSV();
I believe there is absolutely no way to do directly what you propose.
However, you can work around it either by making the anonymous method a class method (this is not what you asked for, but it could be a practical solution) or pulling everything you need out of $this explicitly and passing the extracted values into the function:
class MyClass
{
private $payload = Array( 'a' => 'A element', 'b' => 'B element');
static $csvOrder = Array('b','a');
public function toCSV(){
$payload = $this->payload;
$values = array_map(
function($name) use ($payload) { return $payload[$name]; },
self::$csvOrder
);
return implode(',',$values);
}
}
You can hack around the limitation by creating a wrapper that utilizes Reflection to allow you to access all properties and methods. You can use it like this then:
$self = new FullAccessWrapper($this);
function () use ($self) { /* ... */ }
Here a sample implementation of the wrapper, taken from here:
class FullAccessWrapper
{
protected $_self;
protected $_refl;
public function __construct($self)
{
$this->_self = $self;
$this->_refl = new ReflectionObject($self);
}
public function __call($method, $args)
{
$mrefl = $this->_refl->getMethod($method);
$mrefl->setAccessible(true);
return $mrefl->invokeArgs($this->_self, $args);
}
public function __set($name, $value)
{
$prefl = $this->_refl->getProperty($name);
$prefl->setAccessible(true);
$prefl->setValue($this->_self, $value);
}
public function __get($name)
{
$prefl = $this->_refl->getProperty($name);
$prefl->setAccessible(true);
return $prefl->getValue($this->_self);
}
public function __isset($name)
{
$value = $this->__get($name);
return isset($value);
}
}
Obviously the above implementation doesn't cover all aspects (e.g. it can't use magic properties and methods).
As you said yourself, it is private and therefore in accessible.
You can:
Pass $this->payload as a parameter to the anonymous function.
Create a method in the class and use it instead.

How to get name of the constant?

Assuming you have a constant defined in a class:
class Foo {
const ERR_SOME_CONST = 6001;
function bar() {
$x = 6001;
// need to get 'ERR_SOME_CONST'
}
}
Is it possible with PHP?
You can get them with the reflection API
I'm assuming you would like to get the name of the constant based on the value of your variable (value of variable == value of constant). Get all the constants defined in the class, loop over them and compare the values of those constants with the value of your variable.
Note that with this approach you might get some other constant that the one you want, if there are two constants with the same value.
example:
class Foo {
const ERR_SOME_CONST = 6001;
const ERR_SOME_OTHER_CONST = 5001;
function bar() {
$x = 6001;
$fooClass = new ReflectionClass ( 'Foo' );
$constants = $fooClass->getConstants();
$constName = null;
foreach ( $constants as $name => $value )
{
if ( $value == $x )
{
$constName = $name;
break;
}
}
echo $constName;
}
}
ps: do you mind telling why you need this, as it seems very unusual ...
Here's what I did to achieve it. Inspired by Jan Hancic.
class ErrorCode
{
const COMMENT_NEWCOMMENT_DISABLED = -4;
const COMMENT_TIMEBETWEENPOST_ERROR = -3;
/**
* Get error message of a value. It's actually the constant's name
* #param integer $value
*
* #return string
*/
public static function getErrorMessage($value)
{
$class = new ReflectionClass(__CLASS__);
$constants = array_flip($class->getConstants());
return $constants[$value];
}
}
With Reflection:
$class = new ReflectionClass("Foo");
$constants = $class->getConstants();
$constants is an array which holds all the names and values of the constants defined in class Foo.
All the other answers cover the essential points. But, if crazy one liners is your thing, then:
function getConstantName($class, $value)
{
return array_flip((new \ReflectionClass($class))->getConstants())[$value];
}
If you need to handle the case where the value might not actually be one of the constants, then you can give up an extra line:
function getConstantName($class, $value)
{
$map = array_flip((new \ReflectionClass($class))->getConstants());
return (array_key_exists($value, $map) ? $map[$value] : null);
}
I know this is an old question and all, but I still feel that I have some useful input. I implemented this using an abstract class that all my enums extend. The abstract class contains a generic toString() method;
abstract class BaseEnum{
private final function __construct(){ }
public static function toString($val){
$tmp = new ReflectionClass(get_called_class());
$a = $tmp->getConstants();
$b = array_flip($a);
return ucfirst(strtolower($b[$val]));
}
}
//actual enum
final class UserType extends BaseEnum {
const ADMIN = 10;
const USER = 5;
const VIEWER = 0;
}
This way you can get a human readable string to use in output, on every enum that extends the base enum. Furthermore, your implementation of the enum, being final, cannot be extended and because the constructor in the BaseEnum is private it can never be instantiated.
So for instance, if you show a list of all usernames with their types you can do something like
foreach($users as $user){
echo "<li>{$user->name}, ".UserType::toString($user->usertype)."</li>";
}
All constant can be assigned to an array using this function.
$const = get_defined_constants();
then using following function you can print the array structure
echo "<pre>";
print_r($const);
and you can see more explanation here www.sugunan.com
Warning: This way you should NOT program... ( if youre not sure what youre doing :) )
I wrote 1 row which echos constants and their numeric values by your choice of CATEGORY_
so here is the list of CATEGORY_ ERR_
foreach(get_defined_constants() as $key => $value) if(strlen($key)>5) if(substr($key, 0,5)=="ERR_") echo"<br>Found an php ERR_ constant! : ".$key."=>".$value;
And if you want just the one youre looking for by number => I created 1row function:
//input parameters: CATEGORYNAME_ , #constantNumber
function getConstantName($category,$constantNumber){foreach(get_defined_constants() as $key => $value) if(strlen($key)>strlen($category)) if(substr($key, 0,strlen($category))==$category) if($value==$constantNumber) return $key; return "No constant found.";}
So for example some info constant with number 64:
echo "NameOfConstant: ".getConstantName("INFO_",64);
will output something like: NameOfConstant: INFO_LICENSE
OK, OK, I know everything is already covered :)
But Jan Hančič asked for use case, so I'll share mine.
Aside: everyone seems to use array_flip(). Why not array_search()?
I needed it in a class that extends \Exception and is base class of small set of my concrete exceptions. Each of those concrete exception classes covers a broad exception domain and has defined several precise exception causes. Reason? I don't want to have a horde of exceptions to maintain and remember of. Also, there is exception handler set that dumps exception's guts into log file - and it's here I need to get the constant name as trying to decipher exception cause from status in quite painful.
Examples from my CLI scripts:
class AbstractException extends Exception {
public function getName() {
return array_search($this->getCode(), (new ReflectionClass($this))->getConstants());
}
}
class SyntaxException extends AbstractException {
const BAD_SYNTAX = 90;
const REQUIRED_PARAM = 91;
const REQUIRED_VALUE = 92;
const VALUE_TYPE = 93;
const VALUE_OUT_OF_BOUNDS = 94;
public function __construct ($message = "", $code = self::BAD_SYNTAX, Exception $previous = NULL) {
$script = basename($GLOBALS['argv'][0]);
echo "Invalid syntax: $message \nSee: `$script --help` for more information\n";
parent::__construct($message, $code, $previous);
}
}
// in autoload include
set_exception_handler(function(Exception $e) {
error_log(basename($GLOBALS['argv'][0]) . ';'. date('Y-m-d H:i:s') .';'. $e->getName() .';'. $e->getMessage() .';'. $e->getFile() .';'. $e->getLine() ."\n", 3, 'error.log');
exit ($e->getCode());
});
class OrderStatus
{
public const PENDING = 1;
public const PROCESSED = 2;
public static function getStatusCode($value)
{
$class = new ReflectionClass(__CLASS__);
$constants = array_flip($class->getConstants());
return $constants[$value] ?? null;
}
// OrderStatus::getStatusCode(1); // 'PENDING'
}
if you need to get the constant value on a method of the same class, you just need to use the self operator. You could use reflection if you want to use the constants on another class
class Foo {
const ERR_SOME_CONST = 6001;
function bar() {
self::ERR_SOME_CONST;
}
}

How can I protect part of an array in php from being modified?

I have an array in php like this:
$myArray = array('name'=>'juank', 'age'=>26, 'config'=>array('usertype'=>'admin','etc'=>'bla bla'));
I need this array to be accesible along the script to allow changes in any field EXCEPT in the "config" field. Is there a way to protect an array or part of an array from being modified as if it where declared private inside a class? I tried defining it as a constant but it's value changes during script execution. Implementing it as a class would mean I'd have to rebuild the complete application from scratch :S
thanks!
I do not think you can do this using "pure" "real" arrays.
One way to get to this might be using some class that implements ArrayInterface ; you code would look like it's using arrays... But it would actually be using objects, with accessor methods that could forbid write-access to some data, I guess...
It would have you change a couple of things (creating a class, instanciating it) ; but not everything : access would still be using an array-like syntax.
Something like this might do the trick (adapted from the manual) :
class obj implements arrayaccess {
private $container = array();
public function __construct() {
$this->container = array(
"one" => 1,
"two" => 2,
"three" => 3,
);
}
public function offsetSet($offset, $value) {
if ($offset == 'one') {
throw new Exception('not allowed : ' . $offset);
}
$this->container[$offset] = $value;
}
public function offsetExists($offset) {
return isset($this->container[$offset]);
}
public function offsetUnset($offset) {
unset($this->container[$offset]);
}
public function offsetGet($offset) {
return isset($this->container[$offset]) ? $this->container[$offset] : null;
}
}
$a = new obj();
$a['two'] = 'glop'; // OK
var_dump($a['two']); // string 'glop' (length=4)
$a['one'] = 'boum'; // Exception: not allowed : one
You have to instanciate an object with new, which is not very array-like... But, after that, you can use it as an array.
And when trying to write to an "locked" property, you can throw an Exception, or something like that -- btw, declaring a new Exception class, like ForbiddenWriteException, would be better : would allow to catch those specifically :-)
You could make the array private and create a method to modify its contents that will check if someone doesn't try to overwrite the config key.
<?php
class MyClass {
private static $myArray = array(
'config' => array(...),
'name' => ...,
...
);
public static function setMyArray($key, $value) {
if ($key != 'config') {
$this::myArray[$key] = $value;
}
}
}
Then when you want to modify the array you call:
MyClass::setMyArray('foo', 'bar'); // this will work
MyClass::setMyArray('config', 'bar'); // this will be ignored
No, unfortunately there isn't a way to do what you're describing. Variables don't have any concept of public or private unless they are encapsulated within an object.
Your best solution is unfortunately to re-work the configuration into an object format. You might be able to use a small object inside your array that contains the private settings, which might allow you to only have to update a few places in your code, depending where that portion of the array is used.

Categories