用魔术方法实现PHP可见性__get&__设置


PHP Visibility with Magic Methods __get & __set

我最近参加了一次面试,我提供的代码具有获取和设置变量的神奇功能。我的代码如下:

public function __get($name){
    try { 
        return $this->$name;
    } catch (Exception $e) { 
        throw new Exception('Trying to get a variable "'.$name.'" that does not exist.'); 
    }
}

在采访中,那个家伙问我变量的可见性,我设置了私有变量,但现在可以通过使用魔术函数访问这些变量。从本质上讲,我在这一点上没有通过面试,所以我想了解更多。我遵循了PHP Master的教程,发现了一个不同的__get,我试图打破它,但它似乎有效,但方式很奇怪。

我调用__get('test')来获取我的变量_test,但如果它被设置为私有,它会再次调用自己,并告诉我它无法访问__test。我真的不明白为什么它会再次自称。

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;
}

有人能给我一些关于在类中使用可见性时正确使用__get和__set的建议吗?以及为什么这个函数会再次调用自己。

我已经阅读了这里的其他帖子,但我仍然在为这个概念而挣扎。

我刚刚遇到了这个问题,有一件事可能值得澄清:

我真的不明白为什么它会再次自称。

代码不会再次调用自己,而是尝试执行自定义getter(如果已定义)。让我分解一下方法执行:

public function __get($name)
{

正如在其他答案和这里已经解释的那样,当您试图访问未声明或在调用范围中不可见的属性时,会调用__get()magic方法。

$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));

在这里,它创建要调用的getter的名称(如果存在的话)。因此,如果您尝试访问名为email的属性,并且有一个名为_email的私有成员,那么$accessor变量现在将包含'getEmail'字符串。

return (method_exists($this, $accessor) && is_callable(array($this, $accessor))) ?
    $this->$accessor() : $this->$field;

最后一部分有点虚构,因为很多事情都发生在一行:

  • method_exists($this, $accessor)。检查接收器($this)是否具有名称为$accessor的方法(在我们的示例中为getEmail
  • is_callable(array($this, $accessor))。检查是否可以调用getter
  • 如果这两个条件都满足,则调用自定义getter并返回其返回值($this->$accessor())。如果没有,则返回属性内容($this->$field

作为一个例子,考虑这个类的定义:

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;
}
}

然后运行:

$example = new AccessorsExample();
echo $example->test1 . "'n";
echo $example->test2 . "'n";

你应该看到:

One
Calling the getter
Two

HTH

我发现在允许通过__get()访问属性时最好是显式的。这样,您仍然可以拥有真正的私人成员,并且不会冒着意外暴露您稍后添加的内容的风险。

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
  }
}

我不知道你的问题是什么,但例如,这段代码可以使用

<?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;

你可以在这里查看(http://codepad.org/jmkvHiDe)。

当你试图访问一个私有属性时,像__get()这样的魔术方法会被调用,就像访问一个不存在的属性一样,当然,如果你把一个属性设置为"私有",然后用户可以通过魔术方法访问变量,为什么要把这个属性设置为私有呢?

代替$this->$name;

使用类似$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.'); 
    }
}

这个方法目前有几个问题,没有看到代码的其余部分:

  • 它允许对类内所有私有和受保护属性进行不受限制的公共读取访问。除非在特殊情况下,否则这通常是不可取的,因为它确实会破坏类成员可见性的对象。如前所述,应该通过检查允许的列表(如Rob Agar的回答)或检查定义的getter(如OP的问题)来限制访问。

  • 访问未定义的属性时通常不会引发异常(除非您设置了自定义错误处理程序)。访问未定义的属性通常会触发E_NOTICE,因此您的方法不会捕获此属性。您应该首先验证$name是否确实存在。