【问题标题】:Why are the method declarations not compatible?为什么方法声明不兼容?
【发布时间】:2012-12-02 01:34:37
【问题描述】:

为什么我会收到此错误:

Fatal error: Declaration of ConcreteFooMapper::load() must be compatible with that of AbstractFooMapper::load() on line 18

从此代码:

<?php
interface Foo {
    public function foo();
}

class ConcreteFoo implements Foo {
    public function foo() {

    }
}

abstract class AbstractFooMapper {
    abstract public function load(Foo $entity, array $data);
}

class ConcreteFooMapper extends AbstractFooMapper {
    public function load(ConcreteFoo $entity, array $data) {

    }
}
?>

我最初的想法是这是一个错误; PHP 在评估方法声明时没有检测到 ConcreteFoo 实现了 Foo 。我认为这是因为当您运行此代码时:

<?php
interface Foo {
    public function foo();
}

class ConcreteFoo implements Foo {
    public function foo() {

    }
}

$foo = new ConcreteFoo();

if ($foo instanceof Foo) 
{
    print 'w00t!';
} 
else 
{
    print 'FAIL!';
}
?>

它打印出 w00t! 表明 ConcreteFooFoo 的一个实例。

对这种行为是否正确有任何见解吗?

【问题讨论】:

    标签: php oop inheritance


    【解决方案1】:

    有趣的是你可以使用构造函数来加强谓词:

    // ABuilder.php
    
    abstract class ABuilder
    {
        public function __construct(BaseApi $api = null)
        {
            $this->api = $api;
        }
    
        public static function fromArray(array $data, BaseApi $api = null)
        {
            $entity = new static($api);
    
            // ...
    
            return $entity;
        }
    }
    
    // ContactBuilder.php
    class Contact extends ABuilder
    {
        // here, you cannot change signature of ::fromArray to require ContactApi
        // (which extends/implements BaseApi), like in this example:
        public static function fromArray(array $data, ContactApi $api = null) {}
    
        // but you can specify ContactApi in the constructor arguments:
        public function __construct(ContactApi $api = null) {}
    }
    

    因此,当您尝试使用错误的 Api 实现(甚至通过 ::fromArray() 结构)初始化您的 ContactBuilder 类时,您将收到正确的类型错误消息,例如:

    传递给 Builder/Contact::__construct() 的参数 1 必须是 ContactApi 的实例或 null,LeadApi 的实例给定

    【讨论】:

      【解决方案2】:

      根据docs,类型提示必须完全匹配。

      【讨论】:

      • 好的;很高兴我对多态性的理解没有错,只是 PHP 的实现。
      • 在扩展类中加强谓词会违反 Liskov 的替换原则;这可能不是根本原因,但它对我有用:)
      • 会不会因为方差不符合原则?
      • PHP 行为正常。如所写,该方法的某些覆盖可能接受仅实现Foo 的内容,而其他一些可能仅接受ConcreteFooMapper。那将不是正确的多态性。 override 方法可以为Foo 键入提示,并且仍然接受Conrete... 类型的参数。如果绝对必要,它可以在其代码中验证Foo 实现者的类。
      【解决方案3】:

      实现接口的类必须使用与接口中定义的完全相同的方法签名。不这样做会导致致命错误。以及扩展抽象类的类的相同规则。

      请看see in details here,see here too

      这是正确的行为\逻辑。

      check here 抽象类型很有用,因为它们可用于定义和执行协议;实现协议的所有对象都必须支持的一组操作。

      如果我们假设您的代码将毫无例外地工作,那么我们有以下问题:ConcreteFooMapper 不能使用某些class ConcreteFoo2 implements Foo 的实例作为load 方法的参数,但应该(通过抽象类定义)

      另外,如果你使用相同的签名,这真的不是问题,因为所有类\类型信息都可用。请检查以下代码

      <?php
      interface Foo {
              public function foo();
      }
      
      class ConcreteFoo implements Foo {
              public function foo() {
              }
      }
      
      abstract class AbstractFooMapper {
              abstract public function load(Foo $entity, array $data);
      }
      
      class ConcreteFooMapper extends AbstractFooMapper {
              public function load(Foo $entity, array $data) {
                      var_dump($entity instanceof Foo);
                      var_dump($entity instanceof ConcreteFoo);
              }
      
      }
      
      $someConcreteFoo = new ConcreteFoo();
      $someFooMapper = new ConcreteFooMapper();
      
      $someFooMapper->load($someConcreteFoo, array('somekey' => 'somevalue'));
      // output
      // bool(true) bool(true)  
      
      ?>
      

      【讨论】:

      • 啊。这就说得通了。我希望使用类型提示来强制仅在ConcreteFooMapperload 方法中传递ConcreteFoo 的实例,但现在我看到了它是如何破坏协议/合同的。我将使用instanceof 在方法的开头添加类型检查。这是我过去所做的。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-01-11
      • 1970-01-01
      • 1970-01-01
      • 2011-03-08
      相关资源
      最近更新 更多