PHP Magic Methods with unexpected behaviour - php

<?php
class MyClass{
private $props;
public function __constructor(){
$this->props = array();
}
public function __get($field) {
return $this->props[$field];
}
public function __set($field, $value) {
$this->props[$field] = $value;
}
}
$myInstance = new MyClass();
$myInstance->a = "Some string";
echo "Property 'a' has value ",$myInstance->a,"<br />"; // First echo
echo "Is property 'a' empty? ",(empty($myInstance->a) ? "Yes, but it shouldn't!" : "No, as expected"); // Second echo
// Output:
// Property 'a' has value Some string
// Is property 'a' empty? Yes, but it shouldn't!
$temp = $myInstance->a;
echo 'Variable $temp has value ',$temp,"<br />";
echo 'Is variable $temp empty? ',(empty($temp) ? "Yes, but it shouldn't!" : "No, as expected");
// Output:
// Variable $temp has value Some string
// Is variable $temp empty? No, as expected
?>
I've made this small class and code to ilustrate exactly what is my problem.
As you can see, my class has one property called $props. This property will be a array with all other properties.
My array $props will be populated with Magic Method __set, when a instance of this class has an atribution at a nonexistent property (http://php.net/manual/en/language.oop5.magic.php).
This part works properly.
When I want to read a nonexistent property, PHP should call MyClass's __get method and return the value stored in $props property.
It works fine in first echo (value is correctly returned).
But, if I use this reading action when I'm sending its return to one empty() function, it always returns TRUE, as you can see in second echo's return (in other words, php seems can't read $props property properly)
If I store property's value in a $temp variable and use this variable, my problem is solved, BUT I don't want to just solve this problem. I want to understand what is happening here.
Is it a PHP bug on Magic Methods?
Is it a empty funcion bug?
Is my code doing something meaningless? (Most probable answer)
Before you ask: I'm running my php server on a Windows machine and my php version is 5.3.8
Thanks in advance.

Because empty check if a variable exists and then if it is empty. If it doesn't exists (in your case because you're not passing it a variable), then it returns true
Checkout the doc page: http://php.net/manual/en/function.empty.php

Related

PHP/Magento: empty($object->property) returns true despite property is String

I set some value with Magento function setData:
$this->getChild('childBlockName')->setData('search_field_value', $this->__('field value'));
in parent .phtml file.
Then, in child .phtml file I try to get the value:
echo $this->search_field_value.'<br />';
$testvar = empty($this->search_field_value) ? 'empty value':'non empty value';
echo $testvar;
echoes:
'field value'
'empty value'
and:
$testvar = $this->search_field_value;
$testvar2 = empty($testvar) ? 'empty value':'non empty value';
echo $testvar2;
echoes:
'non empty value'
Why the object's property is being recognized as non-empty only after assigning it to a variable?
here is problem with php function
empty()
that function accept only parameter. If you have some function like:
function getValue(){
return "hallo";
}
and then try to do something like:
echo empty(getValue());
you will get error, but if you try to do:
$val = getValue();
echo empty($val);
you will get right answer without error. That means the function empty() doesn't except function return value, only variable.
I hope that it helps.
explanation for magento:
yes, you are right, your code works fine, but in magento is a bit different then in your example, because magento actually uses magic functions and array:
when you set virtual variable in magento, it actually becomes part of array, not just virtual variable like in your example. If you take a look in magento function
public function setData($key, $value=null);
there is a line where you add your value in _data array:
$this->_data[$key] = $value;
so, when you do something like this:
$this->search_field_value
you are actually calling magic method __call and that function searches in _data array the entered value, in our case search_field_value. This is reason why magento returns empty.

PHP returning by reference not working with normal functions but working with OOP

If I try this code :
<?php
class ref
{
public $reff = "original ";
public function &get_reff()
{
return $this->reff;
}
public function get_reff2()
{
return $this->reff;
}
}
$thereffc = new ref;
$aa =& $thereffc->get_reff();
echo $aa;
$aa = " the changed value ";
echo $thereffc->get_reff(); // says "the changed value "
echo $thereffc->reff; // same thing
?>
Then returning by reference works and the value of the object property $reff gets changed as the variable $aa that references it changes too.
However, when I try this on a normal function that is not inside a class, it won't work !!
I tried this code :
<?php
function &foo()
{
$param = " the first <br>";
return $param;
}
$a = & foo();
$a = " the second <br>";
echo foo(); // just says "the first" !!!
it looks like the function foo() wont recognize it returns by reference and stubbornly returns what it wants !!!
Does returning by reference work only in OOP context ??
That is because a function's scope collapses when the function call completes and the function local reference to the variable is unset. Any subsequent calls to the function create a new $param variable.
Even if that where not the case in the function you are reassigning the variable to the first <br> with each invocation of the function.
If you want proof that the return by reference works use the static keyword to give the function variable a persistent state.
See this example
function &test(){
static $param = "Hello\n";
return $param;
}
$a = &test();
echo $a;
$a = "Goodbye\n";
echo test();
Echo's
Hello
Goodbye
Does returning by reference work only in OOP context ??
No. PHP makes no difference if that is a function or a class method, returning by reference always works.
That you ask indicates you might have not have understood fully what references in PHP are, which - as we all know - can happen. I suggest you read the whole topic in the PHP manual and at least two more sources by different authors. It's a complicated topic.
In your example, take care which reference you return here btw. You set $param to that value - always - when you call the function, so the function returns a reference to that newly set variable.
So this is more a question of variable scope you ask here:
Variable scope

Overloading in php

Can any one tell what does below given lines means?
The lines below are copied from PHP manual:
Note:
It is not possible to use overloaded properties in other language
constructs than isset(). This means if empty() is called on an
overloaded property, the overloaded method is not called.
To workaround that limitation, the overloaded property must be copied
into a local variable in the scope and then be handed to empty().
BUT this is not true that we cant call empty() on overloaded properties, when i called empty() , it triggered __isset()
It is a documentation bug:
<?php
class PropNameReturn {
function __isset($propname){
return true;
}
function __get($propname){
echo 'Yes, it IS called!'.PHP_EOL;
return $propname;
}
}
$o = new PropNameReturn();
var_dump(empty($o->this_prop_name));
//Yes, it IS called!
//bool(false)
$b = new stdClass();
var_dump(empty($b->this_prop_name));
//bool(true)
Looks like the manual is wrong. This also works in PHP 5.2
By the way it seems that __get() is called when used by empty(), but the result also depends on __isset().
empty() returns true only if __isset() returns true and __get() returns an empty value.
See this example code:
class Foo {
function __get($name) {
if ($name == "bar")
return '';
}
function __isset($name) {
if ($name == "bar")
return true;
}
}
$foo = new Foo();
echo $foo->bar . PHP_EOL; // outputs "" - correct
echo var_export(isset($foo->bar)) . PHP_EOL; // outputs "true" - correct
$bar = $foo->bar;
// outputs both "true" -- so the manual is wrong here
echo var_export(empty($foo->bar)) . PHP_EOL;
echo var_export(empty($bar)) . PHP_EOL;
Also see this bug report
Apparently I didn't notice the link you included in your own question, so here's a basic breakdown of overloading:
Overloading in PHP is the concept of dynamically creating methods and parameters. Consider a basic scenario:
class MyClass {
public $foo = 'bar';
}
$Instance = new MyClass();
echo $Instance->foo;
We would all expect that to print "bar," and it would. Here is that same example using overloding:
class MyOverloadedClass {
public function __get($variable) {
return $variable == 'foo' ? 'bar' : '';
}
}
$Instance = new MyOverloadedClass();
echo $Instance->foo;
In this second example, because MyOverloadedClass::$foo does not exist, the __ge method will be executed, and will be passed the variable that the user asked for (in this case, "foo"). My code inside of the __get method would determine that the user is looking for foo, and return "bar"
Language constructs are parts of the PHP language. In other words, the parser interprets them and knows what to do, without having to lookup function references. Some examples are exit, die, print, echo, isset and empty.
isset() is the only language construct to work with overloading. The manual page describes an overloaded method __isset which you can add to your class, and will be executed every time someone calls isset on a property of that class. Calling empty() on an overloaded property, however, will always return true and likely throw an E_NOTICE. Here's why:
Disclaimer: calling empty() on an overloaded property DOES seem to execute __get(), IF AND ONLY IF you have an __isset() method that returns true for the passed variable name:
empty() will not call your overloaded __get() method. It will look in the class definition to see if that parameter exists. If it does, it'll evaluate whether or not it is empty. However, accessing undefined class parameters throws an error. In my first example, I would have thrown an error by asking for $Instance->bar, because it doesn't exist. This is the same error you would get if you asked for
empty( $Instance->foo );
In the second example. I hope this helps.
See the page you mentioned for additional details on how to apply this to functions, or setting variables.

Assigning PHP class fields to array values when passing both into a function doesn't set the fields, why?

As the description states, I have a function that takes in an array and an object as arguments and assigns all of the objects fields to their respective values in the array depending on the type of the object. The objects all have different fields, but they all have a type attribute which the function uses to determine which fields to assign.
It works something like this:
function unload($arr,&$obj){ <-- //&$obj not $obj
if($obj->type == 'A'){
echo 'Setting field for A';
$obj->a = $arr['a_value'];
//some more assignments..
}
elseif($obj->type == 'B'){
$obj->b = $arr['b_value'];
echo 'Setting field for B';
//some more assignments...
}
//some more elseifs
//return an error if
//object's type doesn't
//match
else{
echo 'Error: Object type '.$obj->type.' not recognized.';
}
}
$arr['a_value'] = 'SomeValue';
$arr['b_value'] = 'SomeOtherValue';
$obj = new A(); //A's type set to 'A' upon initialization
unload($arr,$obj);
echo 'A->a set to: '.$obj->a;
Output:
A->a set to:
The code enters the correct branch for the object that is passed in but none of the object's fields get assigned. What am I doing wrong?
The server is running PHP 4.4.7, I still have no idea what's causing this.
Edit: I FINALLY figured it out, it was a combination of 2 things:
I didn't realize the $this keyword was required when referencing class field names from within the class. I assumed the variables had global scope so $this was optional like it is in Java. This is why just changing the function declaration didn't fix the problem. Now everything works fine!
Which PHP version are you on?
Because in PHP4 you need to explicitly pass the object by reference:
function unload($arr,&$obj){
If otherwise you are on PHP5, double check your $arr contents. And do some print_r inside and outside the function ...
If you want to get the class name i'd suggest you to use get_class() which will return the class name.
Anyway why are you using A->a instead of $obj->a? It seems to be wrong.
And notice that switch could best suits your needs in this case.
EDIT Finally got it: you have to replace
$arr['a'] = 'SomeValue';
$arr['b'] = 'SomeOtherValue';
with
$arr['a_value'] = 'SomeValue';
$arr['b_value'] = 'SomeOtherValue';
or otherwise set $obj->b = $arr['a_value']; to $obj->b = $arr['a']; and do the same with the b value.
The meaning of this is that the array keys have to be the same.

Getting static property from a class with dynamic class name in PHP

I have this:
one string variable which holds the class name ($classname)
one string variable with holds the property name ($propertyname)
I want to get that property from that class, the problem is, the property is static and I don't know how to do that.
If the property weren't static, it would have been:
$classname->$propertyname;
if the property were a method, I could have used call_user_function
call_user_func(array($classname, $propertyname));
But in my case, am I just lost. I am however hoping that it is possible. With the thousands of functions that PHP has, he'd better have something for this as well. Maybe I'm missing something?
Thanks!
Edit:
for those with eval() solutions: thanks, but it is out of the question
for those with get _class _vars() solutions: thanks, but it seems it returns "the default properties of the given class" (php.net), and yes, I would like that value to be changable (even though it does help me in some of the cases)
If you are using PHP 5.3.0 or greater, you can use the following:
$classname::$$propertyname;
Unfortunately, if you are using a version lower than 5.3.0, you are stuck using eval() (get_class_vars() will not work if the value is dynamic).
$value = eval($classname.'::$'.$propertyname.';');
EDIT: I've just said get_class_vars() wouldn't work if the value is dynamic, but apparently, variable static members are part of "the default properties of a class". You could use the following wrapper:
function get_user_prop($className, $property) {
if(!class_exists($className)) return null;
if(!property_exists($className, $property)) return null;
$vars = get_class_vars($className);
return $vars[$property];
}
class Foo { static $bar = 'Fizz'; }
echo get_user_prop('Foo', 'bar'); // echoes Fizz
Foo::$bar = 'Buzz';
echo get_user_prop('Foo', 'bar'); // echoes Buzz
Unfortunately, if you want to set the value of the variable, you will still need to use eval(), but with some validation in place, it's not so evil.
function set_user_prop($className, $property,$value) {
if(!class_exists($className)) return false;
if(!property_exists($className, $property)) return false;
/* Since I cannot trust the value of $value
* I am putting it in single quotes (I don't
* want its value to be evaled. Now it will
* just be parsed as a variable reference).
*/
eval($className.'::$'.$property.'=$value;');
return true;
}
class Foo { static $bar = 'Fizz'; }
echo get_user_prop('Foo', 'bar'); // echoes Fizz
set_user_prop('Foo', 'bar', 'Buzz');
echo get_user_prop('Foo', 'bar'); // echoes Buzz
set_user_prop() with this validation should be secure. If people start putting random things as $className and $property, it will exit out of the function as it won't be an existing class or property. As of $value, it is never actually parsed as code so whatever they put in there won't affect the script.
I think this is the simplest:
$foo = new ReflectionProperty('myClassName', 'myPropertyName');
print $foo->getValue();
To return a variable value that is set by a Static Variable you need to call:
$static_value = constant($classname.'::'.$propertyname);
Check out the documentation :: PHP Constant Documentation
You should be able to do something like:
eval("echo $classname::$propertyname;");
I just did a quick test and got this to work for me. Not sure if there's a better way or not, but I wasn't able to find one.
'eval' looks so close to 'evil', and I hate using it and/or seeing it in code. With a few ideas from other answers, here's a way to avoid it even if your php isn't 5.3 or higher.
Changed to reflect testing based on a comment.
class A {
static $foo = 'bar';
}
A::$foo = 'baz';
$a = new A;
$class = get_class($a);
$vars = get_class_vars($class);
echo $vars['foo'];
Outputs 'baz'.
One thing I noticed is that you can't set variables which are protected in static classes as the eval() command runs in a scope outside the class. The only thing to get around this would be to implement a static method inside the/every class which runs the eval(). This method could be protected as the call_user_func() [to call the setter method] also runs from inside the class.
Potentially relevant: discussion on late static binding in PHP - When would you need to use late static binding?.
get_class_vars is not same as get_object_vars.
I think get_clas_vars should return the original property values.
Even if for you said eval is out of the question, prior PHP 5.3 the easiest solution is still by using eval:
eval("\$propertyval = $classname::\$propertyname;");
echo $propertyval;
Getting and setting both static and non static properties without using Reflection
Using Reflection works but it is costly
Here is what I use for this purpose,
It works for PHP 5 >= 5.1.0 because I'm using property_exist
function getObjectProperty($object, $property)
{
if (property_exists(get_class($object), $property)) {
return array_key_exists($property, get_object_vars($object))
? $object->{$property}
: $object::$$property;
}
}
function setObjectProperty($object, $property, $value)
{
if (property_exists(get_class($object), $property)) {
array_key_exists($property, get_object_vars($object))
? $object->{$property} = $value
: $object::$$property = $value;
}
}
You can use ReflectionClass:
class foo
{
private static $bar = "something";
}
$class = "foo";
$reflector = new ReflectionClass($class);
$static_vars = $reflector->getStaticProperties();
var_dump($static_vars["bar"]);

Categories