If i use magic __set to set a value to private var how could i set a var as an array ?
Im thinking of something like this, pretend i have a class with __get __set
$myclass->names = 'Name'; // Works
$myclass->names = array('n1'=>'Name1', 'n2' => 'Name2'); // works as well
//this does not work
$myclass->names['n1'] = 'Name1';
$myclass->names['n2'] = 'Name2';
Its the 2 last examples i want to get to work. Have tested various ways but cant figure it out.
You obviously don't output notices, otherwise you'd have gotten the error
Notice: Indirect modification of overloaded property Foo::$bar has no
effect
What you're trying to do is simply not possible. There is exactly one way to make arrays received through __get writable, but that is most likely not what you want.
<?php
class Foo {
protected $bar = array();
public function &__get($name) {
return $this->$name;
}
public function __set($name, $value) {
return $this->$name = $value;
}
}
$foo = new Foo();
$foo->bar = array('a', 'b', 'c');
echo $foo->bar[0]; // output "a"
$foo->bar[0] = 'z'; // fires warning
echo $foo->bar[0]; // output "z"
// all fine, but here's the catch:
$t =& $foo->bar;
$t = array('y');
echo $foo->bar[0]; // output "y"
Now that you've seen how returning values by reference can be a problem, you may be interested in ArrayObject. Something like
<?php
class Foo {
protected $bar = array();
public function __get($name) {
return new ArrayObject(&$this->$name);
}
public function __set($name, $value) {
return $this->$name = $value;
}
}
$foo = new Foo();
$foo->bar = array('a', 'b', 'c');
echo $foo->bar[0]; // output "a"
$foo->bar[0] = 'z'; // fires warning
echo $foo->bar[0]; // output "z"
// all fine, and no catch
$t =& $foo->bar;
$t = array('y');
echo $foo->bar[0]; // still outputs "z"
It won't work. $class->arr['key'] will execute the getter. So basically, what your code will look like is:
array('key' => 'value')['key'] = 'new value';
Which, obviously, does nothing. If you want that to work, you will have to declare the names as a public property.
This expression will invoke the getter:
$myclass->names['n1'] = 'Name1';
^^^^^^^^^^^^^^^
needs to be get
^^^^^^^^^^^^^^^^
assignment later
The only way to make that work is a fugly workaround. By letting the getter return an reference to the know array the following assignment could work.
function & __get($name) {
if (is_array($this->$name)) {
return & $this->$name;
}
else ...
}
So it's really only advisable if it significantly simplifies your API.
Try this code:
class Foo
{
private $bar;
public function __construct()
{
$this->bar = new ArrayObject(array());
}
public function __get($item)
{
if(property_exists($this, $item)) {
return $this->$item;
}
}
public function __set($item, $value)
{
if(property_exists($this, $item)) {
$this->{$item} = $value;
}
}
}
$obj = new Foo();
$obj->bar['color'] = 'green';
foreach($obj->bar as $attribute => $value) {
echo '<p>' . $attribute . ' : ' . $value . '</p>' . PHP_EOL;
}
// output => color : green
Related
I wrote the __set in order to make sure something is done before setter anything
public function __setter($name, $value) {
if ($this->needDoSomething) {$this->doSomeThingNecessaryBeforeSetAnything();}
$this->needDoSomething = false;
$this->$name = $value;
}
However, the magic method will impact performance. In the same class, I have another function
private function loadData() {
if ($this->needDoSomething) {$this->doSomeThingNecessaryBeforeSetAnything();}
foreach ($data as $key=>$value) {
$this->$key = $value;
}
}
Since the doSomeThingNecessaryBeforeSetAnything() is already called, I don't need to call __set, but would like to set the property directly. This will largely help on performance.
However, I cannot remove the __set, because there are lot's of legacy code outside the class, and I need it to make the logic correct.
Seems with PHP I cannot add or remove methods to object on the fly. Any ideas?
Edit: The performance is caused by __set itself, because I have large number of objects and each have large number of properties to set. The code below shows __set is 6 times slower than set properties directly.
class Test {}
class Test2 {
public function __set($name, $value) {
$this->$name = $value;
}
}
function runOnObject($o) {
$t = microtime(true);
for ($i=0; $i<100000; $i++) {
$prop = "prop{$i}";
$o->$prop = $i;
}
echo "".(microtime(true) - $t)." second ";
}
echo runOnObject(new Test()). "(With out __set)<p>";
echo runOnObject(new Test2()). "(With __set)";
The result:
0.084139823913574 second (With out __set)
0.47258400917053 second (With __set)
If you add a __get, you can store the properties in a private data structure (eg. an array), allowing direct access to the data from within the class, while still maintaining the same public interface.
Like this:
class Container
{
private $properties = array();
public function __set($key, $value)
{
$this->doSomethingExpensive();
$this->properties[$key] = $value;
}
public function __get($key)
{
if (!isset($this->properties[$key])) {
throw new Exception('Invalid Property ' . $key);
}
return $this->properties[$key];
}
public function loadData($data)
{
$this->doSomethingExpensive();
foreach ($data as $key => $value) {
$this->properties[$key] = $value;
}
}
private function doSomethingExpensive()
{
echo 'Doing Work...' . PHP_EOL;
}
}
// Example
$c = new Container();
$c->loadData(array(
'alpha' => 'A',
'beta' => 'D',
'charlie' => 'C',
'delta' => 'D'
));
var_dump($c->alpha, $c->beta);
If this will be any faster, I don't know, it depends on your specific use case as you avoid running the "expensive" code repeatedly, but there will be some overhead from using __get.
Obviously not exactly what you want, but another thought... Use ArrayAccess for the dynamic set functionality instead of __set. Though you'll have to update the 'tons' of client code (not sure how unrealistic that may be).
<?php
class A implements ArrayAccess
{
private $_doThings = false;
public function __construct() {
$this->loadData();
}
public function offsetExists($k) { return isset($this->$k); }
public function offsetGet($k) { return $this->$k; }
public function offsetUnset($k) { unset($this->$k); }
public function offsetSet($k, $v) {
if($this->_doThings)
$this->doSomeThingNecessaryBeforeSetAnything();
$this->$k = $v;
}
private function doSomeThingNecessaryBeforeSetAnything() {
echo __METHOD__ . PHP_EOL;
}
private function loadData() {
$this->doSomeThingNecessaryBeforeSetAnything();
$this->_doThings = false;
foreach (array('a' => 1, 'b' => 2, 'c' => 3) as $key=>$value) {
$this->$key = $value;
}
}
}
Demo code
$a = new A();
// Need to change all calls like $a->c = 4 to $a['c'] = 4
$a['c'] = 4;
var_dump($a);
So there's a painful code change required, but you get the best of both worlds. The dynamic behavior, and the performance.
Here's a little mock-up to describe my predicament:
<?php
$var = "Before";
function getVar(){
global $var;
return $var;
}
$array = Array(
"variable" => "Var = " . getVar()
);
$var = "After";
echo $array['variable'];
?>
That code would echo 'Before', I'm aiming for it to echo 'after'. I realize that this is how PHP is supposed to work however it's crucial for the array to execute getVar() only when it's called.
How would I go about doing this?
You can not do this since array declaration will initialize it - so you're mixing function calling at array's 'usage' and at it's definition. There's no 'usage': array is already defined to that moment.
However, an answer could be using ArrayAccess, like this:
class XArray implements ArrayAccess
{
private $storage = [];
public function __construct()
{
$this->storage = func_get_args();
}
public function offsetSet($offset, $value)
{
if(is_null($offset))
{
$this->storage[] = $value;
}
else
{
$this->storage[$offset] = $value;
}
}
public function offsetExists($offset)
{
return isset($this->storage[$offset]);
}
public function offsetUnset($offset)
{
unset($this->storage[$offset]);
}
public function offsetGet($offset)
{
if(!isset($this->storage[$offset]))
{
return null;
}
return is_callable($this->storage[$offset])?
call_user_func($this->storage[$offset]):
$this->storage[$offset];
}
}
function getVar()
{
global $var;
return $var;
}
$var = 'Before Init';
$array = new XArray('foo', 'getVar', 'bar');
$var = 'After Init';
var_dump($array[1]);//'After Init'
-i.e. try to call data, which is inside element, when actual get happened. You may want to have different constructor (for associative arrays) - but the general idea was shown.
Editing my answer after the question was edited.
No, what you are trying to achieve isn't possible because when you call the function it returns and it's done at that point. But you could achieve something similar with object oriented coding. I'll create something for you, please wait.
<?php
class Foo {
public function __toString() {
global $var;
return "Var = {$var}";
}
}
$var = "Before";
$array = array( "variable" => new Foo() );
$var = "After";
echo $array['variable'];
?>
PS: Sorry for the late answer, but there was a blackout in Salzburg. :(
It occurred to me that you could also use anonymous functions and invoke/execute those
Proof of concept:
$var = "Before";
function getVar(){
global $var;
return $var;
}
$array = Array(
"variable" => create_function(null, "return 'Var = ' . getVar();")
);
$var = "After";
echo $array['variable']();
returns
Var = After
Is PHP exists a function that detect the change of variable?
That is something like this:
//called when $a is changed.
function variableChanged($value) {
echo "value changed to " . $value;
}
$a = 1;
//attach the variable to the method.
$a.attachTo("variableChanged");
$a = 2;
$a = 3;
//expected output:
//value changed to 2
//value changed to 3
I know that it is easy to achieve if I use the "setter" method. But since I am working on some existing codes, I am not able to modify them. Can somebody tell me how to achieve my purpose? Thanks.
know that it is easy to achieve if I use the "setter" method. But since I am working on some existing codes, I am not able to modify them.
I assume that you can change some code, but not the object / class you are working with. If you cannot change any code at all this question would be useless.
What you can do is make your own class, extending the class you are working with, and adding your setter there. For all purposes you can not-override the parent setting, except for a magic setter on whatever you need to track. Track changes and then call the parent functions, so no changes in any other internal workings will be in effect.
This could only be achieved by wrapping your variable within a class, and implementing a onchange yourself.
ie.
class MyVarContainer {
var $internalVar = array();
function __get($name) {
return !empty($this->internalVar[$name]) $this->internalVar[$name] ? FALSE;
}
function __set($name, $value) {
$oldval = $this->$name;
$this->internalVar[$name] = $value;
if($oldval !== FALSE) {
onUpdated($name, $oldval, $value);
} else {
onCreated($name, $value);
}
}
function onCreated($name, $value) {
}
function onUpdated($name, $oldvalue, $newvalue) {
}
}
You could revised your code as simple like this just to produce that expected output you want.
function variableChanged($value) {
return "value changed to " . $value;
}
$a = 1;
echo $a = variableChanged(2);
echo '<br/>';
echo $a = variablechanged(3);
=================
//output
value changed to 2
value changed to 3
or using a class like this....
class VariableHandler{
private $Variable;
function setVariable($initialValue = NULL){
$this->Variable = $initialValue;
return $initialValue;
}
function changeValue($newValue = NULL){
$this->Variable = $newValue;
return "value has change to ". $newValue;
}
}
$var = new VariableHandler;
echo $a = $var->setVariable(1);
echo '<br/>';
echo $var->changeValue(2);
echo '<br/>';
echo $var->changeValue(3);
=================
//output
value changed to 2
value changed to 3
Besides using a debugger:
The SplObserver interface is used alongside SplSubject to implement
the Observer Design Pattern.
http://www.php.net/manual/en/class.splobserver.php
Or the magic methods __get() and __set(): Encapsulating the variable into a class, you could implement a event handler yourself and register the change of a variable. Also you could attach callbacks like here:
<?php
header("content-type: text/plain");
class WatchVar {
private $data = array();
private $org = array();
private $callbacks = array();
public function __set($name, $value) {
if (!array_key_exists($name, $this->data)) {
$this->org[$name] = $value;
} else {
//variable gets changed again!
$this->triggerChangedEvent($name, $value);
}
$this->data[$name] = $value;
}
public function &__get($name) {
if (array_key_exists($name, $this->data)) {
if ($this->data[$name] != $this->org[$name]) {
//variable has changed, return original
//return $this->org[$name];
//or return new state:
return $this->data[$name];
} else {
//variable has not changed
return $this->data[$name];
}
}
}
public function addCallback($name, $lambdaFunc) {
$this->callbacks[$name] = $lambdaFunc;
}
protected function triggerChangedEvent($name, $value) {
//$this->data[$name] has been changed!
//callback call like:
call_user_func($this->callbacks[$name], $value);
}
}
$test = new WatchVar;
$test->addCallback('xxx', function($newValue) { echo "xxx has changed to {$newValue}\n"; });
$test->xxx = "aaa";
echo $test->xxx . "\n";
//output: aaa
$test->xxx = "bbb";
//output: xxx has changed to bbb
echo $test->xxx . "\n";
//output bbb
function messyFunction(&$var) {
$var = "test";
}
messyFunction($test->xxx);
//output:
i have something like this:
class foo
{
//code
}
$var = new foo();
$var->newVariable = 1; // create foo->newVariable
$var->otherVariable = "hello, im a variable"; //create foo->otherVariable
i can get in class foo a list of all variables defined outside by user (newVariable, otherVariable,etc)? Like this:
class foo
{
public function getUserDefined()
{
// code
}
}
$var = new foo();
$var->newVariable = 1; // create foo->newVariable
$var->otherVariable = "hello, im a variable"; //create foo->otherVariable
var_dump($var->getUserDefined()); // returns array ("newVariable","otherVariable");
Thanks!.
Yes, using get_object_vars() and get_class_vars():
class A {
var $hello = 'world';
}
$a = new A();
$a->another = 'variable';
echo var_dump(get_object_vars($a));
echo '<hr />';
// Then, you can strip off default properties using get_class_vars('A');
$b = get_object_vars($a);
$c = get_class_vars('A');
foreach ($b as $key => $value) {
if (!array_key_exists($key,$c)) echo $key . ' => ' . $value . '<br />';
}
What is your goal? Imo it's not very good practice (unless you really know what you are doing). Maybe it's good idea consider create some class property like "$parameters" and then create setter and getter for this and use it in this way:
class foo {
private $variables;
public function addVariable($key, $value) {
$this->variables[$key] = $value;
}
public function getVariable($key) {
return $this->variables[$key];
}
public function hasVariable($key) {
return isset($this->variables[$key]);
}
(...)
}
$var = new foo();
$var->addVariable('newVariable', 1);
$var->addVariable('otherVariable', "hello, im a variable");
And then you can use it whatever you want, for example get defined variable:
$var->getVariable('otherVariable');
To check if some var is already defined:
$var->hasVariable('someVariable')
get_class_vars() http://php.net/manual/en/function.get-class-vars.php
You question is not clear though.
$var->newVariable = 1;
there are two possible contex of above expression
1) you are accessing class public variables.
like
class foo
{
public $foo;
public function method()
{
//code
}
}
$obj_foo = new foo();
$obj_foo->foo = 'class variable';
OR
2) you are defining class variable runtime using _get and _set
class foo
{
public $foo;
public $array = array();
public function method()
{
//code
}
public function __get()
{
//some code
}
public function __set()
{
// some code
}
}
$obj_foo = new foo();
$obj_foo->bar= 'define class variable outside the class';
so in which context your question is talking about?
I am trying to do something with variable variables and I got stuck on an object problem. Imagine this class setup:
class A
{
public $field = 10;
}
class B
{
public $a;
public function __construct()
{
$this->a = new A();
}
}
Now everyone knows that this pice of code works:
$a = new A();
$var = 'field';
echo $a->$var; // this will echo 10
Is there any possibility I could make something like this work?:
$b = new B();
$var = 'a->field';
echo $b->$var; // this fails
Note: any option which does not use eval function?
How about using a closure?
$getAField = function($b) {
return $b->a->field;
};
$b = new B();
echo $getAField($b);
Though, it's only possible in newer versions of PHP.
Or, as a more generic version, something like this:
function getInnerField($b, $path) { // $path is an array representing chain of member names
foreach($path as $v)
$b = $b->$v;
return $b;
}
$b = new B();
echo getInnerField($b, array("a", "field"));
You can write a custom __get method on your class to access the childrens property. This works:
class A
{
public $field = 10;
}
class B
{
public $a;
public function __construct()
{
$this->a = new A();
}
public function __get($property) {
$scope = $this;
foreach (explode('->', $property) as $child) {
if (isset($scope->$child)) {
$scope = $scope->$child;
} else {
throw new Exception('Property ' . $property . ' is not a property of this object');
}
}
return $scope;
}
}
$b = new B();
$var = 'a->field';
echo $b->$var;
Hope that helps
I don't recommend it, but you could use eval:
$b = new B();
$var = 'a->field';
eval( 'echo $b->$'.$var );
This should also work I guess:
$b = new B();
$var1 = 'a';
$var2 = 'field'
echo ($b->$var1)->$var2;