覆盖函数不兼容性

6

简述

我想覆盖 ArrayObject 中的 offsetSet($index,$value) 方法,像这样: offsetSet($index, MyClass $value),但会生成一个致命错误("declaration must be compatible")。

什么及为什么

我正在尝试创建一个 ArrayObject 子类,强制所有值都是某个对象。我的计划是通过覆盖添加值的所有函数并给它们一个类型提示来实现,这样你就不能添加任何不是 MyClass 值的东西

如何操作

首先: append($value);
来自 SPL:

/**
 * Appends the value
 * @link http://www.php.net/manual/en/arrayobject.append.php
 * @param value mixed <p>
 * The value being appended.
 * </p>
 * @return void 
 */
public function append ($value) {}

My version:

/**
 * @param MyClass $value
 */
public function append(Myclass $value){
    parent::append($value);
}

看起来非常顺利。

您可以在此处找到此示例的工作方式

第二步:offsetSet($index,$value);

同样来自SPL:

/**
 * Sets the value at the specified index to newval
 * @link http://www.php.net/manual/en/arrayobject.offsetset.php
 * @param index mixed <p>
 * The index being set.
 * </p>
 * @param newval mixed <p>
 * The new value for the index.
 * </p>
 * @return void 
 */
public function offsetSet ($index, $newval) {}

我的版本:

/**
 * @param mixed $index
 * @param Myclass $newval
 */
public function offsetSet ($index, Myclass $newval){
    parent::offsetSet($index, $newval);
}

然而,这会产生以下致命错误:

致命错误:Namespace\MyArrayObject::offsetSet()的声明必须与ArrayAccess::offsetSet()相兼容

你可以在这里看到一个不能正常工作的版本 如果我像这样定义它,就可以了:
public function offsetSet ($index, $newval){
    parent::offsetSet($index, $newval);
}

您可以在此处查看此工作版本

问题

  1. 为什么覆盖offsetSet()不适用于上面的代码,而append()适用?
  2. 如果我在append()offsetSet()的定义旁边添加exchangeArray()的定义,那么我是否拥有添加对象的所有函数?

我知道在调用父函数之前可以在函数中进行类型检查,但是除了这个检查与我知道自己做错了什么无关之外,它也不会为该类的任何用户提供易于使用的类型提示。 - Nanne
2个回答

3

APIs不应该变得更加具体。

事实上,我认为append(Myclass $value)不是致命错误是一个bug。对于offsetSet()的致命错误是正确的。

原因很简单:

function f(ArrayObject $ao) { 
    $ao->append(5); //Error
} 

$ao = new YourArrayObject(); 

使用带类型要求的append会出错,但看起来似乎没有问题。实际上,您已经使API更具体化,对基类的引用不再能够假定具有预期的API。
基本上可以理解为,如果API变得更具体化,则子类将不再与其父类兼容。
这种奇怪的差异可以在f中看到:它允许您将Test传递给它,但在执行$ao->append(5)时会失败。如果上面有echo 'hello world';,那么它将执行。我认为这是错误的行为。
在像C++、Java或C#这样的语言中,这就是泛型发挥作用的地方。在PHP中,恐怕没有一个漂亮的解决方案。运行时检查会很麻烦且容易出错,而自己编写类会完全摧毁ArrayObject作为基类的优势。不幸的是,想要将ArrayObject作为基类也是问题所在。它存储混合类型,因此您的子类也必须存储混合类型。
您可以在自己的类中实现ArrayAccess接口,并清楚地标记该类仅适用于某种类型的对象。但是,我担心这仍然有些笨拙。
没有泛型,没有运行时instanceof样式检查的情况下,就没有办法拥有通用的同质容器。唯一的方法是拥有ClassAArrayObject、ClassBArrayObject等。

我在某种程度上同意你的说法,尤其是你的例子。但是,我需要一种数据结构来保证(如果可能的话,还带有类型提示等),它只包含类型为MyClass的对象。在函数中进行检查很容易,但这并不能轻松地帮助使用/创建该对象。目标是拥有只包含一种类型对象的ArrayObject,并且这似乎是一个有效的选项(除了它实际上不是:))。 - Nanne

3
abstract public void offsetSet ( mixed $offset , mixed $value )

ArrayAccess 接口声明了 public mixed offsetGet ( mixed $offset ) 方法,而 public void append ( mixed $value ) 没有相应的接口。显然,在后一种情况下,php比在接口方面更加“宽容”。

例如:

<?php
class A {
    public function foo($x) { }
}

class B extends A {
    public function foo(array $x) { }
}

"only"会打印一条警告信息。

Strict Standards: Declaration of B::foo() should be compatible with A::foo($x)

当 ⏎
<?php
interface A {
    public function foo($x);
}

class B implements A {
    public function foo(array $x) { }
}

逃脱(bails out with)
Fatal error: Declaration of B::foo() must be compatible with A::foo($x)

嗯,当然。结合@corbin的例子,这意味着我无论如何都不应该这样做。如果您对如何修复我的最终目标(例如仅包含一种类型变量的对象数组)有想法,我会非常感激,但也许我应该重新开始或者提出第二个问题 :) - Nanne

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接