如何制作PHP类,扩展抽象类,需要更具体的类型


How to make PHP class, extending an abstract class, require a more specific type

我使用的是表-数据-网关模式。我所有的模型都不是映射器或DB_Table类扩展Model_Base。我所有的映射器都扩展了Model_MapperBase。我所有的DB_Table类都扩展了Zend_Db_Table_Abstract

在我所有的映射器类中,我想强制有一个函数map,它接受一个DB_Row对象,以及映射到的模型类。理想情况下,我希望能够做到的是,在我的Model_MapperBase中,我会有:

abstract public function map(Model_Base $model, Zend_Db_Table_Row_Abstract $row);

然后当我实现它时,我会这样做:

public function map(Model_User $user, Zend_Db_Table_Row_Abstract $row) {
    $user->setId($row->id)
         ->setName($row->name);
    return $user;
}

但显然,这不起作用,因为抽象函数说它应该采用任何扩展Model_Base的类。但我想确保,是的,它必须是一个扩展Model_Base,但然后我想有实现是更具体的

我唯一能想到的是:

public function map(Model_Base $user, Zend_Db_Table_Row_Abstract $row) {
  if (get_class($user) != 'Model_User') { 
      throw new Exception('Invalid Class');
  } else {
      $user->setId($row->id)
           ->setName($row->name);
      return $user;
  }
}

但在某种程度上,这似乎是错误的。有更好的方法吗?

编辑:所以,对下面的不满意,我采取了这个:

final public function map(Model_Base $model, 
                          Zend_Db_Table_Row_Abstract $row)
{
    if (get_class($this) != get_class($model).'Mapper') {
        throw new Exception("Invalid Class: Expected " . substr(get_class($this), 0, strlen($this)-6) . '. Received ' .
            get_class($model) . '.');
    }
    return $this->mapImplementation($model, $row);
}
abstract protected function mapImplementation(Model_Base $model, 
                                              Zend_Db_Table_Row_Abstract $row);

然后让我的Mapper类实现mapImplementation方法,但总是调用map方法。这有效地完成了我想要的,但似乎有点做作,只是碰巧工作,因为到目前为止,我所有的Mapper类都有一个同名的类,没有"Mapper"。

是否有更好的方法来完成我正在尝试做的事情,而不依赖于命名方案或在每个类中抛出异常?

编辑:我不是在寻找"使用instanceOf而不是get_class"-这对我的目的来说本质上是一样的,如果我计划扩展我的非基映射器类,我会做出改变。我正在寻找一种方法来强制正确的类类型,而无需手动抛出异常。

我不知道这会带来多少好处但一种方法是在抽象类上创建map方法,就像这样

final public function map(Model_Base $user, Zend_Db_Table_Row_Abstract $row) {
    return $this->_map($user, $row);
}

然后为您的用户映射

public function _map(Model_User $user, Zend_Db_Table_Row_Abstract $row) {
    $user->setId($row->id)
         ->setName($row->name);
    return $user;
}

由于这是不可能改变方法的头,同时扩展抽象类,我认为你的解决方案是好的。

但是在我看来修改代码为

public function map(Model_Base $user, Zend_Db_Table_Row_Abstract $row) {
if (!$user instanceof Model_User)
  throw new Exception('Invalid Class');

会更好,因为如果您希望在后代类中重写此方法,而使用parent::map(...)之类的东西,它会更灵活。

如果classA扩展了classB,并且$objectA$objectB分别是类的实例,则$objectA instanceof classBtrue,而不是get_class($objectA) === 'classA'

PHP要求方法签名必须相同。

方法的签名必须匹配,即类型提示和所需参数的数量必须相同。

http://php.net/manual/en/language.oop5.abstract.php

这是有意义的,因为你的抽象类为使用它的类定义了一个契约,而你正试图在你的实现中改变这个契约。天真地,我应该期望能够传递一个Model_Base到这个函数的任何地方,它应该能够工作。

因此,为了在派生类中强制执行更严格的类型,您需要进行某种类型检查。我更喜欢与使用instanceof的其他答案相同的方法。这允许在不修改代码的情况下使用该类的扩展。