PHP __get和__set魔术方法


85

除非我完全误解,否则__getand__set方法应该允许→get和的重载set

例如,以下语句应调用该__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

Answers:


164

__get__set__call__callStatic当该方法或属性是不可访问的被调用。您$bar是公开的,因此并非无法访问。

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

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

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


5
要对此进行详细说明,只需删除“ public $ bar”,以便该属性不再存在,它将像一种魅力一样工作。
SteffenMüller

谢谢。这就是我没有完全阅读本手册,而是看一些手册博客示例的目的;)尽管如此,仍然在等待真正的运算符重载。
airbear 2011年

@airbear有Sara Golemon提供的旧的PECL软件包,该软件包可让您重载运算符。不知道它与PHP.current的兼容性如何。
Gordon

1
@Pooya这是因为node不是$ foo的属性,而是的属性doesNotExist。因此,除非“ doesNotExist”是一个对象(实现__set或具有称为node的公共属性),否则它将无法工作。
蒂维

1
@艾伦,是的。
Gordon

34

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

class foo {

    protected $values = array();

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

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

}

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


19

PHP手册

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

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


7

为了扩展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次。如果高流量站点负担不起再增加一些服务器以保持其应用程序运行,那么它们的确在做错事。在访问率成为问题的高流量网站的页脚处放置一行文字广告,可能会为拥有该行文字的1000个服务器场支付费用。最终用户永远不会轻触自己的手指来思考加载页面所需的时间如此之久,因为您的应用程序的属性访问需要花费百万分之一秒的时间。

我说这是来自.NET的开发人员,但对于消费者而言,无形的get和set方法并不是.NET的发明。它们根本就是没有它们的属性,这些魔术方法是PHP开发人员的节省之选,甚至可以将它们的属性版本都称为“ properties”。此外,我认为,针对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;
    }
}

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


我在'access'=> $ property-> getModifier()上发现了一个问题
macki,2016年

5

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

$foo->bar = 'test';

运行上面的代码时无需调用magic方法。

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


1

最好将魔术设置/获取方法与预定义的自定义设置/获取方法结合使用,如下例所示。这样,您就可以结合两个世界的优点。在速度方面,我同意它们的速度要慢一些,但是您甚至可以感觉到差异。下面的示例还针对预定义的设置程序验证数据数组。

“魔术方法不能替代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);
    }
}

索引文件

$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

意图骗局:

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

_SET($k,$v){
 return $this->$k = $v;
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.