89

除非我完全弄错了,否则__getand__set方法应该允许重载 →getset.

例如,以下语句应调用该__get方法:

echo $foo->bar;
$var = $foo->bar;

以下应使用该__set方法:

$foo->bar = 'test';

这在我的代码中不起作用,并且可以通过这个简单的示例重现:

class foo {

    public $bar;
    public function __get($name) {

        echo "Get:$name";
        return $this->$name;
    }

    public function __set($name, $value) {

        echo "Set:$name to $value";
        $this->$name = $value;
    }
}


$foo = new foo();

echo $foo->bar;
$foo->bar = 'test';

echo "[$foo->bar]";

这只会导致:

[test]

在那里打一些die()电话表明它根本没有击中它。

现在,我只是说搞砸了,现在手动使用__get它现在需要的地方,但这不是很动态,需要知道除非特别调用,否则实际上不会调用“重载”代码。我想知道这是否不应该按照我理解的方式运行,或者为什么这不起作用。

这是在运行php 5.3.3

4

8 回答 8

175

__get, __set,__call__callStatic在方法或属性不可访问时调用。你$bar是公开的,因此不是不可访问的。

请参阅手册中有关属性重载的部分:

  • __set()在将数据写入不可访问的属性时运行。
  • __get()用于从不可访问的属性中读取数据。

魔术方法不能替代 getter 和 setter。它们只允许您处理方法调用或属性访问,否则会导致错误。因此,还有更多与错误处理相关的内容。另请注意,它们比使用正确的 getter 和 setter 或直接方法调用要慢得多。

于 2011-01-17T13:34:30.593 回答
39

我建议使用数组来存储所有值__set()

class foo {

    protected $values = array();

    public function __get( $key )
    {
        return $this->values[ $key ];
    }

    public function __set( $key, $value )
    {
        $this->values[ $key ] = $value;
    }

}

这样你就可以确保你不能以另一种方式访问​​变量(注意它$values是受保护的),以避免冲突。

于 2011-01-17T13:37:04.983 回答
20

PHP 手册

  • __set() 在将数据写入不可访问的属性时运行。
  • __get() 用于从不可访问的属性中读取数据。

这仅在读取/写入不可访问的属性时调用。但是,您的财产是公开的,这意味着它是可访问的。将访问修饰符更改为受保护解决了这个问题。

于 2011-01-17T13:36:01.683 回答
8

为了扩展 Berry 的答案,将访问级别设置为 protected 允许 __get 和 __set 与显式声明的属性一起使用(至少在类外部访问时)并且速度相当慢,我将引用另一个问题的评论关于这个主题并提出使用它的理由:

我同意 __get 对自定义 get 函数(做同样的事情)更慢,这是 __get() 的 0.0124455 时间,而这个 0.0024445 是 10000 次循环后的自定义 get() 时间。– Melsi 2012 年 11 月 23 日 22:32最佳实践:PHP 魔术方法 __set 和 __get

根据 Melsi 的测试,相当慢大约是慢 5 倍。这肯定要慢得多,但还要注意,测试表明您仍然可以使用此方法访问属性 10,000 次,计算循环迭代的时间,大约为 1/100 秒。与实际定义的 get 和 set 方法相比,它要慢得多,这是一种轻描淡写的说法,但从总体上看,即使慢 5 倍也不会真正慢。

该操作的计算时间仍然可以忽略不计,在 99% 的实际应用中不值得考虑。唯一真正应该避免的情况是,当您实际上要在单个请求中访问属性超过 10,000 次时。如果高流量站点负担不起增加几台服务器以保持其应用程序运行,那么他们就做错了。在访问率成为问题的高流量网站页脚上的单行文字广告可能会为拥有该行文字的 1,000 台服务器场支付费用。最终用户永远不会敲击他们的手指想知道是什么让页面加载这么长时间,因为您的应用程序的属性访问需要百万分之一秒。

