简体   繁体   English

PHP __get和__set魔术方法

[英]PHP __get and __set magic methods

Unless I'm completely mistaken, the __get and __set methods are supposed to allow overloading of the → get and set . 除非我完全弄错了,否则__get__set方法应该允许重载→ getset

For example, the following statements should invoke the __get method: 例如,以下语句应调用__get方法:

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

And the following should use the __set method: 以下应使用__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 __get where 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. 现在,我只是说它,并且我手动使用__get现在需要它,但这不是非常动态的,并且需要知道“重载”代码实际上没有被调用,除非特别调用。 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. 我想知道这是不应该以我理解它应该或为什么这不起作用的方式起作用。

This is running on php 5.3.3 . 这是在php 5.3.3上运行的。

__get , __set , __call and __callStatic are invoked when the method or property is inaccessible. __get__set__call__callStatic当方法或属性是不可访问的被调用。 Your $bar is public and therefor not inaccessible. 您的$bar是公开的,因此无法访问。

See the section on Property Overloading in the manual: 请参阅手册中有关属性重载部分:

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

The magic methods are not substitutes for getters and setters. 魔术方法不能替代getter和setter。 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或直接方法调用慢得多。

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 $values is protected), to avoid collisions. 这样您就可以确保不能以其他方式访问变量(注意$values是受保护的),以避免冲突。

From the PHP manual : PHP手册

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

This is only called on reading/writing inaccessible properties. 这仅在读/写不可访问的属性时调用。 Your property however is public, which means it is accessible. 但您的财产是公开的,这意味着它是可以访问的。 Changing the access modifier to protected solves the issue. 将访问修饰符更改为受保护可解决此问题。

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. 我同意__get对自定义get函数更慢(做同样的事情),这是__get()的时间是0.0124455,而这个0.0024445是10000循环后的自定义get()。 – Melsi Nov 23 '12 at 22:32 Best practice: PHP Magic Methods __set and __get - Melsi 12年11月23日在22:32 最佳实践:PHP魔术方法__set和__get

According to Melsi's tests, considerably slower is about 5 times slower. 根据Melsi的测试,相当慢的速度大约慢5倍。 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. 这肯定要慢得多,但同时请注意,测试显示您仍然可以使用此方法访问属性10,000次,计算循环迭代的时间,大约是1/100秒。 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. 与定义的实际get和set方法相比,它要慢得多,这是轻描淡写的,但在宏观方案中,即使慢5倍也从未真正变慢。

The computing time of the operation is still negligible and not worth considering in 99% of real world applications. 该操作的计算时间仍然可以忽略不计,并且在99%的实际应用中不值得考虑。 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. 真正应该避免的唯一一次是你实际上要在一次请求中访问属性超过10,000次。 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. 高流量站点的页脚上的单行文本广告(访问率成为问题)可能需要支付具有该行文本的1,000个服务器的服务器场。 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. 最终用户永远不会轻拍他们的手指,因为您的应用程序的属性访问需要花费百万分之一秒,因此想知道该页面需要如此长时间才能加载。

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. 我说这是一个来自.NET背景的开发人员,但对于消费者来说,隐形的get和set方法并不是.NET的发明。 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. 它们不是没有它们的属性,而这些神奇的方法是PHP的开发人员甚至可以节省它们的属性“属性”的版本。 Also, the Visual Studio extension for PHP does support intellisense with protected properties, with that trick in mind, I'd think. 另外,PHP的Visual Studio扩展确实支持带有受保护属性的intellisense,我想这就是这个技巧。 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. 我认为有足够的开发人员以这种方式使用magic __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. 实际上,事实证明,在访问类定义和扩展类中的属性时,很多时候您会想要使用getter和setter。 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: 我正在使用我的项目的基类做更多的事情,所以我现在没有提供的接口,但这里是未经测试的剥离类定义,使用魔法属性获取并使用反射设置移除并将属性移动到受保护的数组:

/** 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. 如果代码中有任何错误,我很抱歉。

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; 删除public $bar; from your class should correct this. 从你的班级应该纠正这一点。

Best use magic set/get methods with predefined custom set/get Methods as in example below. 最好使用具有预定义自定义set / get方法的magic set / get方法,如下例所示。 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. 下面的示例还根据预定义的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 的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 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

Drop the public $bar; 放弃public $bar; declaration and it should work as expected. 声明,它应该按预期工作。

Intenta con: Intenta con:

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

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM