PHP magic methods behave differently when dynamically building property names - php

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.

Related

PHP Visibility with Magic Methods __get & __set

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.

PHP overloading - avoid Notice: Trying to get property of non-object

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;

Automatically use getters and setters with PHP's __get and __set methods?

I am trying to build a base class that all of my classes extend from that allows for property access through simple $obj->property syntax, but if get_property() or set_property($value) is defined, then any attempt to get or set that property is routed through those getters and setters.
The tricky part, though, is that I would like updates to object properties to be reflected in an array (which is a property of the object, call it $changed_array) which can output an array of the properties that were changed, for some purpose, say, insertion into a db update call.
The problem lies in this sample:
class Sample {
private $changed_array;
public __get($var_ame){
if(method_exists($this, $method = 'get_' . $var_name)){
return $this->$method();
} else {
return $this->$var_name;
}
}
public __set($var_name, $value){
if(method_exists($this, $method = 'set_' . $var_name))}
return $this->$method($value);
} else {
// pseudo code
if($this->$var_name isset and isn't $value) { // add to $changed_array }
return $this->$var_name = $value;
}
}
}
Which works great, until there is a setter method defined like so:
public set_var_name($value){
// pretend we're mapping a db column to another name
$this->other_var_name = $value;
}
With this, the setter is called, but property it is setting is accessible, so the new value doesn't use the __set or __get function, and the changed array isn't updated with other_var_name as a changed property.
Is there some kind of hack, other that using something like $this->set('variable', 'value') to achieve the result? I would just write getter's and setters, but they vary based on the db schema, and it would be lovely if there was an elegantly simple solution.
Try this,
$objects = array("model", "make", "version");
foreach ($objects as $object) {
$getter = "get".ucfirst($object);
if (is_object($iProduct->$getter())) {
echo "getvalue"+$iProduct->$getter()
}

Ambiguous syntax of $this->$variable in PHP

Please excuse me if this question has been asked before, but I tried searching for it with no satisfactory results.
I'm learning PHP (coming from a C++ background) and have come across the following ambiguity. The following two bits of code work exactly the same:
class A
{
public $myInteger;
public function __get($name)
{
return $this->$name;
}
public function __set($name, $value)
{
$this->$name = $value;
}
}
and
class A
{
public $myInteger;
public function __get($name)
{
return $this->name;
}
public function __set($name, $value)
{
$this->name = $value;
}
}
that is, in the class methods $this->$name and $this->name have the exact same function. I'm finding this a bit confusing, especially when considering that if you add the following code,
$myA = new A();
$myA->myInteger = 5;
$hereInt = $myA->myInteger;
echo "<p>" . $hereInt . "</p>";
it only works if there is no $ before myInteger. Could someone please explain the rationale behind this?
$this->$name and $this->name do not mean the same thing. The first is using a locally scoped variable $name to access the field of $this whose name is whatever $name contains, while the second accesses the name field directly.
For example, the following will output something:
$foo = new stdClass;
$foo->bar = 'something';
$baz = 'bar';
echo $foo->$baz;
In the case of __get and __set, $name contains the name of the property that was accessed at the call site; in your case, myInteger.
In your example, the __get and __set methods are actually superfluous, since $myA->myInteger is public and can be accessed directly. __get and __set are only needed to catch access attempts to a property that is not declared explicitly in the class.
For example, you might have a backing array that allows arbitrary "properties" to be set dynamically:
class Foo
{
private $_values = array();
public function __get($key)
{
if (isset($this->_values[$key]))
{
return $this->_values[$key]
}
}
public function __set($key, $value)
{
$this->_values[$key] = $value;
}
}
One thing that's somewhat confusing about this aspect of PHP's syntax is that a $ precedes a field declaration in a class, but there is none when accessing that field. This is compounded by the syntax for accessing static fields, which does require a $!

How to avoid PHP to auto-create attributes?

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.

Categories