我是作为一名具有 .NET 背景的开发人员说的,但对消费者来说不可见的 get 和 set 方法并不是 .NET 的发明。没有它们它们根本就不是属性,这些神奇的方法是 PHP 开发人员的救命稻草,甚至根本称它们的属性版本为“属性”。此外,我认为,PHP 的 Visual Studio 扩展确实支持具有受保护属性的智能感知,考虑到这一技巧。我认为有足够多的开发人员以这种方式使用神奇的 __get 和 __set 方法,PHP 开发人员会调整执行时间以迎合开发人员社区。

编辑:理论上,受保护的属性似乎在大多数情况下都可以工作。实际上,事实证明,在访问类定义和扩展类中的属性时,很多时候您会想要使用 getter 和 setter。更好的解决方案是在扩展其他类时使用基类和接口,因此您只需将几行代码从基类复制到实现类中即可。我正在对我的项目的基类做更多的事情,所以我现在没有要提供的接口,但这里是未经测试的精简类定义,它使用反射获取和设置魔法属性来删除和移动属性到受保护的数组:

/** Base class with magic property __get() and __set() support for defined properties. */
class Component {
    /** Gets the properties of the class stored after removing the original
     * definitions to trigger magic __get() and __set() methods when accessed. */
    protected $properties = array();

    /** Provides property get support. Add a case for the property name to
     * expand (no break;) or replace (break;) the default get method. When
     * overriding, call parent::__get($name) first and return if not null,
     * then be sure to check that the property is in the overriding class
     * before doing anything, and to implement the default get routine. */
    public function __get($name) {
        $caller = array_shift(debug_backtrace());
        $max_access = ReflectionProperty::IS_PUBLIC;
        if (is_subclass_of($caller['class'], get_class($this)))
            $max_access = ReflectionProperty::IS_PROTECTED;
        if ($caller['class'] == get_class($this))
            $max_access = ReflectionProperty::IS_PRIVATE;
        if (!empty($this->properties[$name])
            && $this->properties[$name]->class == get_class()
            && $this->properties[$name]->access <= $max_access)
            switch ($name) {
                default:
                    return $this->properties[$name]->value;
            }
    }

    /** Provides property set support. Add a case for the property name to
     * expand (no break;) or replace (break;) the default set method. When
     * overriding, call parent::__set($name, $value) first, then be sure to
     * check that the property is in the overriding class before doing anything,
     * and to implement the default set routine. */
    public function __set($name, $value) {
        $caller = array_shift(debug_backtrace());
        $max_access = ReflectionProperty::IS_PUBLIC;
        if (is_subclass_of($caller['class'], get_class($this)))
            $max_access = ReflectionProperty::IS_PROTECTED;
        if ($caller['class'] == get_class($this))
            $max_access = ReflectionProperty::IS_PRIVATE;
        if (!empty($this->properties[$name])
            && $this->properties[$name]->class == get_class()
            && $this->properties[$name]->access <= $max_access)
            switch ($name) {
                default:
                    $this->properties[$name]->value = $value;
            }
    }

    /** Constructor for the Component. Call first when overriding. */
    function __construct() {
        // Removing and moving properties to $properties property for magic
        // __get() and __set() support.
        $reflected_class = new ReflectionClass($this);
        $properties = array();
        foreach ($reflected_class->getProperties() as $property) {
            if ($property->isStatic()) { continue; }
            $properties[$property->name] = (object)array(
                'name' => $property->name, 'value' => $property->value
                , 'access' => $property->getModifier(), 'class' => get_class($this));
            unset($this->{$property->name}); }
        $this->properties = $properties;
    }
}

如果代码中有任何错误,我深表歉意。

于 2014-03-26T02:03:59.193 回答
5

这是因为 $bar 是公共财产。

$foo->bar = 'test';

运行上面的时候不需要调用魔法方法。

从您的班级中删除public $bar;应该可以纠正这一点。

于 2011-01-17T13:36:14.857 回答
2

