PHP __get 和 __set 魔法方法

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4713680/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-25 14:06:23  来源:igfitidea点击:

PHP __get and __set magic methods

phpmagic-methods

提问by airbear

Unless I'm completely mistaken, the __getand __setmethods are supposed to allow overloading of the → getand set.

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

For example, the following statements should invoke the __getmethod:

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

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

And the following should use the __setmethod:

并且以下应使用该__set方法:

$foo->bar = 'test';

This was not working in my code, and is reproducible with this simple example:

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

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]";

This only results in:

这只会导致:

[test]

Putting some die()calls in there shows that it is not hitting it at all.

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

For now, I just said screw it, and am manually using __getwhere it's needed for now, but that's not very dynamic and requires knowledge that the 'overloaded' code is in fact not being called unless specifically called. I'd like to know if this is either not supposed to function the way I've understood that it should or why this is not working.

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

This is running on php 5.3.3.

这是在运行php 5.3.3

回答by Gordon

__get, __set, __calland __callStaticare invoked when the method or property is inaccessible. Your $baris public and therefor not inaccessible.

__get__set__call__callStatic在方法或属性不可访问时调用。您$bar是公开的,因此不会无法访问。

See the section on Property Overloading in the manual:

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

  • __set()is run when writing data to inaccessible properties.
  • __get()is utilized for reading data from inaccessible properties.
  • __set()在将数据写入无法访问的属性时运行。
  • __get()用于从不可访问的属性读取数据。

The magic methods are not substitutes for getters and setters. They just allow you to handle method calls or property access that would otherwise result in an error. As such, there are much more related to error handling. Also note that they are considerably slower than using proper getter and setter or direct method calls.

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

回答by Fidi

I'd recommend to use an array for storing all values via __set().

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

class foo {

    protected $values = array();

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

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

}

This way you make sure, that you can't access the variables in another way (note that $valuesis protected), to avoid collisions.

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

回答by Berry Langerak

From the PHP manual:

PHP 手册

  • __set() is run when writing data to inaccessible properties.
  • __get() is utilized for reading data from inaccessible properties.
  • __set() 在将数据写入无法访问的属性时运行。
  • __get() 用于从不可访问的属性中读取数据。

This is only called on reading/writing inaccessibleproperties. Your property however is public, which means it is accessible. Changing the access modifier to protected solves the issue.

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

回答by Jason Ensinger

To expand on Berry's answer, that setting the access level to protected allows __get and __set to be used with explicitly declared properties (when accessed outside the class, at least) and the speed being considerably slower, I'll quote a comment from another question on this topic and make a case for using it anyway:

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

I agree that __get is more slow to a custom get function (doing the same things), this is 0.0124455 the time for __get() and this 0.0024445 is for custom get() after 10000 loops.– Melsi Nov 23 '12 at 22:32 Best practice: PHP Magic Methods __set and __get

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

According to Melsi's tests, considerably slower is about 5 times slower. That is definitely considerably slower, but also note that the tests show that you can still access a property with this method 10,000 times, counting time for loop iteration, in roughly 1/100 of a second. It is considerably slower in comparison with actual get and set methods defined, and that is an understatement, but in the grand scheme of things, even 5 times slower is never actually slow.

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

The computing time of the operation is still negligible and not worth considering in 99% of real world applications. The only time it should really be avoided is when you're actually going to be accessing the properties over 10,000 times in a single request. High traffic sites are doing something really wrong if they can't afford throwing a few more servers up to keep their applications running. A single line text ad on the footer of a high traffic site where the access rate becomes an issue could probably pay for a farm of 1,000 servers with that line of text. The end user is never going to be tapping their fingers wondering what is taking the page so long to load because your application's property access takes a millionth of a second.

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

I say this speaking as a developer coming from a background in .NET, but invisible get and set methods to the consumer is not .NET's invention. They simply aren't properties without them, and these magic methods are PHP's developer's saving grace for even calling their version of properties "properties" at all. Also, the Visual Studio extension for PHP does support intellisense with protected properties, with that trick in mind, I'd think. I would think with enough developers using the magic __get and __set methods in this way, the PHP developers would tune up the execution time to cater to the developer community.

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

Edit: In theory, protected properties seemed like it'd work in most situation. In practice, it turns out that there's a lot of times you're going to want to use your getters and setters when accessing properties within the class definition and extended classes. A better solution is a base class and interface for when extending other classes, so you can just copy the few lines of code from the base class into the implementing class. I'm doing a bit more with my project's base class, so I don't have an interface to provide right now, but here is the untested stripped down class definition with magic property getting and setting using reflection to remove and move the properties to a protected array:

编辑:理论上,受保护的属性似乎在大多数情况下都有效。在实践中,事实证明,在访问类定义和扩展类中的属性时,很多时候您会想要使用 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;
    }
}

My apologies if there are any bugs in the code.

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

回答by Matt Lowden

It's because $bar is a public property.

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

$foo->bar = 'test';

There is no need to call the magic method when running the above.

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

Deleting public $bar;from your class should correct this.

public $bar;从你的班级中删除应该纠正这个问题。

回答by DevWL

Best use magic set/get methods with predefined custom set/get Methods as in example below. This way you can combine best of two worlds. In terms of speed I agree that they are a bit slower but can you even feel the difference. Example below also validate the data array against predefined setters.

最好使用带有预定义自定义 set/get 方法的魔法 set/get 方法,如下例所示。通过这种方式,您可以结合两全其美。在速度方面,我同意它们有点慢,但您甚至能感觉到差异。下面的示例还根据预定义的 setter 验证数据数组。

"The magic methods are not substitutes for getters and setters. They just allow you to handle method calls or property access that would otherwise result in an error."

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

This is why we should use both.

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

CLASS ITEM EXAMPLE

课程项目示例

    /*
    * 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

OUTPUT

输出

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

回答by Alix Axel

Drop the public $bar;declaration and it should work as expected.

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

回答by charly

Intenta con:

意图骗局:

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

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