PHP:类型检查返回值是弥补 PHP 缺乏泛型的好习惯吗?

Posted

技术标签:

【中文标题】PHP:类型检查返回值是弥补 PHP 缺乏泛型的好习惯吗?【英文标题】:PHP: Is type-checking return values a good practice to compensate PHP's lack of generics? 【发布时间】:2014-10-23 07:43:48 【问题描述】:

注意:为了防止被否决,因为良好做法可能是基于意见的 - 您也可以将问题改写为:类型检查返回的缺点是什么值来弥补 php 缺乏泛型?(我没有使用它,因为它暗示存在 缺点)。

问题

来自 Java/C# 世界,PHP 的松散类型处理总是有些烦人。引入输入参数的类型提示时它变得更好,但我仍然缺少generics 和返回值的类型提示。

我发现自己偶尔会通过显式检查代码中的类型来解决这个问题——这感觉有点不对劲,因为语言本身可以为我处理它——我想把这些问题推给社区:

类型检查返回值是弥补 PHP 缺乏泛型的好习惯吗? 有更好/更标准的方法吗? 目前是否正在讨论泛型以供将来在 PHP 中实现?

示例

要更好地了解为什么会出现问题,请考虑以下示例:

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

通过 xpath 表达式选择 DomDocument 的标题,将表示 XML 文档的字符串转换为 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;
  
  // ...

【问题讨论】:

好主意与否,实际上没有人这样做。 如果您使用类型提示或简单地对所有函数和对象的 input 参数进行类型验证,那么无论如何,这个问题几乎不存在。错误可能会进一步传播,因为它们是在下一个函数调用而不是在函数返回时进行类型检查的,但实际上这几乎不是问题。如果你绝对坚持这样的检查,我会添加一个简单的助手:\My\InvalidArgumentException::assertIs($returnValue, 'string') 另见github.com/box/augmented_types @deceze 检查输入参数引入了一个全新的问题,因为您需要将输入传播给前任——他们可能期望不同类型的输入参数。在 Java/C# 中,我将解决使用方法重载的问题——这(再次)在 PHP 中不可用...... 没错,这些东西并没有融入语言本身;如果您想要这些功能,则必须在用户代码中复制它们。返回类型提示必须通过显式类型检查、通过switch 语句或类似语句重载的函数来完成。这些功能可以使代码更简洁,但它们的不存在并不会从根本上影响您编写同类功能的能力。 【参考方案1】:

我会按照你的方法:测试 $this-&gt;predecessor-&gt;transform($data) 的返回值数据类型,如果不是预期的,则抛出异常。

不知道你是否对Hack programming language by Facebook感兴趣:

Hack 是一种无缝互操作的 HHVM 编程语言 用 PHP。 Hack 将 PHP 的快速开发周期与 静态类型提供的纪律,同时增加了许多功能 常见于其他现代编程语言中。

【讨论】:

以上是关于PHP:类型检查返回值是弥补 PHP 缺乏泛型的好习惯吗?的主要内容,如果未能解决你的问题,请参考以下文章

TS泛型类、泛型接口、泛型函数

Typescript 类型检查具有混合类型和混合泛型的数组

C# 泛型的使用

带有 Kotlin 的 Dagger 2,在 ApplicationComponent 中返回具有泛型的类型

java学习----泛型

泛型的相关