最好使用带有预定义自定义 set/get 方法的魔法 set/get 方法,如下例所示。通过这种方式,您可以将两个世界中最好的结合起来。就速度而言,我同意它们有点慢,但你能感觉到不同吗。下面的示例还根据预定义的设置器验证数据数组。

“魔术方法不能替代 getter 和 setter。它们只是允许您处理方法调用或属性访问,否则会导致错误。”

这就是为什么我们应该同时使用两者。

类项目示例

    /*
    * Item class
    */
class Item{
    private $data = array();

    function __construct($options=""){ //set default to none
        $this->setNewDataClass($options); //calling function
    }

    private function setNewDataClass($options){
        foreach ($options as $key => $value) {
            $method = 'set'.ucfirst($key); //capitalize first letter of the key to preserve camel case convention naming
            if(is_callable(array($this, $method))){  //use seters setMethod() to set value for this data[key];      
                $this->$method($value); //execute the setters function
            }else{
                $this->data[$key] = $value; //create new set data[key] = value without seeters;
            }   
        }
    }

    private function setNameOfTheItem($value){ // no filter
        $this->data['name'] = strtoupper($value); //assign the value
        return $this->data['name']; // return the value - optional
    }

    private function setWeight($value){ //use some kind of filter
        if($value >= "100"){ 
            $value = "this item is too heavy - sorry - exceeded weight of maximum 99 kg [setters filter]";
        }
        $this->data['weight'] = strtoupper($value); //asign the value
        return $this->data['weight']; // return the value - optional
    }

    function __set($key, $value){
        $method = 'set'.ucfirst($key); //capitalize first letter of the key to preserv camell case convention naming
        if(is_callable(array($this, $method))){  //use seters setMethod() to set value for this data[key];      
            $this->$method($value); //execute the seeter function
        }else{
            $this->data[$key] = $value; //create new set data[key] = value without seeters;
        }
    }

    function __get($key){
        return $this->data[$key];
    }

    function dump(){
        var_dump($this);
    }
}

索引.PHP

$data = array(
    'nameOfTheItem' => 'tv',
    'weight' => '1000',
    'size' => '10x20x30'
);

$item = new Item($data);
$item->dump();

$item->somethingThatDoNotExists = 0; // this key (key, value) will trigger magic function __set() without any control or check of the input,
$item->weight = 99; // this key will trigger predefined setter function of a class - setWeight($value) - value is valid,
$item->dump();

$item->weight = 111; // this key will trigger predefined setter function of a class - setWeight($value) - value invalid - will generate warning.
$item->dump(); // display object info

输出

object(Item)[1]
  private 'data' => 
    array (size=3)
      'name' => string 'TV' (length=2)
      'weight' => string 'THIS ITEM IS TOO HEAVY - SORRY - EXIDED WEIGHT OF MAXIMUM 99 KG [SETTERS FILTER]' (length=80)
      'size' => string '10x20x30' (length=8)
object(Item)[1]
  private 'data' => 
    array (size=4)
      'name' => string 'TV' (length=2)
      'weight' => string '99' (length=2)
      'size' => string '10x20x30' (length=8)
      'somethingThatDoNotExists' => int 0
object(Item)[1]
  private 'data' => 
    array (size=4)
      'name' => string 'TV' (length=2)
      'weight' => string 'THIS ITEM IS TOO HEAVY - SORRY - EXIDED WEIGHT OF MAXIMUM 99 KG [SETTERS FILTER]' (length=80)
      'size' => string '10x20x30' (length=8)
      'somethingThatDoNotExists' => int 0
于 2015-05-01T04:52:43.850 回答
0

删除public $bar;声明,它应该按预期工作。

于 2011-01-17T13:36:42.750 回答
-7

意图:

__GET($k){
 return $this->$k;
}

_SET($k,$v){
 return $this->$k = $v;
}
于 2019-05-31T15:13:18.523 回答