I'm using __get and __set methods for variable overloading.
In some cases I will assign another object to a variable, for example:
$object->name->another = $another_object;
$object, has the __get and __set methods. When I try to access $object->name->another before it's been set, I get the error
Notice: Trying to get property of non-object
Is there anyway around this using overloading? without having to check isset on the variable.
Thanks!
While this doesn't answer your question, it addresses an important aspect:
It's worth noting that you ought not allow your magic __get and __set methods to raise that notice. The "best practice" is to throw one of the new(ish) SPL Exception types specifically designed for this purpose. For example:
public function __get($name)
{
if (isset($this->vals[$name])) {
return $this->vals[$name];
} else {
$msg = "Invalid property: '$name' does not exist";
throw new OutOfBoundsException($msg);
}
}
And for __set ...
public function __set($name, $val)
{
if (isset($this->vals[$name])) {
$this->vals[$name] = $val;
} else {
$msg = "Invalid property: '$name' does not exist";
throw new OutOfBoundsException($msg);
}
}
Here's a link to a helpful article on the subject of the new SPL Exception types.
Not $object but $object->name needs to have the overloading with __get()/__set() if you want to interact to prevent the error in question:
$object->name->another = 'something';
^^^^^^^^^^^^^
For example you can make $object::__get() return an empty stdClass object, PHP would then automatically assign a public member to it in this case.
Just make sure you are returning an overloaded class for "name". PHP will fetch the property, then try to set the second property on the resulting object.
<?php
class Bob {
public function __get($name) {
return new Jim();
}
}
class Jim extends Bob {
public function __get($name) {
return 12;
}
}
$b = new Bob();
echo $b->someJim->apples;
Related
In PHP does such a magic method automatically run when a variable referencing an object is in an echo statement?
I am sorry, I had quite a difficulty understanding what you're asking. I believe you want the __toString() method:
The __toString() method allows a class to decide how it will react when it is treated like a string. For example, what echo $obj; will print. This method must return a string, as otherwise a fatal E_RECOVERABLE_ERROR level error is emitted.
Here's a quick example:
class A
{
public function __toString()
{
return 'banana';
}
}
$a = new A();
echo $a;
This will print out banana
I have two examples of a simple class using the __set() and __get() magic methods. One throws a fatal error and the other does not when attempting to access a protected property with the unset() function.
In Example 1, I'm naming my protected property starting with an underscore and allowing access via the friendly name and prepending the underscore in my __set() and __get() methods. (Effectively exposing the property without an underscore).
In Example 2, I'm not starting the name with an underscore and allowing access via the name directly in the __set() and __get() methods.
Questions
1) Why does Example 1 not throw a fatal error while Example 2 does throw a fatal error? I would expect either both to throw the error or neither to throw the error.
2) Also, why does Example 1 not actually unset the property? I would expect the property to not contain a value after the unset() function is called.
Example 1
class Example {
protected $_my_property;
function __get($name) {
echo '<h4>__get() was triggered!</h4>';
$name = '_' . $name;
if (property_exists($this, $name)) {
return $this->$name;
}
else {
trigger_error("Undefined property in __get(): $name");
return NULL;
}
}
function __set($name, $value) {
echo '<h4>__set() was triggered!</h4>';
$name = '_' . $name;
if (property_exists($this, $name)) {
$this->$name = $value;
return;
}
else {
trigger_error("Undefined property in __set(): {$name}");
}
}
}
$myExample = new Example();
$myExample->my_property = 'my_property now has a value';
echo $myExample->my_property;
unset($myExample->my_property);
echo "Did I unset my property?: {$myExample->my_property}";
Example 2
class Example {
protected $my_property;
function __get($name) {
echo '<h4>__get() was triggered!</h4>';
if (property_exists($this, $name)) {
return $this->$name;
}
else {
trigger_error("Undefined property in __get(): $name");
return NULL;
}
}
function __set($name, $value) {
echo '<h4>__set() was triggered!</h4>';
if (property_exists($this, $name)) {
$this->$name = $value;
return;
}
else {
trigger_error("Undefined property in __set(): {$name}");
}
}
}
$myExample = new Example();
$myExample->my_property = 'my_property now has a value';
echo $myExample->my_property;
unset($myExample->my_property);
echo "Did I unset my property?: {$myExample->my_property}";
As a side note, this is just a simplistic example that demonstrates the behavior I'm seeing in my real world project. Thanks!
The problem you have is that you have not defined an __unset() magic method.
This means that when you call unset($myExample->my_property), it is trying to directly unset a public property with the specified name.
In example 1, the real protected property has an underscore in it's name. Therefore, when you try to unset the property, PHP looks at the object, sees that there is nothing with the specified name, and effectively ignores it.
This is the same behaviour that unset() would exhibit if you tried to unset a non-existent variable or array element.
However in example 2, the protected property has the same name as you have given in the unset() call.
In this example, PHP looks at the object and sees that the property does exist but that it it is non-accessible. It therefore throws an error complaining that it can't unset the property.
You can resolve this issue by including an __unset() method alongside your __get() and __set() methods. If you're planning to use magic methods, you should ideally define all three.
Hope that helps.
I recently went to an interview and my code I supplied had magic functions to get and set variables. My code was as follows:
public function __get($name){
try {
return $this->$name;
} catch (Exception $e) {
throw new Exception('Trying to get a variable "'.$name.'" that does not exist.');
}
}
In the interview the guy asked me about the visibility on my variables, I had private ones set but these were now accessible by using magic functions. Essentially I failed the interview on this point, so I wanted to understand more. I was following a tutorial from PHP Master and found a different __get, I have tried to break it but it seems to work, but in a strange way.
I call __get('test') to get my variable _test but if it is set to private it calls itself again and tells me that it cannot access __test. I do not really understand why it calls itself again.
public function __get($name)
{
$field = '_' . strtolower($name);
if (!property_exists($this, $field)){
throw new \InvalidArgumentException(
"Getting the field '$field' is not valid for this entity"
);
}
$accessor = 'get' . ucfirst(strtolower($name));
return (method_exists($this, $accessor) && is_callable(array($this, $accessor))) ?
$this->$accessor() : $this->$field;
}
Can anyone give me some pointers on proper use of __get and __set when using visibility in a class and why this function would call itself again.
I have read the other posts here but I am still struggling with this concept.
I just bumped into this question and there is a little thing that may be worth clarifying:
I do not really understand why it calls itself again.
The code is not calling itself again but trying to execute a custom getter if there is one defined. Let me break down the method execution:
public function __get($name)
{
As already explained in the other answers and here, the __get() magic method is called when you are trying to access a property that is not declared or not visible in the calling scope.
$field = '_' . strtolower($name);
if (!property_exists($this, $field)){
throw new \InvalidArgumentException(
"Getting the field '$field' is not valid for this entity"
);
}
Here it just checks that a property with an underscore pre-appended exists in the class definition. If it doesn't, an exception is thrown.
$accessor = 'get' . ucfirst(strtolower($name));
Here it creates the name of the getter to call if it exists. Thus, if you try to access a property named email and there is a private member called _email the $accessor variable will now hold the 'getEmail' string.
return (method_exists($this, $accessor) && is_callable(array($this, $accessor))) ?
$this->$accessor() : $this->$field;
The final part is a bit cryiptic, since many things are happening in one line:
method_exists($this, $accessor). Checks if the receiver ($this) has a method with $accessor name (in our example, getEmail).
is_callable(array($this, $accessor)). Checks that the getter can be called.
If both conditions are met, the custom getter is called and its return value is returned ($this->$accessor()). If not, the property contents are returned ($this->$field).
As an example consider this class definition:
class AccessorsExample
{
private $_test1 = "One";
private $_test2 = "Two";
public function getTest2()
{
echo "Calling the getter\n";
return $this->_test2;
}
public function __get($name)
{
$field = '_' . strtolower($name);
if (!property_exists($this, $field)){
throw new \InvalidArgumentException(
"Getting the field '$field' is not valid for this entity"
);
}
$accessor = 'get' . ucfirst(strtolower($name));
return (method_exists($this, $accessor) && is_callable(array($this, $accessor))) ?
$this->$accessor() : $this->$field;
}
}
and then run:
$example = new AccessorsExample();
echo $example->test1 . "\n";
echo $example->test2 . "\n";
You should see:
One
Calling the getter
Two
HTH
I find it's better to be explicit when allowing access to properties via __get(). This way you can still have truly private members, and you don't run the risk of accidentally exposing things you add later.
class Foo
{
// readonly
private $foo;
private $bar;
// truly private
private $baz;
public function __get($var)
{
switch ($var)
{
// readonly access to foo and bar, but not baz
case 'foo':
case 'bar':
return $this->$var;
// readonly dynamically generated property
case 'buzz':
return $this->buzz();
default:
throw new InvalidPropertyException($var);
}
}
public function __isset($var)
{
switch ($var)
{
// return true for foo, bar and buzz so functions like isset()
// and empty() work as expected
case 'foo':
case 'bar':
case 'buzz':
return true;
default:
return false;
}
}
// dynamic readonly property implementation
private function buzz()
{
// calculate and return something which depends on other private properties
}
}
I don't get exactl what is your problem, but for example this code works
<?php
class foo {
private $_test = "my";
public function __get($name)
{
$field = '_' . strtolower($name);
if (!property_exists($this, $field)){
throw new InvalidArgumentException(
"Getting the field '$field' is not valid for this entity"
);
}
$accessor = 'get' . ucfirst(strtolower($name));
return (method_exists($this, $accessor) && is_callable(array($this, $accessor))) ?
$this->$accessor() : $this->$field;
}
}
$foo = new foo();
echo $foo->test;
as you can check here (http://codepad.org/jmkvHiDe).
Magic methods like __get() will be called, when you try to access a private property exactly as they would be called to access a non existing property, of course if you set a property as "private" and then the user can access the variable through a magic method, why set the property as private in the first place?
Instead of $this->$name;
Use something like $this->protected_values[$name];
public function __get($name){
try {
return $this->$name;
} catch (Exception $e) {
throw new Exception('Trying to get a variable "'.$name.'" that does not exist.');
}
}
A couple of problems with this method as it stands, without seeing the rest of the code:
It allows unrestricted public read access to all private and protected properties inside the class. Except in special cases this is usually undesirable as it does defeat the object of class member visibility. As mentioned earlier, access should be restricted, either by checking against an allowed list (as in Rob Agar's answer) or by checking for a defined getter (as in the OP's question).
An exception is not normally thrown when accessing an undefined property (unless you have a custom error handler that is set to do so). Accessing an undefined property normally triggers an E_NOTICE, so your method would not trap this. You should first validate that $name does actually exist.
When doing this :
class MyClass
{
public $myAttr;
}
$c = new MyClass();
$c->someStuff = 1;
I have no error message, no notice, no whatever to tell me "hey, I do not know someStuff !"
Is there anyway to make PHP less easy about this ?
Thanks
Implement the magic methods __get() and __set() and tell them to spit a notice. These methods are called when you access a property that wasn't declared (or isn't visible).
public function __get($name) {
trigger_error(__CLASS__ . " object has no such property $name", E_USER_NOTICE);
return NULL;
}
public function __set($name, $value) {
trigger_error(__CLASS__ . " object has no such property $name", E_USER_NOTICE);
}
I often define a SafeObject to inherit from, which dies when I attempt to read/write an undefined attribute:
class SafeObject {
function __get($key) { die("Attempt to get nonexistent member $key"); }
function __set($key, $value) { die("Attempt to set nonexistent member $key"); }
}
The idea being I should never actually call either of these methods during production code.
I have been doing some tests (to replace old code) with the __invoke magic method and I'm not sure this is a bug or not:
Lets suppose we have a class:
class Calc {
function __invoke($a,$b){
return $a*$b;
}
}
The following is possible and works without any problem:
$c = new Calc;
$k = $c;
echo $k(4,5); //outputs 20
However if I want to have another class to store an instance of that object,
this doesn't work:
class Test {
public $k;
function __construct() {
$c = new Calc;
$this->k = $c; //Just to show a similar situation than before
// $this-k = new Calc; produces the same error.
}
}
The error occurs when we try to call it like:
$t = new Test;
echo $t->k(4,5); //Error: Call to undefined method Test::k()
I know that a "solution" could be to have a function inside the class Test (named k) to "forward" the call using call_user_func_array but that is not elegant.
I need to keep that instance inside a common class (for design purposes) and be able to call it as function from other classes... any suggestion?
Update:
I found something interesting (at least for my purposes):
If we assign the "class variable" into a local variable it works:
$t = new Test;
$m = $t->k;
echo $m(4,5);
PHP thinks you want to call a method k on instance $t when you do:
$t->k(4, 5)
which is perfectly reasonable. You can use an intermediate variable to call the object:
$b = $t->k;
$b(4, 5);
See also bug #50029, which describes your issue.
When you do $test->k(), PHP thinks you are calling a method on the $test instance. Since there is no method named k(), PHP throws an exception. What you are trying to do is make PHP return the public property k and invoke that, but to do so you have to assign k to a variable first. It's a matter of dereferencing.
You could add the magic __call method to your Test class to check if there is a property with the called method name and invoke that instead though:
public function __call($method, $args) {
if(property_exists($this, $method)) {
$prop = $this->$method;
return $prop();
}
}
I leave adding the arguments to the invocation to you.
You might also want to check if the property is_callable.
But anyway, then you can do
$test->k();
You can not use method syntax (like $foo->bar() ) to call closures or objects with __invoke, since the engine always thinks this is a method call. You could simulate it through __call:
function __call($name, $params) {
if(is_callable($this->$name)) {
call_user_func_array($this->$name, $params);
}
}
but it would not work as-is.
If you call $test->k() PHP will search for a method called "k" on the $test instance and obviously it will throws an Exception.
To resolve this problem you can create a getter of the property "k"
class Test {
public $k;
function __construct() {
$c = new Calc;
$this->k = $c; //Just to show a similar situation than before
// $this-k = new Calc; produces the same error.
}
public function getK() {
return $this->k;
}
}
So now you can use the functor in this way:
$t = new Test();
echo $t->getK()(4,5);