【问题标题】:PHP __get and __set magic methodsPHP __get 和 __set 魔术方法
【发布时间】:2011-06-10 11:09:36
【问题描述】:

除非我完全弄错了,__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 上运行的。

【问题讨论】:

    标签: php magic-methods


    【解决方案1】:

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

    section on Property Overloading in the manual:

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

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

    【讨论】:

    • 要详细说明这一点,只需删除“public $bar”,这样该属性就不再存在,它将像一个魅力一样工作。
    • 谢谢。这就是我没有彻底阅读手册而是查看一些人的博客示例的结果;)仍在等待 PHP 中真正的运算符重载。
    • @airbear 有一个old PECL package by Sara Golemon that would allow you to overload operators。不知道它与 PHP.current 的兼容性如何。
    • @Pooya 那是因为node 不是$foo 的属性,而是doesNotExist 的属性。因此,除非 'doesNotExist' 是一个对象(它实现了 __set 或有一个名为 node 的公共属性),否则它不会工作。
    • @Allen 是的,确实如此。
    【解决方案2】:

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

    class foo {
    
        protected $values = array();
    
        public function __get( $key )
        {
            return $this->values[ $key ];
        }
    
        public function __set( $key, $value )
        {
            $this->values[ $key ] = $value;
        }
    
    }
    

    这样可以确保您不能以其他方式访问变量(注意$values 是受保护的),以避免冲突。

    【讨论】:

      【解决方案3】:

      来自PHP manual

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

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

      【讨论】:

        【解决方案4】:

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

        我同意 __get 比自定义 get 函数更慢(做同样的事情),这是 __get() 的 0.0124455 时间,这个 0.0024445 是 10000 次循环后的自定义 get()。 – 梅尔西 2012 年 11 月 23 日 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 扩展确实支持具有受保护属性的智能感知,考虑到这一点。我认为如果有足够多的开发人员以这种方式使用神奇的 __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()
        【解决方案5】:

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

        $foo->bar = 'test';
        

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

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

        【讨论】:

          【解决方案6】:

          最好使用带有预定义自定义 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
          

          【讨论】:

            【解决方案7】:

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

            【讨论】:

              【解决方案8】:

              意图:

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

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2012-04-05
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2011-06-02
                • 1970-01-01
                • 2010-12-09
                相关资源
                最近更新 更多