为了扩展 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 次时。如果高流量站点负担不起增加几台服务器以保持其应用程序运行,那么他们就做错了。在访问率成为问题的高流量网站页脚上的单行文字广告可能会为拥有该行文字的 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;
}
}
如果代码中有任何错误,我深表歉意。