PHP:检查返回值的类型是否是一种弥补PHP泛型缺失的良好实践?

3

注意: 为了防止因为“好的实践”基于个人观点而被踩,您也可以将问题改述为:检查返回值类型是补偿PHP缺乏泛型的好实践吗?(我没有使用这个,因为它暗示了存在缺陷)。

问题

从Java/C#世界来看,PHP的松散类型处理总是有点让人烦恼。当输入参数的类型提示引入后,情况有所改善,但我仍然缺少泛型和对返回值的类型提示。

我发现自己偶尔需要在代码中显式检查类型-这感觉有点不对,因为语言本身可以为我处理-我想向社区提出以下问题:

  • 检查返回值类型是补偿PHP缺乏泛型的好实践吗?
  • 是否有更好/更标准的方法来解决这个问题?
  • 目前正在讨论在PHP中实现泛型吗?

例子

为了更好地理解这个问题,考虑以下例子:

假设我们正在构建一个框架,将输入数据转换为其他输出数据。例如:

将表示XML文档的字符串转换为DomDocument,然后通过xpath表达式选择所述DomDocument的标题,并将其转换为另一个字符串。

(string) $xml =[TransformToDomDocument]=> (DomDocument) $doc =[TransformToString]=> (string) $title

现在假设输入的不是包含XML的字符串,而是Json(但是保持其他数据相同)。我们现在想要将Json输入转换为Json对象,并使用JsonPath表达式选择标题。

(string) $jsonString =[TransformToJson]=> (Json) $jsonObject =[TransformToString]=> (string) $title

(:第二个例子应该说明整个框架应该是非常灵活的。)

使用一系列适配器对象执行转换,这些对象处理从输入到输出的转换:

interface AdapterInterface{

  /**
    * Transform some input data into something else.
    * @param mixed $data
    * @return mixed
   */
  public function transform($data);

  /**
    * Set the Adapter that is used to preprocess the $data before calling $this->transform($data)
    * @param AdapterInterface $adapter
   */
  public function setPredecessorAdapter(AdapterInterface $adapter);

}

class XmlToDomDocumentAdapter implements AdapterInterface{

  private $predecessor;

  /**
    * Transform an xml string into a DOMDocument.
    * @param mixed $data
    * @return DomDocument
   */
  public function transform($data){

    if($this->predecessor !== null){
      $data = $this->predecessor->transform($data); 
      // At this point, we just have to "trust" that the predecessor returns a (string)
    }
    $doc = new DomDocument();
    $doc->loadXml($data);
    return $doc;
  }

}

class DomDocumentToStringAdapter implements AdapterInterface{

  private $xpathExpression;

  private $predecessor;

  /**
    * Transform a DomDocument into a string.
    * @param mixed $data
    * @return string
   */
  public function transform($data){

    if($this->predecessor !== null){
      $data = $this->predecessor->transform($data); 
      // At this point, we just have to "trust" that the predecessor returns a (DOMDocument)
    }
    $xpath = new DOMXpath($data);
    $nodes = $xapth->query($this->xpathExpression);
    if($nodes->length > 0){
        throw new UnexpectedValueException("Xpath didn't match");
    }
    $result = $nodes->item(0)->nodeValue;
    return $result;
  }

}

使用方法:

$input = "..."
$xmlToDom = new XmlToDomDocumentAdapater();
$domToString = DomDocumentToStringAdapter();
$domToString->setPredecessorAdapter($xmlToDom);
$output = $domToString->transform($input);

问题出现在适配器依赖于它的前置适配器返回正确的输入时。
    if($this->predecessor !== null){
      $data = $this->predecessor->transform($data); 
      // At this point, we just have to "trust" that the predecessor returns a (DOMDocument)
    }

在C#中,我会使用泛型来解决这个问题:Generics
interface AdapterInterface{

  /**
    * Tranform some input data into something else.
    * @param mixed $data
    * @return T
   */
  public function T transform<T>(object data);

}

/* using it */
//...

    if(this.predecessor !== null){
      data = this.predecessor.transform<string>(data); 
      // we now know for sure that the data is of type 'string'
    }
//...

由于PHP不支持泛型,我在思考是否在每次调用transform($data)后添加类型检查是一种好的实践:

    if($this->predecessor !== null){
      $data = $this->predecessor->transform($data); 
      if(!is_string($data){
        throw new UnexpectedValueException("data is not a string!");
      }
      // we now know for sure that the data is of type 'string'
    }

我的当前解决方案

我目前正在使用多个接口来定义transform方法的输出,如下所示:

interface ToStringAdapterInterface extends AdapterInterface{

  /**
    * Transform some input data into something else.
    * @param mixed $data
    * @return string <<< define expected output
   */
  public function transform($data);
}

interface ToDomDocumentAdapterInterface extends AdapterInterface{

  /**
    * Transform some input data into something else.
    * @param mixed $data
    * @return DOMDocument<<< define expected output
   */
  public function transform($data);
}

在每个转换器中,我会确保仅接受适合的接口作为前继接口:
class DomDocumentToStringAdapter implements ToStringAdapterInterface {

  private $xpathExpression;

  private $predecessor;

  public function __construct(ToDomDocumentAdapterInterface $predecessor){
      $this->predecessor = $predecessor;
  }
  // ...
}

4
实际上,无论是好主意还是不好,几乎没有人这样做。 - Barmar
另请参见https://github.com/box/augmented_types。 - deceze
1
没错,这些特性并没有被内置在语言本身中;如果你需要这些特性,你必须在用户层代码中复制它们。返回类型提示必须通过显式类型检查来完成,函数重载则需要使用switch语句或类似的方式。这些特性可以使代码更简洁,但它们的不存在并不会从根本上影响你编写同样功能的能力。 - deceze
@deceze 我完全同意你的观点。尽管如此,我发现了解其他开发人员的意见并了解他们处理情况的方式非常有帮助 :) - Hirnhamster
没有任何问题。我的观点是:决定它到底有多大的问题以及如果您不检查返回类型可能会发生什么不好的事情,然后再决定是否必须这样做。 :) - deceze
显示剩余2条评论
1个回答

0

我会遵循你的方法:测试$this->predecessor->transform($data)的返回值数据类型,如果不符合预期,则抛出异常。

我不知道你是否对Facebook的Hack编程语言感兴趣:

Hack是一个与PHP无缝互操作的HHVM编程语言。Hack将PHP的快速开发周期与静态类型提供的规范相结合,同时添加了许多其他现代编程语言常见的功能。


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