使用Magic方法实现PHP可见性__get& __组

时间:2012-03-21 11:10:23

标签: php oop get visibility

我最近接受了采访,我提供的代码具有魔术功能来获取和设置变量。我的代码如下:

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的指示。

我已阅读其他帖子,但我仍在努力解决这个问题。

5 个答案:

答案 0 :(得分:5)

我刚刚提出这个问题,有一件事可能值得澄清:

  

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

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

public function __get($name)
{

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

$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 can be called
  • 如果满足两个条件,则调用自定义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

答案 1 :(得分:1)

我发现在允许通过__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
  }
}

答案 2 :(得分:0)

我没有得到完整的问题,但是例如这段代码可以运行

<?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()等魔术方法,就像调用它们以访问非现有属性一样,当然如果您将属性设置为“私有”,然后用户可以通过魔术方法访问变量,为什么首先将属性设置为私有?

答案 3 :(得分:0)

而不是$this->$name;

使用$this->protected_values[$name];

之类的内容

答案 4 :(得分:0)

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确实存在。