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 . $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
}
}
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.
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];
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.