PHP __get和__set魔术方法

除非我完全错误, __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现在需要的地方,但是这不是很dynamic,需要知道“重载”代码实际上没有被调用,除非专门调用。 我想知道这是不是应该以我已经理解的方式工作,或者为什么这不起作用。

这是运行在php 5.3.3

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

请参阅手册中的“属性重载 ” 部分:

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

神奇的方法并不能取代吸气者和制定者。 他们只是让你处理方法调用或财产访问,否则会导致错误。 因此,更多的error handling相关。 另外请注意,它们比使用适当的getter和setter或直接方法调用要慢很多。

我build议使用一个数组通过__set()来存储所有的值。

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

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

从PHP手册 :

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

这只是读/写无法访问的属性。 然而,你的财产是公共的,这意味着它是可访问的。 将访问修改器更改为受保护可解决问题。

为了扩大Berry的答案,将访问级别设置为protected使得__get和__set与显式声明的属性一起使用(至less在类之外访问时),速度相当慢,我将引用另一个问题的评论在这个话题上,无论如何使用它的情况下:

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

根据Melsi的testing,相当慢的速度大约慢了5倍。 这肯定是相当慢,但也要注意,testing表明,你仍然可以用这个方法访问一个属性10,000次,计算循环迭代的时间,大约在1/100秒内。 与实际定义和定义的方法相比,速度要慢得多,这是一个轻描淡写的问题,但在事物的macros伟计划中,慢5倍的速度实际上并不慢。

操作的计算时间仍然可以忽略不计,99%的实际应用中不值得考虑。 唯一真正应该避免的是当你实际要在一个请求中访问10000次以上的属性时。 高stream量网站正在做一些真正错误的事情,如果他们无法承担投入更多的服务器,以保持其应用程序运行。 在访问率成为问题的高stream量站点的页脚上的单行文本广告可能支付具有该行文本的1000个服务器的场。 最终用户永远不会用手指敲打什么页面花费很长时间才能加载,因为您的应用程序的属性访问需要百万分之一秒。

我认为这是一个来自.NET背景的开发人员,但是不可见的获取和设置方法并不是.NET的发明。 它们不是没有它们的属性,而这些神奇的方法是PHP的开发者甚至可以调用它们的属性“属性”版本来保存优雅。 此外,用于PHP的Visual Studio扩展程序确实支持具有受保护属性的智能感知,考虑到这一点,我想。 我会用足够多的开发人员以这种方式使用魔术__get和__set方法,PHP开发人员会调整执行时间,以迎合开发人员社区。

编辑:从理论上讲,保护属性似乎是在大多数情况下工作。 在实践中,事实certificate,在访问类定义和扩展类中的属性时,有很多时候你会想要使用getter和setter。 更好的解决scheme是扩展其他类时的基类和接口,所以您可以将基类中的几行代码复制到实现类中。 我正在做更多与我的项目的基类,所以我没有现在提供的接口,但这里是未经testing的剥离类定义与魔术属性获取和设置使用reflection去除和移动属性受保护的数组:

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

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

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

 $foo->bar = 'test'; 

运行上述操作时无需调用魔术方法。

删除public $bar; 从你的class级应该纠正这一点。

放弃public $bar; 宣言,它应该按预期工作。

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

“神奇的方法不能取代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 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; } } } private function setNameOfTheItem($value){ // no filter $this->data['name'] = strtoupper($value); //asign 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 - exided 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 triger magic function __set() without any controll 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