So I'm working with a bag class, and I'm trying to dynamically create variables from the bag class.
Here's what I have:
class BagFoo
{
public $a;
public $b;
public $c;
}
class Bar
{
private $bagFoo;
public function output()
{
foreach($this->bagFoo as $key => $value)
{
$$key = $value;
}
//output then happens here.
}
}
This allows me to call $a instead of $this->bagFoo->getA(); which I rather like, but the problem is I have to expose the member variables to implement it. I'd like to have the same dynamic variable assignment, but access the member variables through a getter, instead of accessing directly.
Solutions I've though of and didn't really like:
Having a getVars() function in BagFoo that would return an array of var names and their values, and then iterating through that.
Calling get_class_methods() and then doing parsing and iterating through the getters (ew).
I'm sure there's a way to do what I'm trying in a more elegant form, but I just can't think of how to implement it.
Your code would probably be more understandable if you just used an associative array to store your values.
class Bar
{
private $bagFoo = [];
public function __construct($arr)
{
$this->bagFoo = $arr;
foreach($arr as $key => $value)
{
$$key = $value;
}
echo $a; //echos 'aaaa'
}
}
$bar = new Bar([
'a' => 'aaaa',
'b' => 'bbbb',
'c' => 'cccc'
]);
Word of advice: Be very careful using $$ because you can overwrite variables in the current scope and cause all kinds of problems in your application. For example:
$bar = new Bar([
'_SERVER' => 'broken server vars!',
'_COOKIE' => 'broken cookies!',
'arr' => 'broken iterator!',
]);
Related
How to convert (cast) Object to Array without Class Name prefix in PHP?
class Teste{
private $a;
private $b;
function __construct($a, $b) {
$this->a = $a;
$this->b = $b;
}
}
var_dump((array)(new Teste('foo','bar')));
Result:
array
'�Teste�a' => string 'foo' (length=3)
'�Teste�b' => string 'bar' (length=3)
Expected:
array (
a => 'foo'
b => 'bar' )
From the manual:
If an object is converted to an array, the result is an array whose elements are the object's properties. The keys are the member variable names, with a few notable exceptions: integer properties are unaccessible; private variables have the class name prepended to the variable name; protected variables have a '*' prepended to the variable name. These prepended values have null bytes on either side. This can result in some unexpected behaviour:
You can therefore work around the issue like this:
$temp = (array)(new Teste('foo','bar'));
$array = array();
foreach ($temp as $k => $v) {
$k = preg_match('/^\x00(?:.*?)\x00(.+)/', $k, $matches) ? $matches[1] : $k;
$array[$k] = $v;
}
var_dump($array);
It does seem odd that there is no way to control/disable this behaviour, since there is no risk of collisions.
The "class name prefix" is part of the (internal) name of the property. Because you declared both as private PHP needs something to distinguish this from properties $a and $b of any subclass.
The easiest way to bypass it: Don't make them private. You can declare them as protected instead.
However, this isn't a solution in every case, because usually one declares something as private with an intention. I recommend to implement a method, that makes the conversion for you. This gives you even more control on how the resulting array looks like
public function toArray() {
return array(
'a' => $this->a,
'b' => $this->b
);
}
As far as I know, PHP doesn't have a simple way to do what you want. Most languages don't. You should look into reflection. Take a look at this document: http://www.php.net/manual/en/reflectionclass.getproperties.php
I've made a function that should work as expected:
function objectToArr($obj)
{
$result = array();
ReflectionClass $cls = new ReflectionClass($obj);
$props = $cls->getProperties();
foreach ($props as $prop)
{
$result[$prop->getName()] = $prop->getValue($obj);
}
}
You can use Reflection to solve this task. But as usual this is a strong indicator that your class design is somewhat broken. However:
function objectToArray($obj) {
// Create a reflection object
$refl = new ReflectionClass($obj);
// Retrieve the properties and strip the ReflectionProperty objects down
// to their values, accessing even private members.
return array_map(function($prop) use ($obj) {
$prop->setAccessible(true);
return $prop->getValue($obj);
}, $refl->getProperties());
}
// Usage:
$arr = objectToArray( new Foo() );
class Util_Model
{
/**
* Get model property by property name chain.
* Usage: Util_Model::get_prop($order, 'item', 'name')
*/
public static function get_prop()
{
$obj = func_get_arg(0);
$props = array_slice(func_get_args(), 1);
if (!is_object($obj)) {
throw new \InvalidArgumentException('First parameter must be an object');
}
foreach ($props as $prop) {
if (preg_match('/^(.*)\(\)$/', $prop, $matches)) {
$obj = call_user_func(array($obj, $matches[1]));
} else {
$obj = $obj->{$prop};
}
if (!is_object($obj)) {
break;
}
}
return is_object($obj) ? (string)$obj : $obj;
}
}
$obj->{$prop} i wonder the meaning of this line, why there is a brace here? and why there is no error when {$prop} is null.if you don't understand my question, leave something I will amend it.thanks!
$obj->{$prop} means that $obj is trying to access a property whose name is present in the variable $prop. I'll explain with an example
class A {
public $d;
public $e;
public $f;
function X() {}
function Y() {}
function Z() {}
}
$obj = new A();
$prop = 'X';
$propVar = 'f';
$obj->{$prop}();
$obj->{$propVar};
In the above code, $prop contains the value 'X', so function X will be invoked, likewise if it was containing values 'Y' or 'Z', they would be invoked. So the invocation of function can be decided at runtime depending on the value the variable contains.
As for the case when $prop is null, no object is being accessed, so the reference of object is returned instead and no error is thrown.
It's the preferred syntax when using Variable Variables when accessing object properties. In this example, the brackets aren't required, though. Their main purpose is to avoid ambiguity:
$obj = (object) array(
'foo' => array('key' => 123)
);
$access = array('key' => 'foo');
var_dump($obj->{$access['key']});//will dump array(key => 123)
var_dump($obj->$access['key']);//ambiguous
The latter is ambiguous, because PHP might take the statement to mean "convert $access to its string value (which is Array), access the property with that name, and get the index key from that value", or it could mean, access the property with the name you find under $access['key'].
Either way, this can be useful, your code is taking it too far. You calling methods like this is not a good idea.
A valid use-case could be when dealing with JSON encoded data, or a parsed XML DOM, where there are numeric keys, or keys like foo-bar, you can't write:
$obj->123;
$obj->foo-bar;//- is invalid
For these cases, you use the variable variable notation:
$keys = [123, 'foo-bar'];
foreach ($keys as $key)
echo $obj->{$key}, PHP_EOL;
I'm trying to build a __construct function for my class. This function should get All $_REQUEST values and store them in an array so they can be called later by id. The problem is that it doesn't store my variables and I don't know why.
Seeing this is my first "serious" attempt at a construct, it's most probably my stupidity. But I'd like to know what that is.
Class Regex {
private static $requests = array();
function __construct() {
foreach($_REQUEST as $key => $value) {
self::$requests[$key] = array(
'value' => $value,
'status' => false,
'errorList' => array()
);
}
}
public static function preg($key, $rules) {
var_dump(self::$requests); // for test purpose
}
}
The result of above is: array (size=0) empty.
Are you even calling the constructor? A constructor is only called when calling it either explicitly or via the new keyword).
PHP doesn't have anything like static constructors like Java has.
You have to ensure that the array is filled at the first access to preg() method:
public static function preg($key, $rules) {
if (empty(self::$requests)) {
foreach($_REQUEST as $key => $value) {
self::$requests[$key] = array(
'value' => $value,
'status' => false,
'errorList' => array()
);
}
}
var_dump(self::$requests); // for test purpose
}
the constructor of your Regex class is called upon creating a new regex object like so:
$regex = new Regex;
but you never create a Regex object so the constructor is never called, resulting in an empty $requests array.
You work with a static function. I think you don't call the construct method. The __construct function is called if you make a new instance.
$regex = new Regex;
If you call the static class for example Regex::preg the contructor is not called.
Expanding upon what bwoebi has answered, you can still get the intended results simply by adding a call to the static function preg from the constructor itself as you can see bellow:
That said, this is just adding more bloat without any real benefit.
Whilst this could come in useful in certain cases, and I only added it within this answer as a mean to display that your current structure could work with the simple addition of two simple lines, I would recommend going with #bwoebi's answer but to keep in mind that whilst the controller is initself not static, it does not in any way or form stop it from communicating with static methods.
PHP:
class Regex {
private static $requests = array();
function __construct(){
if (empty(self::$requests)) {
foreach($_REQUEST as $key => $value) {
self::$requests[$key] = array(
'value' => $value,
'status' => false,
'errorList' => array()
);
}
}
self::preg();
}
public static function preg(){
var_dump(self::$requests); // for test purpose
}
}
$var = new Regex();
I'm having the same problem as this guy with the application I'm writing right now. The problem is that static properties are not being inherited in subclasses, and so if I use the static:: keyword in my main class, it sets the variable in my main class as well.
It works if I redeclare the static variables in my subclass, but I expect to have a large number of static properties and subclasses and wish to avoid code duplication. The top-rated response on the page I linked has a link to a few "workarounds", but it seems to have 404'd. Can anyone lend me some help or perhaps point me in the direction of said workarounds?
I'm not sure what specific workaround it was talking about, I can think of quite a few that can work. I personally wouldn't use these in any code. I'd recommend you take a look Is it possible to overuse late static binding in PHP? and rethink if there's a better way to do whatever you hope accomplish.
Also keep in mind my code is completely untested as I wrote it just here.
Method 1: Always use an array to figure
All the other methods are based on this one. Whereever you use the static property, you insert the code to detect the class and get it. I would only consider this if you never plan on using the property anywhere else.
$class = get_called_class();
if(isset(self::$_names[$class])) {
return self::$_names[$class];
}
else {
return static::NAME_DEFAULT;
}
Method 2: Use a getter/setting methods
If you plan on having it used in more than one spot, this method would be better. Some singleton patterns use a similar method.
<?php
class SomeParent {
const NAME_DEFAULT = 'Whatever defaults here';
private static $_names = array();
static function getName($property) {
$class = get_called_class();
if(isset(self::$_names[$class])) {
$name self::$_names[$class];
}
else {
$name = "Kandy"; // use some sort of default value
}
}
static function setName($value) {
$class = get_called_class();
self::$_names[$class] = $value;
}
}
Method 3: __callStatic
This is by far the most convenient method. However you need to have an object instance to use it (__get and __set can't be used statically). It is also slowest the method (much slower than the other two). I'm guessing that since you're already using static properties this is already a non-option. (If this method works for you, it'd be probably be better if you didn't use static properties)
<?php
class SomeParent {
const NAME_DEFAULT = 'Whatever defaults here';
private static $_names = array();
function __get($property) {
if($property == 'name') {
$class = get_called_class();
if(isset(self::$_names[$class])) {
return self::$_names[$class];
}
else {
return static::NAME_DEFAULT;
}
}
// should probably trigger some sort of error here
}
function __set($property, $value) {
if($property == 'name') {
$class = get_called_class();
self::$_names[$class] = $value;
}
else {
static::$property = $value;
}
}
}
To go a little further than Reece45's answer, you can use the following method to get the value of your array.
<?php
class MyParent {
public static $config = array('a' => 1, 'b' => 2);
public static function getConfig() {
$ret = array();
$c = get_called_class();
do {
$ret = array_merge($c::$config, $ret);
} while(($c = get_parent_class($c)) !== false);
return $ret;
}
}
class MyChild extends MyParent {
public static $config = array('a' => 5, 'c' => 3, 'd' => 4);
public function myMethod($config) {
$config = array_merge(self::getConfig(), $config);
}
}
class SubChild extends MyChild {
public static $config = array('e' => 7);
}
var_export(MyChild::getConfig());
// result: array ( 'a' => 5, 'b' => 2, 'c' => 3, 'd' => 4, )
$mc = new MyChild();
var_export($mc->myMethod(array('b' => 6)));
// result: array ( 'a' => 5, 'b' => 6, 'c' => 3, 'd' => 4, )
var_export(SubChild::getConfig());
// result: array ( 'a' => 5, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 7, )
To those that end up here wondering "WTF PHP", it seems there are a couple of reasons for this behavior and why it was kept, albeit weird:
static properties will always use the same memory reference, just like static vars do (source)
that same reference is shared between classes and subclasses (source)
it seems to be useful in some other contexts, so it isn't a "full" bug, just undocumented behavior. If it got "fixed", it would then cause compatibility issues with previously working code (a backwards-compatibility break).
There are two questions left, though:
why wouldn't late static bindings change that: probably related to #1
why that drawback we see isn't explained in the doc pages................ Well, that's PHP, right?
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.