php 没有重载,php-获取“重载属性的间接修改无效” noti

我知道这现在已经是一个古老的话题,但这是我今天第一次遇到自己,我认为如果我根据自己的发现扩展上面所说的内容,可能会对其他人有所帮助。

据我所知,这不是PHP中的错误。 实际上,我怀疑PHP解释器必须付出特殊的努力才能如此具体地检测和报告此问题。 它与您访问“ foo”变量的方式有关。

Registry::getInstance()->foo

当PHP看到语句的这一部分时,它要做的第一件事就是检查对象实例是否具有称为“ foo”的可公共访问的变量。 在这种情况下,它不是,所以下一步是调用其中一种魔术方法,即__set()(如果您尝试替换当前值“ foo”)或__get()(如果您正在 尝试访问该值)。

Registry::getInstance()->foo = array(1, 2, 3);

在此语句中,您尝试将“ foo”的值替换为array(1、2、3),因此PHP使用$ key =“ foo”和$ value = array(1、2、2来调用__set()方法 3),一切正常。

Registry::getInstance()->foo[] = 4;

但是,在此语句中,您正在检索“ foo”的值,以便可以对其进行修改(在这种情况下,将其视为数组并附加一个新元素)。 该代码意味着您想要修改实例所持有的“ foo”的值,但实际上您实际上是在修改__get()返回的foo的临时副本,因此PHP会发出警告(如果您 通过引用而不是通过值将Registry :: getInstance()-> foo传递给函数)。

您有一些解决此问题的方法。

方法一

您可以将“ foo”的值写入变量,修改该变量,然后将其写回,即

$var = Registry::getInstance()->foo;

$var[] = 4;

Registry::getInstance()->foo = $var;

功能齐全,但非常冗长,因此不建议使用。

方法2

根据cillosis的建议,让__get()函数通过引用返回(不需要让__set()函数通过引用返回,因为根本不应该返回值)。 在这种情况下,您需要知道PHP只能返回对已经存在的变量的引用,并且如果违反此约束,则可能发出通知或行为异常。 如果我们查看适合于您的班级的cillosis的__get()函数(如果您确实选择沿这条路线走,则出于下面说明的原因,请坚持使用__get()的实现,并认真地进行是否存在检查,然后再进行任何读取 从您的注册表):

function &__get( $index )

{

if( array_key_exists( $index, $this->_registry ) )

{

return $this->_registry[ $index ];

}

return;

}

如果您的应用程序从未尝试过获取注册表中尚不存在的值,那很好,但是一旦您这样做,您将点击“ return”。 语句并获得“仅应按引用返回变量引用”警告,并且您无法通过创建后备变量并返回该变量来解决此问题,因为这将使您“对重载属性的间接修改无效”警告 再次出于与以前相同的原因。 如果您的程序没有任何警告(警告是一件坏事,因为警告会污染您的错误日志并影响您的代码到PHP其他版本/配置的可移植性),那么您的__get()方法将必须创建条目 在返回它们之前不存在,即

function &__get( $index )

{

if (!array_key_exists( $index, $this->_registry ))

{

// Use whatever default value is appropriate here

$this->_registry[ $index ] = null;

}

return $this->_registry[ $index ];

}

顺便说一句,PHP本身似乎通过其数组执行了与此非常相似的操作,即:

$var1 = array();

$var2 =& $var1['foo'];

var_dump($var1);

上面的代码(至少在某些版本的PHP上)将输出类似“ array(1){[“ foo”] =>&NULL}“的内容,表示“ $ var2 =&$ var1 ['foo'];” 语句可能会影响表达式的两面。 但是,我认为从读取操作更改变量的内容从根本上来说很不好,因为它可能导致一些严重的错误(因此,我认为上述数组行为是PHP错误)。

例如,让我们假设您只将对象存储在注册表中,并且修改了__set()函数以在$ value不是对象的情况下引发异常。 注册表中存储的任何对象还必须符合特殊的“ RegistryEntry”接口,该接口声明必须定义“ someMethod()”方法。 因此,您的注册表类的文档指出,调用者可以尝试访问注册表中的任何值,并且结果将是检索有效的“ RegistryEntry”对象,如果该对象不存在,则为null。 我们还假设您进一步修改注册表以实现Iterator接口,以便人们可以使用foreach构造遍历所有注册表项。 现在想象下面的代码:

function doSomethingToRegistryEntry($entryName)

{

$entry = Registry::getInstance()->$entryName;

if ($entry !== null)

{

// Do something

}

}

...

foreach (Registry::getInstance() as $key => $entry)

{

$entry->someMethod();

}

这里的合理性是doSomethingToRegistryEntry()函数知道从注册表读取任意条目是不安全的,因为它们可能存在或不存在,因此它会检查“ null”情况并相应地运行。 一切都很好。 相比之下,循环“知道”对注册表的任何写操作都将失败,除非所写的值是符合“ RegistryEntry”接口的对象,因此不必费心检查以确保$ entry确实是 这样的对象节省了不必要的开销。 现在,我们假设有一种非常罕见的情况,在这种情况下,尝试读取尚不存在的任何注册表项之后的某个时间会到达此循环。 砰!

在上述情况下,该循环将生成致命错误“在非对象上调用成员函数someMethod()的成员”(并且,如果警告为Bad Things,则致命错误为灾难)。 发现这实际上是由上个月的更新添加的程序中其他地方看似无害的读取操作引起的,这并不是一件容易的事。

就我个人而言,我也避免使用这种方法,因为尽管在大多数情况下它看起来表现良好,但如果被激怒,它确实会给您带来极大的痛苦。 令人高兴的是,有一个简单得多的解决方案。

方法3

只是不要定义__get(),__set()或__isset()! 然后,PHP将在运行时为您创建属性,并使它们可公开访问,以便您可以在需要时直接直接访问它们。 完全不必担心引用,如果您希望注册表是可迭代的,则仍然可以通过实现IteratorAggregate接口来实现。 考虑到您在原始问题中给出的示例,我相信这是迄今为止最好的选择。

final class Registry implements IteratorAggregate

{

private static $_instance;

private function __construct() { }

public static function getInstance()

{

if (self::$_instance == null) self::$_instance = new self();

return self::$_instance;

}

public function getIterator()

{

// The ArrayIterator() class is provided by PHP

return new ArrayIterator($this);

}

}

实现__get()和__isset()的时机是您要为调用者提供对某些私有/受保护属性的只读访问权,在这种情况下,您不想通过引用返回任何内容。

我希望这个对你有用。 :)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值