PHP __get和__set魔术方法

时间:2011-01-17 13:30:44

标签: php magic-methods

除非我完全弄错,否则__get__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上运行。

8 个答案:

答案 0 :(得分:148)

当方法或属性不可访问时,将调用

__get__set__call__callStatic。您的$bar是公开的,因此无法访问。

请参阅section on Property Overloading in the manual:

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

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

答案 1 :(得分:29)

我建议使用数组通过__set()存储所有值。

class foo {

    protected $values = array();

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

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

}

通过这种方式,您可以确保无法以其他方式访问变量(请注意$values受保护),以避免冲突。

答案 2 :(得分:18)

来自PHP manual

    将数据写入不可访问的属性时,会运行
  • __ set()。
  • __ get()用于从不可访问的属性中读取数据。

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

答案 3 :(得分:5)

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

$foo->bar = 'test';

运行上述内容时无需调用magic方法。

从您的班级中删除public $bar;应该更正此问题。

答案 4 :(得分:5)

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

我同意__get对自定义get函数更慢(做同样的事情),这是__get()的时间是0.0124455,而这个0.0024445是10000次循环后的自定义get()。 - Melsi 11月23日和12月22日22:32 Best practice: PHP Magic Methods __set and __get

根据Melsi的测试,相当慢的速度大约慢5倍。这肯定要慢得多,但同时请注意,测试显示您仍然可以使用此方法访问属性10,000次,计算循环迭代的时间,大约是1/100秒。与定义的实际get和set方法相比,它要慢得多,这是轻描淡写的,但在宏观方案中,即使慢5倍也不会实际上很慢。

该操作的计算时间仍然可以忽略不计,在99%的实际应用中不值得考虑。真正应该避免的唯一一次是,您实际上将在单个请求中访问属性超过10,000次。如果高流量站点无法承担更多服务器以保持其应用程序运行,则它们正在做一些非常错误的事情。高流量站点的页脚上的单行文本广告(访问率成为问题)可能需要支付具有该行文本的1,000个服务器的服务器场。最终用户永远不会轻拍他们的手指,因为您的应用程序的属性访问需要花费百万分之一秒,因此想知道该页面需要加载多长时间。

我说这是一个来自.NET背景的开发人员,但对于消费者来说,隐形的get和set方法并不是.NET的发明。如果没有它们,它们根本就没有属性,而这些神奇的方法是PHP开发人员甚至可以通过调用他们的属性版本来保存优雅的属性"属性"一点都不此外,PHP的Visual Studio扩展确实支持带有受保护属性的intellisense,我想到了这个技巧。我认为有足够的开发人员以这种方式使用magic __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;
    }
}

如果代码中有任何错误,我很抱歉。

答案 5 :(得分:1)

最好使用具有预定义自定义set / get方法的magic set / get方法,如下例所示。这样你就可以结合两个世界中最好的一个。在速度方面我同意他们有点慢,但你甚至可以感受到差异。下面的示例还根据预定义的setter验证数据数组。

  

&#34;魔术方法不能代替getter和setter。他们   只是允许你处理方法调用或属性访问   否则会导致错误。&#34;

这就是我们应该同时使用两者的原因。

CLASS ITEM示例    

    /*
    * 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);
    }
}

的index.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

答案 6 :(得分:0)

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

答案 7 :(得分:-1)

意图骗局:

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

_SET($k,$v){
 return $this->$k = $v;
}