【问题标题】:Can parameter types be specialized in PHP参数类型可以在 PHP 中特化吗
【发布时间】:2011-06-01 00:51:57
【问题描述】:

假设我们有以下两个类:

abstract class Foo {
    public abstract function run(TypeA $object);
}

class Bar extends Foo {
    public function run(TypeB $object) {
        // Some code here
    }
}

TypeB 类扩展了 TypeA 类。

尝试使用它会产生以下错误消息:

Bar::run() 的声明必须与 Foo::run() 的声明兼容

当涉及到参数类型时,PHP 真的这么糟糕吗,还是我只是错过了这一点?

【问题讨论】:

  • 您确定Foo 不是接口且run() 不是final
  • 好点,佩卡。这是一种抽象方法。我会纠正的:)

标签: php parameters types


【解决方案1】:

PHP 7.4 (partially since 7.2)以来,此答案已过时。


您描述的行为称为covariance,PHP 根本不支持。我不知道内部原理,但我可能怀疑 PHP 的核心在应用所谓的“类型提示”检查时根本不会评估继承树。

顺便说一句,PHP 也不支持 contravariance 那些类型提示(其他 OOP 语言中通常支持的功能) - 很可能是上面怀疑的原因。所以这也不起作用:

abstract class Foo {
    public abstract function run(TypeB $object);
}

class Bar extends Foo {
    public function run(TypeA $object) {
        // Some code here
    }
}

最后还有更多信息:http://www.php.net/~derick/meeting-notes.html#implement-inheritance-rules-for-type-hints

【讨论】:

  • 谢谢斯特凡。我知道这适用于大多数其他 OO 语言,如 Java、.NET、SmallTalk 等。我想我必须稍微放松一下我的设计,并转向反射以确保安全。
  • 我之前的发言似乎有点草率。 Java 参数实际上是不变的,但由于 Java 支持方法重载,因此可以给人一种协变的印象。
  • 自 PHP 7.4 以来不再是这样:PHP RFC: Covariant Returns and Contravariant Parameters
【解决方案2】:

这似乎与大多数 OO 原则非常一致。 PHP 不像.Net - 它不允许您覆盖类成员。 Foo 的任何扩展都应该滑入之前使用 Foo 的位置,这意味着您不能放松约束。

简单的解决方案显然是删除类型约束,但如果Bar::run() 需要不同的参数类型,那么它确实是不同的函数,理想情况下应该有不同的名称。

如果 TypeATypeB 有任何共同点,请将共同元素移至基类并将其用作参数约束。

【讨论】:

  • 扩展类型不是特化它,从而收紧约束而不是放松约束吗?
  • 确实如此。我建议使用基类作为 arg 类型,以便其所有子类(TypeA 和 TypeB)都符合。当然,假设这在您的设置中是可能的。
【解决方案3】:

我认为这是by design:抽象定义的重点是定义其方法的底层行为。

从抽象类继承时,所有在父类声明中标记为抽象的方法都必须由子类定义;此外,这些方法必须定义为具有相同(或限制较少)的可见性。

【讨论】:

  • 当然,但这与方法可见性有关,而不是参数类型。
  • @Johan:你为什么认为这与方法可见性有关?
  • @Stefan 他可能指的是我引用的块,诚然这并没有解决手头的问题。还是@Johan,基本原则是有效的OOP而不是bug
  • 没错,佩卡。我指的是引用的文本,而不是 PHP 手册中的引用文章。
【解决方案4】:

总是可以在代码中添加约束:

public function run(TypeA $object) {
    assert( is_a($object, "TypeB") );

然后,您必须记住或记录特定的类型限制。优点是它成为纯粹的开发工具,因为断言通常在生产服务器上关闭。 (实际上,这是在开发过程中发现的一类错误,而不是随机破坏生产。)

【讨论】:

    【解决方案5】:

    问题中显示的代码不会在 PHP 中编译。如果它确实类Bar 将无法兑现其父Foo 做出的能够接受TypeA 的任何实例的保证,并违反Liskov Substitution Principle

    目前相反的代码也无法编译,但在预计于 2019 年 11 月 28 日发布的 PHP 7.4 版本中,使用新的 contravariant arguments 功能,下面的类似代码有效:

    abstract class Foo {
        public abstract function run(TypeB $object); // TypeB extends TypeA
    }
    
    class Bar extends Foo {
        public function run(TypeA $object) {
            // Some code here
        }
    }
    

    所有的 Bars 都是 Foos,但并非所有的 Foos 都是 Bars。所有 TypeB 都是 TypeAs,但并非所有 TypeA 都是 TypeB。任何 Foo 都将能够接受任何 TypeB。那些也是 Bars 的 Foo 也将能够接受非 TypeB 的 TypeAs。

    PHP 还将支持协变返回类型,其工作方式相反。

    【讨论】:

      【解决方案6】:

      虽然你不能使用整个类层次结构作为类型提示。您可以使用selfparent 关键字在某些情况下强制执行类似的操作。

      引用 PHP 手册 cmets 中的 r dot wilczek at web-appz dot de

      <?php
      interface Foo 
      {
          public function baz(self $object);
      }
      
      class Bar implements Foo
      {
          public function baz(self $object)
          {
              // 
          }
      }
      ?>
      

      现在还没有提到的是,你也可以使用 'parent' 作为类型提示。带有接口的示例:

      <?php
      interface Foo 
      {
          public function baz(parent $object); 
      }
      
      class Baz {}
      class Bar extends Baz implements Foo
      {
          public function baz(parent $object)
          {
              // 
          }
      }
      ?>
      

      Bar::baz() 现在将接受任何 Baz 实例。 如果 Bar 不是任何类的继承者(没有“扩展”),PHP 将引发致命错误: '当前类范围没有父级时无法访问父级::'。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-12-15
        • 2021-10-17
        • 2022-08-08
        • 2012-06-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多